hue-lib 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +9 -8
- data/hue-lib.gemspec +4 -3
- data/lib/hue/animations/candle.rb +54 -0
- data/lib/hue/animations/sunrise.rb +106 -0
- data/lib/hue/bridge.rb +23 -28
- data/lib/hue/bulb.rb +33 -332
- data/lib/hue/colors/color.rb +35 -0
- data/lib/hue/colors/color_temperature.rb +85 -0
- data/lib/hue/colors/hue_saturation.rb +104 -0
- data/lib/hue/colors/rgb.rb +102 -0
- data/lib/hue/colors/xy.rb +63 -0
- data/lib/hue/colors.rb +42 -0
- data/lib/hue.rb +29 -8
- data/spec/hue/bulb_spec.rb +25 -15
- data/spec/hue/colors/color_spec.rb +33 -0
- data/spec/hue/colors/color_temperature_spec.rb +106 -0
- data/spec/hue/colors/hue_saturation_spec.rb +91 -0
- data/spec/hue/colors/rgb_spec.rb +64 -0
- data/spec/hue/colors/xy_spec.rb +51 -0
- data/spec/hue/colors_spec.rb +92 -0
- data/spec/hue_spec.rb +8 -0
- metadata +20 -4
@@ -0,0 +1,35 @@
|
|
1
|
+
module Hue
|
2
|
+
module Colors
|
3
|
+
class Color
|
4
|
+
|
5
|
+
ERROR_METHOD_NOT_IMPLEMENTED = 'method-not-implemented'
|
6
|
+
|
7
|
+
def self.ranged(min, val, max)
|
8
|
+
[[min, val].max, max].min
|
9
|
+
end
|
10
|
+
|
11
|
+
public
|
12
|
+
|
13
|
+
def to_hash
|
14
|
+
raise ERROR_METHOD_NOT_IMPLEMENTED
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
raise ERROR_METHOD_NOT_IMPLEMENTED
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_rgb
|
22
|
+
raise ERROR_METHOD_NOT_IMPLEMENTED
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
|
28
|
+
def ranged(min, val, max)
|
29
|
+
# For convinence and polymorphism
|
30
|
+
self.class.ranged(min, val, max)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
module Hue
|
4
|
+
module Colors
|
5
|
+
class ColorTemperature < Color
|
6
|
+
|
7
|
+
MEGA = 1e6
|
8
|
+
KELVIN_MIN = 2000
|
9
|
+
KELVIN_MAX = 6500
|
10
|
+
MIRED_MIN = 153
|
11
|
+
MIRED_MAX = 500
|
12
|
+
|
13
|
+
public
|
14
|
+
|
15
|
+
def initialize(temperature)
|
16
|
+
if scale = Hue.percent_to_unit_interval(temperature)
|
17
|
+
self.mired = unit_to_mired_interval(scale)
|
18
|
+
else
|
19
|
+
# Assume an integer value
|
20
|
+
temperature = temperature.to_i
|
21
|
+
if temperature >= KELVIN_MIN
|
22
|
+
self.kelvin = temperature
|
23
|
+
else
|
24
|
+
self.mired = temperature
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def mired
|
30
|
+
@mired.floor
|
31
|
+
end
|
32
|
+
|
33
|
+
def mired=(t)
|
34
|
+
@mired = ranged(MIRED_MIN, t.to_f, MIRED_MAX)
|
35
|
+
end
|
36
|
+
|
37
|
+
def kelvin
|
38
|
+
ranged(KELVIN_MIN, MEGA / @mired, KELVIN_MAX).round
|
39
|
+
end
|
40
|
+
|
41
|
+
def kelvin=(t)
|
42
|
+
self.mired = (MEGA / ranged(KELVIN_MIN, t.to_f, KELVIN_MAX))
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_hash
|
46
|
+
{ct: mired}
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"Temperature=#{self.kelvin.to_i}°K (#{self.mired} mired)"
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_rgb
|
54
|
+
# using method described at
|
55
|
+
# http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
|
56
|
+
temp = kelvin / 100
|
57
|
+
|
58
|
+
red = temp <= 66 ? 255 : 329.698727446 * ((temp - 60) ** -0.1332047592)
|
59
|
+
|
60
|
+
green = if temp <= 66
|
61
|
+
99.4708025861 * Math.log(temp) - 161.1195681661
|
62
|
+
else
|
63
|
+
288.1221695283 * ((temp - 60) ** -0.0755148492)
|
64
|
+
end
|
65
|
+
|
66
|
+
blue = if temp >= 66
|
67
|
+
255
|
68
|
+
elsif temp <= 19
|
69
|
+
0
|
70
|
+
else
|
71
|
+
138.5177312231 * Math.log(temp - 10) - 305.0447927307
|
72
|
+
end
|
73
|
+
|
74
|
+
RGB.new(red, green, blue)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def unit_to_mired_interval(unit_interval)
|
80
|
+
unit_interval * (MIRED_MAX - MIRED_MIN) + MIRED_MIN
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Hue
|
2
|
+
module Colors
|
3
|
+
class HueSaturation < Color
|
4
|
+
|
5
|
+
HUE_MIN = 0
|
6
|
+
HUE_MAX = 65536.0
|
7
|
+
HUE_DEGREES = 360
|
8
|
+
HUE_SCALE = HUE_MAX / HUE_DEGREES
|
9
|
+
SATURATION_MIN = 0
|
10
|
+
SATURATION_MAX = 255
|
11
|
+
|
12
|
+
attr_reader :hue, :saturation
|
13
|
+
alias :sat :saturation
|
14
|
+
|
15
|
+
public
|
16
|
+
|
17
|
+
def initialize(hue, saturation)
|
18
|
+
self.hue = hue
|
19
|
+
self.saturation = saturation
|
20
|
+
end
|
21
|
+
|
22
|
+
def hue=(value)
|
23
|
+
if scale = Hue.percent_to_unit_interval(value)
|
24
|
+
@hue = unit_to_hue_interval(scale)
|
25
|
+
else
|
26
|
+
@hue = ranged(HUE_MIN, value.to_i, HUE_MAX)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def hue_in_degrees
|
31
|
+
self.hue.to_f / HUE_SCALE
|
32
|
+
end
|
33
|
+
|
34
|
+
def hue_in_unit_interval
|
35
|
+
hue_in_degrees / HUE_DEGREES
|
36
|
+
end
|
37
|
+
|
38
|
+
def saturation=(value)
|
39
|
+
if scale = Hue.percent_to_unit_interval(value)
|
40
|
+
@saturation = unit_to_saturation_interval(scale)
|
41
|
+
else
|
42
|
+
@saturation = ranged(SATURATION_MIN, value.to_i, SATURATION_MAX)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
alias :sat= :saturation=
|
46
|
+
|
47
|
+
def saturation_in_unit_interval
|
48
|
+
self.saturation / SATURATION_MAX.to_f
|
49
|
+
end
|
50
|
+
alias :sat_in_unit_interval :saturation_in_unit_interval
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
"Hue=#{self.hue}, Saturation=#{self.saturation}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_hash
|
57
|
+
{hue: hue, sat: saturation}
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_rgb(brightness_in_unit_interval = 1.0)
|
61
|
+
h, s, v = hue_in_unit_interval, saturation_in_unit_interval, brightness_in_unit_interval
|
62
|
+
if s == 0 #monochromatic
|
63
|
+
red = green = blue = v
|
64
|
+
else
|
65
|
+
|
66
|
+
v = 1.0 # We are setting the value to 1. Don't count brightness here
|
67
|
+
i = (h * 6).floor
|
68
|
+
f = h * 6 - i
|
69
|
+
p = v * (1 - s)
|
70
|
+
q = v * (1 - f * s)
|
71
|
+
t = v * (1 - (1 - f) * s)
|
72
|
+
|
73
|
+
case i % 6
|
74
|
+
when 0
|
75
|
+
red, green, blue = v, t, p
|
76
|
+
when 1
|
77
|
+
red, green, blue = q, v, p
|
78
|
+
when 2
|
79
|
+
red, green, blue = p, v, t
|
80
|
+
when 3
|
81
|
+
red, green, blue = p, q, v
|
82
|
+
when 4
|
83
|
+
red, green, blue = t, p, v
|
84
|
+
when 5
|
85
|
+
red, green, blue = v, p, q
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
RGB.new(red * RGB::MAX, green * RGB::MAX, blue * RGB::MAX)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def unit_to_hue_interval(value)
|
95
|
+
(value * (HUE_MAX - HUE_MIN) + HUE_MIN).round
|
96
|
+
end
|
97
|
+
|
98
|
+
def unit_to_saturation_interval(value)
|
99
|
+
(value * (SATURATION_MAX - SATURATION_MIN) + SATURATION_MIN).round
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
module Hue
|
4
|
+
module Colors
|
5
|
+
class RGB < Color
|
6
|
+
|
7
|
+
MIN = 0
|
8
|
+
MAX = 255
|
9
|
+
|
10
|
+
def self.ranged(value)
|
11
|
+
super(MIN, value, MAX)
|
12
|
+
end
|
13
|
+
|
14
|
+
public
|
15
|
+
|
16
|
+
attr_reader :red, :green, :blue
|
17
|
+
|
18
|
+
def initialize(*rgb)
|
19
|
+
red, green, blue = rgb
|
20
|
+
self.red = red
|
21
|
+
self.green = green
|
22
|
+
self.blue = blue
|
23
|
+
end
|
24
|
+
|
25
|
+
def red=(value)
|
26
|
+
@red = parse(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def green=(value)
|
30
|
+
@green = parse(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def blue=(value)
|
34
|
+
@blue = parse(value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
max = MAX.to_f
|
39
|
+
red, green, blue = self.red / max, self.green / max, self.red / max
|
40
|
+
|
41
|
+
max = [red, green, blue].max
|
42
|
+
min = [red, green, blue].min
|
43
|
+
h, s, l = 0, 0, ((max + min) / 2 * 255)
|
44
|
+
|
45
|
+
d = max - min
|
46
|
+
s = max == 0 ? 0 : (d / max * 255)
|
47
|
+
|
48
|
+
h = case max
|
49
|
+
when min
|
50
|
+
0 # monochromatic
|
51
|
+
when red
|
52
|
+
(green - blue) / d + (green < blue ? 6 : 0)
|
53
|
+
when green
|
54
|
+
(blue - red) / d + 2
|
55
|
+
when blue
|
56
|
+
(red - green) / d + 4
|
57
|
+
end * 60 # / 6 * 360
|
58
|
+
|
59
|
+
h = (h * HueSaturation::HUE_SCALE).to_i
|
60
|
+
{hue: h, sat: s.to_i, bri: Bulb::BRIGHTNESS_MAX}
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
"RGB≈#{rgb}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_rgb
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(rhs)
|
72
|
+
rhs.is_a?(RGB) &&
|
73
|
+
[:red, :green, :blue].all? { |m| self.send(m) == rhs.send(m) }
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def ranged(value)
|
79
|
+
self.class.ranged(value.to_i).round
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def rgb
|
85
|
+
[red, green, blue]
|
86
|
+
end
|
87
|
+
|
88
|
+
def unit_to_rgb_scale(value)
|
89
|
+
(value * (MAX - MIN) + MIN).round
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse(value)
|
93
|
+
if scale = Hue.percent_to_unit_interval(value)
|
94
|
+
unit_to_rgb_scale(scale)
|
95
|
+
else
|
96
|
+
ranged(value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
module Hue
|
3
|
+
module Colors
|
4
|
+
class XY < Color
|
5
|
+
|
6
|
+
MIN = 0.0
|
7
|
+
MAX = 1.0
|
8
|
+
RGB_MATRIX = Matrix[
|
9
|
+
[ 3.233358361244897, -1.5262682428425947, 0.27916711262124544],
|
10
|
+
[-0.8268442148395835, 2.466767560486707, 0.3323241608108406 ],
|
11
|
+
[ 0.12942207487871885, 0.19839858329512317, 2.0280912276039635 ],
|
12
|
+
]
|
13
|
+
|
14
|
+
public
|
15
|
+
|
16
|
+
attr_reader :x, :y
|
17
|
+
|
18
|
+
def initialize(*xy)
|
19
|
+
self.x = xy.first
|
20
|
+
self.y = xy.last
|
21
|
+
end
|
22
|
+
|
23
|
+
def x=(value)
|
24
|
+
@x = ranged(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def y=(value)
|
28
|
+
@y = ranged(value)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
{xy: xy}
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
"XY=#{xy}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_rgb
|
40
|
+
z = 1 - x - y
|
41
|
+
xyz = [x, y, z]
|
42
|
+
values = (RGB_MATRIX * Matrix[xyz].transpose).to_a.flatten.map do |x|
|
43
|
+
RGB.ranged(x * RGB::MAX).to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
RGB.new(*values)
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def ranged(val)
|
52
|
+
super(MIN, val.to_f, MAX)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def xy
|
58
|
+
[x,y]
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/hue/colors.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'colors/color'
|
2
|
+
require_relative 'colors/hue_saturation'
|
3
|
+
require_relative 'colors/color_temperature'
|
4
|
+
require_relative 'colors/xy'
|
5
|
+
require_relative 'colors/rgb'
|
6
|
+
|
7
|
+
module Hue
|
8
|
+
module Colors
|
9
|
+
|
10
|
+
def self.parse(*args)
|
11
|
+
case args.size
|
12
|
+
when 1
|
13
|
+
Colors::ColorTemperature.new(args.first)
|
14
|
+
when 2
|
15
|
+
a,b = args.first.to_f, args.last.to_f
|
16
|
+
if a > 1.0
|
17
|
+
Colors::HueSaturation.new(args.first, args.last)
|
18
|
+
else
|
19
|
+
Colors::XY.new(*args)
|
20
|
+
end
|
21
|
+
when 3
|
22
|
+
Colors::RGB.new(*args)
|
23
|
+
else
|
24
|
+
raise Error.new("Unable to parse to color: #{args.inspect}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.parse_state(state)
|
29
|
+
case state['colormode']
|
30
|
+
when 'ct'
|
31
|
+
Colors::ColorTemperature.new(state['ct'])
|
32
|
+
when 'xy'
|
33
|
+
Colors::XY.new(*state['xy'])
|
34
|
+
when 'hs'
|
35
|
+
Colors::HueSaturation.new(state['hue'], state['sat'])
|
36
|
+
else
|
37
|
+
raise Error.new("Unknown or missing state: #{state.inspect}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
data/lib/hue.rb
CHANGED
@@ -1,15 +1,8 @@
|
|
1
1
|
require 'net/http'
|
2
|
-
require 'json'
|
3
|
-
require 'matrix'
|
4
2
|
require 'digest/md5'
|
3
|
+
require 'json'
|
5
4
|
require 'uuid'
|
6
5
|
|
7
|
-
RGB_MATRIX = Matrix[
|
8
|
-
[3.233358361244897, -1.5262682428425947, 0.27916711262124544],
|
9
|
-
[-0.8268442148395835, 2.466767560486707, 0.3323241608108406],
|
10
|
-
[0.12942207487871885, 0.19839858329512317, 2.0280912276039635]
|
11
|
-
]
|
12
|
-
|
13
6
|
module Hue
|
14
7
|
|
15
8
|
DEVICE_TYPE = 'hue-lib'
|
@@ -131,10 +124,38 @@ ST: ssdp:all
|
|
131
124
|
end
|
132
125
|
end
|
133
126
|
|
127
|
+
def self.logger
|
128
|
+
if !defined?(@@logger)
|
129
|
+
log_dir_path = File.join('/var', 'log', 'hue')
|
130
|
+
begin
|
131
|
+
FileUtils.mkdir_p(log_dir_path)
|
132
|
+
rescue Errno::EACCES
|
133
|
+
log_dir_path = File.join(ENV['HOME'], ".#{device_type}")
|
134
|
+
FileUtils.mkdir_p(log_dir_path)
|
135
|
+
end
|
136
|
+
|
137
|
+
log_file_path = File.join(log_dir_path, 'hue-lib.log')
|
138
|
+
log_file = File.new(log_file_path, File::WRONLY | File::APPEND | File::CREAT)
|
139
|
+
@@logger = Logger.new(log_file)
|
140
|
+
@@logger.level = Logger::INFO
|
141
|
+
end
|
142
|
+
|
143
|
+
@@logger
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.percent_to_unit_interval(value)
|
147
|
+
if percent = /(\d+)%/.match(value.to_s)
|
148
|
+
percent.captures.first.to_i / 100.0
|
149
|
+
else
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
134
154
|
end
|
135
155
|
|
136
156
|
require 'hue/config/abstract'
|
137
157
|
require 'hue/config/application'
|
138
158
|
require 'hue/config/bridge'
|
139
159
|
require 'hue/bridge'
|
160
|
+
require 'hue/colors'
|
140
161
|
require 'hue/bulb'
|
data/spec/hue/bulb_spec.rb
CHANGED
@@ -33,21 +33,20 @@ describe Hue::Bulb do
|
|
33
33
|
bulb.off?.should be_true
|
34
34
|
end
|
35
35
|
|
36
|
-
it "should report the
|
37
|
-
bulb.hue.should == 13234
|
36
|
+
it "should report the brightness and color mode" do
|
38
37
|
bulb.brightness.should == 146
|
39
38
|
bulb.bri.should == 146
|
40
|
-
bulb.saturation.should == 208
|
41
|
-
bulb.sat.should == bulb.saturation
|
42
|
-
end
|
43
|
-
|
44
|
-
it "should report the color temperature and color mode" do
|
45
|
-
bulb.color_temperature.should == 459
|
46
|
-
bulb.ct.should == bulb.color_temperature
|
47
39
|
bulb.color_mode.should == 'ct'
|
48
40
|
bulb.color_mode.should == bulb.colormode
|
49
41
|
end
|
50
42
|
|
43
|
+
it "should report the color" do
|
44
|
+
color = bulb.color
|
45
|
+
color.should be_a(Hue::Colors::ColorTemperature)
|
46
|
+
color.mired.should == 459
|
47
|
+
color.kelvin.should == 2179
|
48
|
+
end
|
49
|
+
|
51
50
|
it "should report the alert state" do
|
52
51
|
bulb.blinking?.should be_false
|
53
52
|
bulb.solid?.should be_true
|
@@ -64,17 +63,28 @@ describe Hue::Bulb do
|
|
64
63
|
end
|
65
64
|
|
66
65
|
it 'should allow setting hue, saturation and brightness' do
|
67
|
-
|
68
|
-
bulb.hue = 120
|
69
|
-
bulb.hue.should == 21845
|
66
|
+
color = Hue::Colors::HueSaturation.new(21845, 1293)
|
70
67
|
|
71
|
-
with_fake_update('lights/1/state', sat:
|
72
|
-
bulb.
|
73
|
-
|
68
|
+
with_fake_update('lights/1/state', hue: 21845, sat: 255)
|
69
|
+
set_color = (bulb.color = color)
|
70
|
+
set_color.hue.should == 21845
|
71
|
+
set_color.saturation.should == 255
|
72
|
+
end
|
74
73
|
|
74
|
+
it 'should allow setting brightness as a number, percentage or string' do
|
75
75
|
with_fake_update('lights/1/state', bri: 233)
|
76
76
|
bulb.brightness = 233
|
77
77
|
bulb.brightness.should == 233
|
78
|
+
|
79
|
+
with_fake_update('lights/1/state', bri: 128)
|
80
|
+
bulb.brightness = "50%"
|
81
|
+
bulb.brightness.should == 128
|
82
|
+
bulb.brightness_in_unit_interval.should == 0.5019607843137255
|
83
|
+
bulb.brightness_percent.should == 50
|
84
|
+
|
85
|
+
with_fake_update('lights/1/state', bri: 128)
|
86
|
+
bulb.brightness = "128"
|
87
|
+
bulb.brightness.should == 128
|
78
88
|
end
|
79
89
|
|
80
90
|
it 'should allow setting blink, solid and flash alerts' do
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe Hue::Colors::Color do
|
4
|
+
|
5
|
+
context 'implements a ranged method: max > value > min' do
|
6
|
+
it 'should return the min when value < min' do
|
7
|
+
described_class.ranged(0, -1, 2).should == 0
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should return the max when value > max' do
|
11
|
+
described_class.ranged(0, 3, 2).should == 2
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should return the value when max > value > min' do
|
15
|
+
described_class.ranged(0, 1, 2).should == 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def abstract_method(method)
|
20
|
+
abstract_color = described_class.new
|
21
|
+
abstract_color.should respond_to(method)
|
22
|
+
lambda do
|
23
|
+
abstract_color.send(method)
|
24
|
+
end.should raise_error(described_class::ERROR_METHOD_NOT_IMPLEMENTED)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'defines but does not implement methods:' do
|
28
|
+
it('#to_hash') { abstract_method(:to_hash) }
|
29
|
+
it('#to_s') { abstract_method(:to_s) }
|
30
|
+
it('#to_rgb') { abstract_method(:to_rgb) }
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
require 'spec_helper.rb'
|
3
|
+
|
4
|
+
describe Hue::Colors::ColorTemperature do
|
5
|
+
|
6
|
+
context 'when initialized with a valid value' do
|
7
|
+
color = described_class.new(500)
|
8
|
+
|
9
|
+
it 'should report the temperature in mireds and kelvins' do
|
10
|
+
color.mired.should == 500
|
11
|
+
color.kelvin.should == 2000
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should have a string representation' do
|
15
|
+
color.to_s.should == "Temperature=2000°K (500 mired)"
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should have a hash representation' do
|
19
|
+
color.to_hash.should == {ct: 500}
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should have an RGB representation' do
|
23
|
+
color.to_rgb.should == Hue::Colors::RGB.new(255,136,13)
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when allowing change to the temperature value' do
|
27
|
+
it 'should go to the max in kelvins' do
|
28
|
+
color.kelvin = 7000
|
29
|
+
color.kelvin.should == described_class::KELVIN_MAX
|
30
|
+
color.mired.should == described_class::MIRED_MIN
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should go to the max in mireds' do
|
34
|
+
color.mired = 600
|
35
|
+
color.mired.should == described_class::MIRED_MAX
|
36
|
+
color.kelvin.should == described_class::KELVIN_MIN
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should go to the min in mireds' do
|
40
|
+
color.mired = 100
|
41
|
+
color.mired.should == described_class::MIRED_MIN
|
42
|
+
color.kelvin.should == described_class::KELVIN_MAX
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should go to the min in kelvins' do
|
46
|
+
color.mired = 2000
|
47
|
+
color.mired.should == described_class::MIRED_MAX
|
48
|
+
color.kelvin.should == described_class::KELVIN_MIN
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should hit the middle' do
|
52
|
+
mired_middle = described_class::MIRED_MAX/2
|
53
|
+
color.mired = mired_middle
|
54
|
+
color.mired.should == mired_middle
|
55
|
+
color.kelvin.should == 4000
|
56
|
+
|
57
|
+
kelvin_middle = described_class::KELVIN_MAX/2
|
58
|
+
color.kelvin = kelvin_middle
|
59
|
+
color.kelvin.should == kelvin_middle
|
60
|
+
color.mired.should == 307
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when initializing' do
|
67
|
+
it 'should set the temperature depending on the value' do
|
68
|
+
color = described_class.new(100)
|
69
|
+
color.mired.should == described_class::MIRED_MIN
|
70
|
+
|
71
|
+
color = described_class.new(600)
|
72
|
+
color.mired.should == described_class::MIRED_MAX
|
73
|
+
|
74
|
+
color = described_class.new(described_class::KELVIN_MIN)
|
75
|
+
color.kelvin.should == described_class::KELVIN_MIN
|
76
|
+
|
77
|
+
color = described_class.new(described_class::KELVIN_MAX + 3000)
|
78
|
+
color.kelvin.should == described_class::KELVIN_MAX
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should report kelvins as an integer' do
|
82
|
+
color = described_class.new(459)
|
83
|
+
color.kelvin.should == 2179
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should accept a string value for the temperature' do
|
87
|
+
color = described_class.new("100")
|
88
|
+
color.mired.should == described_class::MIRED_MIN
|
89
|
+
|
90
|
+
color = described_class.new((described_class::KELVIN_MAX + 3000).to_s)
|
91
|
+
color.kelvin.should == described_class::KELVIN_MAX
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should allow a percentage value for the mired scale' do
|
95
|
+
color = described_class.new("0%")
|
96
|
+
color.mired.should == described_class::MIRED_MIN
|
97
|
+
|
98
|
+
color = described_class.new("50%")
|
99
|
+
color.mired.should == 326
|
100
|
+
|
101
|
+
color = described_class.new("100%")
|
102
|
+
color.mired.should == described_class::MIRED_MAX
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|