milight-v6 0.1.1 → 0.3.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: 565325531747b368d29626966da14d2d2f7281ee9a4d9ed559e068207ffcf999
4
+ data.tar.gz: a59efd0eacc27c31b685e1daa47451ad7711d05e2ada708deeadc708ccfd23ce
5
5
  SHA512:
6
- metadata.gz: f2480b71b8e1e6c31d7d41dd14052d856d5dfbe8f473e2250fdd533b2a404b096831e86601f6e9d520b69dc805e3fd73c2b93b80b83a5362fca6588efd135ad9
7
- data.tar.gz: aa7c8814f18470f8623358b9d5366770159564a982c8f58e9ca2a0d14c558f87cc6e90e9c210d9f1b8979212cfa1d7d9f005c5e884b29ec4f4879df920391f20
6
+ metadata.gz: cd823ac8b7d7488b9ba31fd78a677937887216ea8c56491242947de817e1eab1acccac1df648f21d3b89a68b7d1f423e7616452315cad8cf7193722c04341a18
7
+ data.tar.gz: 8da346ed906eba3f01ca395c61be17c18d3bf23793f7e3f142ceeec930679935b5052dae959c02f6ad08aaaec3c287271009fa8098d3aabf699ed932df39abc8
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # Milight Wifi Bridge v6 Ruby API
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/milight-v6.svg)](https://badge.fury.io/rb/milight-v6)
4
- [![Build Status](https://travis-ci.org/ppostma/milight-v6-api.svg?branch=master)](https://travis-ci.org/ppostma/milight-v6-api)
5
- [![Code Climate](https://codeclimate.com/github/ppostma/milight-v6-api/badges/gpa.svg)](https://codeclimate.com/github/ppostma/milight-v6-api)
4
+ [![CI](https://github.com/ppostma/milight-v6-api/actions/workflows/ci.yml/badge.svg)](https://github.com/ppostma/milight-v6-api/actions/workflows/ci.yml)
5
+ [![CodeQL](https://github.com/ppostma/milight-v6-api/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/ppostma/milight-v6-api/actions/workflows/github-code-scanning/codeql)
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 <host> <command> [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 { |controller| puts controller }
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 Milight::V6::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]
@@ -100,12 +63,39 @@ module Milight
100
63
  def calculate_checksum(bytes)
101
64
  checksum = 0
102
65
 
103
- for i in 10..19 do
66
+ (10..19).each do |i|
104
67
  checksum += bytes[i]
105
68
  end
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,64 @@ 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)
34
- retry if ready
33
+ retry if socket.wait_readable(READ_TIMEOUT)
35
34
 
36
- return false
35
+ nil
36
+ end
37
+
38
+ def close
39
+ socket.close
37
40
  end
38
41
 
39
42
  private
40
43
 
44
+ def socket
45
+ @socket ||= begin
46
+ socket = UDPSocket.new
47
+
48
+ if @host == "<broadcast>" || @host == "255.255.255.255"
49
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true)
50
+ end
51
+
52
+ socket
53
+ end
54
+ end
55
+
56
+ def logger
57
+ @logger ||= begin
58
+ logger = Logger.new($stdout)
59
+ logger.level = Logger::INFO if ENV["MILIGHT_DEBUG"] != "1"
60
+ logger
61
+ end
62
+ end
63
+
41
64
  def format_bytes_as_hex(bytes)
42
- bytes.map { |s| format("0x%02X", s) }
65
+ bytes.map { |s| format("0x%02X", s) }.join(", ")
43
66
  end
44
67
  end
45
68
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Milight
4
4
  module V6
5
- VERSION = "0.1.1"
5
+ VERSION = "0.3.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
metadata CHANGED
@@ -1,43 +1,56 @@
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.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Postma
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2019-01-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: logger
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: bundler
15
28
  requirement: !ruby/object:Gem::Requirement
16
29
  requirements:
17
30
  - - "~>"
18
31
  - !ruby/object:Gem::Version
19
- version: '1.16'
32
+ version: '4.0'
20
33
  type: :development
21
34
  prerelease: false
22
35
  version_requirements: !ruby/object:Gem::Requirement
23
36
  requirements:
24
37
  - - "~>"
25
38
  - !ruby/object:Gem::Version
26
- version: '1.16'
39
+ version: '4.0'
27
40
  - !ruby/object:Gem::Dependency
28
41
  name: rake
29
42
  requirement: !ruby/object:Gem::Requirement
30
43
  requirements:
31
44
  - - "~>"
32
45
  - !ruby/object:Gem::Version
33
- version: '10.0'
46
+ version: '13.0'
34
47
  type: :development
35
48
  prerelease: false
36
49
  version_requirements: !ruby/object:Gem::Requirement
37
50
  requirements:
38
51
  - - "~>"
39
52
  - !ruby/object:Gem::Version
40
- version: '10.0'
53
+ version: '13.0'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: rspec
43
56
  requirement: !ruby/object:Gem::Requirement
@@ -61,33 +74,25 @@ executables:
61
74
  extensions: []
62
75
  extra_rdoc_files: []
63
76
  files:
64
- - ".gitignore"
65
- - ".rspec"
66
- - ".rubocop.yml"
67
- - ".travis.yml"
68
- - Gemfile
69
- - Gemfile.lock
70
77
  - LICENSE.txt
71
78
  - README.md
72
- - Rakefile
73
- - bin/console
74
79
  - bin/milight
75
- - bin/setup
76
80
  - lib/milight/v6.rb
77
81
  - lib/milight/v6/all.rb
82
+ - lib/milight/v6/bridge.rb
78
83
  - lib/milight/v6/color.rb
79
84
  - lib/milight/v6/command.rb
80
85
  - lib/milight/v6/controller.rb
86
+ - lib/milight/v6/discover.rb
81
87
  - lib/milight/v6/exception.rb
82
88
  - lib/milight/v6/socket.rb
83
89
  - lib/milight/v6/version.rb
84
90
  - lib/milight/v6/zone.rb
85
- - milight-v6.gemspec
86
91
  homepage: https://github.com/ppostma/milight-v6-api
87
92
  licenses:
88
93
  - MIT
89
- metadata: {}
90
- post_install_message:
94
+ metadata:
95
+ rubygems_mfa_required: 'true'
91
96
  rdoc_options: []
92
97
  require_paths:
93
98
  - lib
@@ -95,16 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
100
  requirements:
96
101
  - - ">="
97
102
  - !ruby/object:Gem::Version
98
- version: '0'
103
+ version: 3.2.0
99
104
  required_rubygems_version: !ruby/object:Gem::Requirement
100
105
  requirements:
101
106
  - - ">="
102
107
  - !ruby/object:Gem::Version
103
108
  version: '0'
104
109
  requirements: []
105
- rubyforge_project:
106
- rubygems_version: 2.6.14
107
- signing_key:
110
+ rubygems_version: 4.0.24
108
111
  specification_version: 4
109
112
  summary: Ruby API for the Milight Wifi Bridge v6
110
113
  test_files: []
data/.gitignore DELETED
@@ -1,14 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
-
13
- .ruby-version
14
- .ruby-gemset
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,26 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.3
3
- DisplayCopNames: true
4
- DisplayStyleGuide: true
5
- Exclude:
6
- - 'lib/milight/v6/api/version.rb'
7
- - 'vendor/**/*'
8
-
9
- Metrics/BlockLength:
10
- Exclude:
11
- - 'spec/**/*.rb'
12
-
13
- Metrics/LineLength:
14
- Enabled: false
15
-
16
- Style/ConditionalAssignment:
17
- Enabled: false
18
-
19
- Style/Documentation:
20
- Enabled: false
21
-
22
- Style/For:
23
- Enabled: false
24
-
25
- Style/StringLiterals:
26
- Enabled: false
data/.travis.yml DELETED
@@ -1,10 +0,0 @@
1
- language: ruby
2
- sudo: false
3
- cache: bundler
4
- before_install:
5
- - gem update --system
6
- - gem install bundler
7
- rvm:
8
- - 2.3.8
9
- - 2.4.5
10
- - 2.5.3
data/Gemfile DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
-
7
- # Specify your gem's dependencies in milight-v6.gemspec
8
- gemspec
9
-
10
- gem 'rubocop', '~> 0.52.1', group: :test
data/Gemfile.lock DELETED
@@ -1,51 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- milight-v6 (0.1.1)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- ast (2.3.0)
10
- diff-lcs (1.3)
11
- parallel (1.12.1)
12
- parser (2.4.0.2)
13
- ast (~> 2.3)
14
- powerpack (0.1.1)
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)
24
- diff-lcs (>= 1.2.0, < 2.0)
25
- rspec-support (~> 3.7.0)
26
- rspec-mocks (3.7.0)
27
- diff-lcs (>= 1.2.0, < 2.0)
28
- rspec-support (~> 3.7.0)
29
- rspec-support (3.7.0)
30
- rubocop (0.52.1)
31
- parallel (~> 1.10)
32
- parser (>= 2.4.0.2, < 3.0)
33
- powerpack (~> 0.1)
34
- rainbow (>= 2.2.2, < 4.0)
35
- ruby-progressbar (~> 1.7)
36
- unicode-display_width (~> 1.0, >= 1.0.1)
37
- ruby-progressbar (1.9.0)
38
- unicode-display_width (1.3.0)
39
-
40
- PLATFORMS
41
- ruby
42
-
43
- DEPENDENCIES
44
- bundler (~> 1.16)
45
- milight-v6!
46
- rake (~> 10.0)
47
- rspec (~> 3.0)
48
- rubocop (~> 0.52.1)
49
-
50
- BUNDLED WITH
51
- 1.16.1
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
- require "rubocop/rake_task"
6
-
7
- RSpec::Core::RakeTask.new(:spec)
8
- RuboCop::RakeTask.new(:rubocop)
9
-
10
- task default: %i[spec rubocop]
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "milight/v6"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/milight-v6.gemspec DELETED
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path("../lib", __FILE__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require "milight/v6/version"
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = "milight-v6"
9
- spec.version = Milight::V6::VERSION
10
- spec.authors = ["Peter Postma"]
11
- spec.email = ["peter.postma@gmail.com"]
12
-
13
- spec.summary = "Ruby API for the Milight Wifi Bridge v6"
14
- spec.description = "Ruby API for the Milight Wifi Bridge (or Wifi iBOX controller) version 6."
15
- spec.homepage = "https://github.com/ppostma/milight-v6-api"
16
- spec.license = "MIT"
17
-
18
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
- f.match(%r{^(test|spec|features)/})
20
- end
21
- spec.executables = ["milight"]
22
- spec.require_paths = ["lib"]
23
-
24
- spec.add_development_dependency "bundler", "~> 1.16"
25
- spec.add_development_dependency "rake", "~> 10.0"
26
- spec.add_development_dependency "rspec", "~> 3.0"
27
- end