usblamp 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/MANIFEST ADDED
@@ -0,0 +1,24 @@
1
+ .gitignore
2
+ .rubocop.yml
3
+ .travis.yml
4
+ Gemfile
5
+ LICENSE.md
6
+ MANIFEST
7
+ README.md
8
+ Rakefile
9
+ bin/usblamp
10
+ lib/usblamp.rb
11
+ lib/usblamp/cli.rb
12
+ lib/usblamp/cli/blink.rb
13
+ lib/usblamp/cli/fadein.rb
14
+ lib/usblamp/color.rb
15
+ lib/usblamp/controller.rb
16
+ lib/usblamp/version.rb
17
+ spec/spec_helper.rb
18
+ spec/usblamp/cli_spec.rb
19
+ spec/usblamp/color_spec.rb
20
+ spec/usblamp/controller_spec.rb
21
+ task/manifest.rake
22
+ task/rspec.rake
23
+ task/rubocop.rake
24
+ usblamp.gemspec
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ #WebMail Notifier with Ruby (Dream Cheeky)
2
+
3
+ [![Build Status](https://travis-ci.org/PierreRambaud/usblamp.png?branch=master)](https://travis-ci.org/PierreRambaud/usblamp)
4
+
5
+ Ruby script to power the Dreamcheeky USB webmail notifier gadget. <http://www.dreamcheeky.com/webmail-notifier>
6
+
7
+ ##Requirements
8
+
9
+ - Ruby 1.9.2 or newer
10
+
11
+ ##Installation
12
+
13
+ Assuming RubyGems isn't down you can install the Gem as following:
14
+
15
+ ```
16
+ $ gem install usblamp
17
+ ```
18
+
19
+ ##Usage
20
+
21
+ ```
22
+ Usage: usblamp [COMMAND] [OPTIONS]
23
+
24
+ Options:
25
+
26
+ -v, --version Shows the current version
27
+ -r, --red Red
28
+ -g, --green Green
29
+ -b, --blue Blue
30
+ -c, --color Color
31
+ -h, --help Display this help message.
32
+
33
+ Available commands:
34
+
35
+ fadein Fade in effect
36
+ blink Blink effect
37
+ ```
38
+
39
+ ##Troubleshooting
40
+
41
+ Should be run as root unless the necessary udev rules are set.
42
+ Create the file `/etc/udev/rules.d/42-usblamp.rules`
43
+ And add this content by replacing `got` by your username:
44
+ ```
45
+ SUBSYSTEM !="usb_device", ACTION !="add", GOTO="datalogger_rules_end"
46
+ SYSFS{idVendor} =="1d34", SYSFS{idProduct} =="0004", SYMLINK+="datalogger"
47
+ MODE="0666", OWNER="got", GROUP="root"
48
+ LABEL="datalogger_rules_end"
49
+ ```
50
+
51
+ ## Running tests
52
+
53
+ Install dependencies:
54
+
55
+ `$ bundle install`
56
+
57
+ To run tests:
58
+ `$ bundle exec rake`
59
+
60
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'bundler/gem_tasks'
3
+
4
+ GEMSPEC = Gem::Specification.load('usblamp.gemspec')
5
+
6
+ Dir['./task/*.rake'].each do |task|
7
+ import(task)
8
+ end
9
+
10
+ task default: [:rubocop, :spec]
data/bin/usblamp ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require File.expand_path('../../lib/usblamp', __FILE__)
5
+ Usblamp::CLI.slop.parse
@@ -0,0 +1,20 @@
1
+ # -*- coding: utf-8 -*-
2
+ Usblamp::CLI.slop.command 'blink' do
3
+ banner 'Usage: usblamp blink [OPTIONS]'
4
+ description 'Blink effect'
5
+ separator "\nOptions:\n"
6
+
7
+ Usblamp::CLI.slop.options.each do |c|
8
+ next if c.long == 'version'
9
+ options << c
10
+ end
11
+
12
+ on :t=, :times=, 'Times', as: Integer, default: 2
13
+
14
+ run do |opts, _args|
15
+ lamp = Usblamp::Controller.new
16
+ lamp.open
17
+ lamp.switch_off
18
+ lamp.blink(opts[:t], lamp.parse(opts))
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # -*- coding: utf-8 -*-
2
+ Usblamp::CLI.slop.command 'fadein' do
3
+ banner 'Usage: usblamp fadein [OPTIONS]'
4
+ description 'Fade in effect'
5
+ separator "\nOptions:\n"
6
+
7
+ Usblamp::CLI.slop.options.each do |c|
8
+ next if c.long == 'version'
9
+ options << c
10
+ end
11
+
12
+ on :d=, :delay=, 'Delay', as: Integer, default: 2
13
+
14
+ run do |opts, _args|
15
+ lamp = Usblamp::Controller.new
16
+ lamp.open
17
+ lamp.switch_off
18
+ lamp.fade_in(opts[:d], lamp.parse(opts))
19
+ end
20
+ end
@@ -0,0 +1,57 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Usblamp
3
+ # CLI mode
4
+ module CLI
5
+ ##
6
+ # Hash containing the default Slop options.
7
+ #
8
+ # @return [Hash]
9
+ #
10
+ SLOP_OPTIONS = {
11
+ strict: true,
12
+ help: true,
13
+ banner: 'Usage: usblamp [COMMAND] [OPTIONS]'
14
+ }
15
+
16
+ ##
17
+ # @return [Slop]
18
+ #
19
+ def self.slop
20
+ @slop ||= default_slop
21
+ end
22
+
23
+ ##
24
+ # @return [Slop]
25
+ #
26
+ def self.default_slop
27
+ Slop.new(SLOP_OPTIONS.dup) do
28
+ separator "\nOptions:\n"
29
+
30
+ on :v, :version, 'Shows the current version' do
31
+ puts CLI.version_information
32
+ end
33
+
34
+ on :r=, :red=, 'Red', as: Integer
35
+ on :g=, :green=, 'Green', as: Integer
36
+ on :b=, :blue=, 'Blue', as: Integer
37
+ on :c=, :color=, 'Color'
38
+
39
+ run do |opts, _args|
40
+ lamp = Controller.new
41
+ lamp.open
42
+ lamp.switch_off
43
+ lamp.send_color(lamp.parse(opts))
44
+ end
45
+ end
46
+ end
47
+
48
+ ##
49
+ # Returns a String containing some platform/version related information.
50
+ #
51
+ # @return [String]
52
+ #
53
+ def self.version_information
54
+ "usblamp v#{VERSION} on #{RUBY_DESCRIPTION}"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Usblamp module
3
+ module Usblamp
4
+ # Color class
5
+ class Color
6
+ attr_accessor :red, :green, :blue
7
+ MAX_VALUE = 0x40
8
+
9
+ def initialize(red = nil, green = nil, blue = nil)
10
+ set(red, green, blue)
11
+ end
12
+
13
+ def set(red, green = nil, blue = nil)
14
+ if red.is_a?(String) && green.nil? && blue.nil?
15
+ if ['#', '_'].include?(red[0])
16
+ return define_from_hex(red)
17
+ else
18
+ return define_from_string(red)
19
+ end
20
+ end
21
+
22
+ red = 0 if red.nil?
23
+ green = 0 if green.nil?
24
+ blue = 0 if blue.nil?
25
+
26
+ @red = define_value(red)
27
+ @green = define_value(green)
28
+ @blue = define_value(blue)
29
+ end
30
+
31
+ private
32
+
33
+ def define_value(integer)
34
+ [[0, integer.to_i].max, MAX_VALUE].min
35
+ end
36
+
37
+ def define_from_hex(string)
38
+ string = string.gsub(/[#_]/, '')
39
+ fail ArgumentError, 'Wrong length for hexadecimal string' unless
40
+ [3, 6].include?(string.length.to_i)
41
+
42
+ return set(string[0..1].to_i(16),
43
+ string[2..3].to_i(16),
44
+ string[4..5].to_i(16)
45
+ ) if string.length == 6
46
+
47
+ set((string[0] * 2).to_i(16),
48
+ (string[1] * 2).to_i(16),
49
+ (string[2] * 2).to_i(16)
50
+ )
51
+ end
52
+
53
+ def define_from_string(string)
54
+ return set(MAX_VALUE, 0, 0) if string == 'red'
55
+ return set(0, MAX_VALUE, 0) if string == 'green'
56
+ return set(0, 0, MAX_VALUE) if string == 'blue'
57
+ return set(MAX_VALUE, MAX_VALUE, MAX_VALUE) if string == 'white'
58
+ return set(MAX_VALUE, 0, MAX_VALUE) if string == 'magenta'
59
+ return set(0, MAX_VALUE, MAX_VALUE) if string == 'cyan'
60
+ return set(MAX_VALUE, MAX_VALUE, 0) if string == 'yellow'
61
+ return set(MAX_VALUE / 2, MAX_VALUE / 2, MAX_VALUE / 2) if
62
+ string == 'purple'
63
+
64
+ set(0, 0, 0)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,110 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Usblamp module
3
+ module Usblamp
4
+ # Controller class
5
+ class Controller
6
+ attr_accessor :device, :color
7
+
8
+ VENDOR_ID = 0x1d34
9
+ PRODUCT_ID = 0x0004
10
+ INIT_PACKET1 = [0x1F, 0x01, 0x29, 0x00, 0xB8, 0x54, 0x2C, 0x03]
11
+ INIT_PACKET2 = [0x00, 0x01, 0x29, 0x00, 0xB8, 0x54, 0x2C, 0x04]
12
+
13
+ def initialize
14
+ @device = nil
15
+ @color = nil
16
+ end
17
+
18
+ def open
19
+ usb = LIBUSB::Context.new
20
+ device = usb.devices(idVendor: VENDOR_ID, idProduct: PRODUCT_ID).first
21
+
22
+ fail 'Could no find a supported device.' if device.nil?
23
+ @device = device.open
24
+ @device.detach_kernel_driver(0) if @device.kernel_driver_active?(0)
25
+ @device.claim_interface(0)
26
+ send(INIT_PACKET1)
27
+ send(INIT_PACKET2)
28
+ end
29
+
30
+ def send(data)
31
+ return true if @device
32
+ .control_transfer(
33
+ bmRequestType: LIBUSB::REQUEST_TYPE_CLASS |
34
+ LIBUSB::RECIPIENT_INTERFACE,
35
+ bRequest: LIBUSB::REQUEST_SET_CONFIGURATION,
36
+ wValue: 0x81,
37
+ wIndex: LIBUSB::ENDPOINT_OUT,
38
+ dataOut: data.map(&:chr).reduce(:+),
39
+ timeout: 10) == 8
40
+
41
+ false
42
+ end
43
+
44
+ def send_color(color)
45
+ fail TypeError, 'Parameter must be an instance of Color' unless
46
+ color.is_a?(Color)
47
+
48
+ @color = color
49
+ data = [color.red,
50
+ color.green,
51
+ color.blue,
52
+ 0x00,
53
+ 0x00,
54
+ 0x00,
55
+ 0x00,
56
+ 0x05]
57
+ send(data)
58
+ end
59
+
60
+ def switch_off
61
+ send_color(Color.new('black'))
62
+ end
63
+
64
+ def parse(args)
65
+ color = Color.new(args[:c]) unless args[:c].nil?
66
+ color = Color.new(args[:r], args[:g], args[:b]) if args[:c].nil?
67
+ color
68
+ end
69
+
70
+ def fade_in(delay, new_color)
71
+ delay = delay.to_i
72
+ color = Color.new
73
+ max_value = [new_color.red, new_color.green, new_color.blue].max
74
+ (0..max_value).each do |i|
75
+ sleep((delay.to_f * 1000.to_f / max_value.to_f + 1.to_f) / 1000.to_f)
76
+ color.red = transition(i,
77
+ @color.red,
78
+ new_color.red,
79
+ max_value)
80
+ color.green = transition(i,
81
+ @color.green,
82
+ new_color.green,
83
+ max_value)
84
+ color.blue = transition(i,
85
+ @color.blue,
86
+ new_color.blue,
87
+ max_value)
88
+ send_color(color)
89
+ end
90
+ end
91
+
92
+ def blink(times, new_color)
93
+ (1..times).each do
94
+ send_color(new_color)
95
+ sleep 0.5
96
+ switch_off
97
+ sleep 0.5
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def transition(index, start_point, end_point, maximum)
104
+ (((start_point.to_f + (end_point.to_f - start_point.to_f)) *
105
+ (index.to_f + 1.to_f)
106
+ ) / maximum.to_f
107
+ ).to_i
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Gemirro Version
3
+ module Usblamp
4
+ VERSION = '0.1.1'
5
+ end
data/lib/usblamp.rb ADDED
@@ -0,0 +1,15 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'slop'
3
+ require 'libusb'
4
+
5
+ unless $LOAD_PATH.include?(File.expand_path('../', __FILE__))
6
+ $LOAD_PATH.unshift(File.expand_path('../', __FILE__))
7
+ end
8
+
9
+ require 'usblamp/color'
10
+ require 'usblamp/controller'
11
+ require 'usblamp/version'
12
+
13
+ require 'usblamp/cli'
14
+ require 'usblamp/cli/fadein'
15
+ require 'usblamp/cli/blink'
@@ -0,0 +1,8 @@
1
+ # -*- coding: utf-8 -*-
2
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
3
+
4
+ require 'simplecov'
5
+
6
+ SimpleCov.start do
7
+ add_filter '/spec/'
8
+ end
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'usblamp/cli'
4
+ require 'slop'
5
+
6
+ # Usblamp tests
7
+ module Usblamp
8
+ # CLI tests
9
+ module CLI
10
+ describe 'CLI' do
11
+ it 'should return options' do
12
+ options = CLI.slop
13
+ expect(options).to be_a(::Slop)
14
+ expect(options.config[:strict]).to be_truthy
15
+ expect(options.config[:banner])
16
+ .to eq('Usage: usblamp [COMMAND] [OPTIONS]')
17
+ expect(options.to_s)
18
+ .to match(/-v, --version(\s+)Shows the current version/)
19
+ expect(options.to_s)
20
+ .to match(/-h, --help(\s+)Display this help message./)
21
+
22
+ version = options.fetch_option(:v)
23
+ expect(version.short).to eq('v')
24
+ expect(version.long).to eq('version')
25
+ expect { version.call }.to output(/usblamp v.* on ruby/).to_stdout
26
+ end
27
+
28
+ it 'should retrieve version information' do
29
+ expect(CLI.version_information).to eq(
30
+ "usblamp v#{VERSION} on #{RUBY_DESCRIPTION}"
31
+ )
32
+ end
33
+
34
+ it 'should change color' do
35
+ controller = double(open: true,
36
+ switch_off: true,
37
+ parse: { c: 'blue' },
38
+ send_color: true)
39
+ allow(Controller).to receive(:new).and_return(controller)
40
+ expect(CLI.slop.parse(c: 'blue')).to eq(c: 'blue')
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,90 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'usblamp/color'
4
+
5
+ # Usblamp module
6
+ module Usblamp
7
+ describe 'Color' do
8
+ before(:each) do
9
+ @color = Color.new
10
+ end
11
+
12
+ it 'should test initialize with color' do
13
+ color = Color.new(10, 11, 12)
14
+ expect(color.red).to eq(10)
15
+ expect(color.green).to eq(11)
16
+ expect(color.blue).to eq(12)
17
+ end
18
+
19
+ it 'should test const' do
20
+ expect(Color::MAX_VALUE).to eq(0x40)
21
+ end
22
+
23
+ it 'should set' do
24
+ @color.set(10, 11, 12)
25
+ expect(@color.red).to eq(10)
26
+ expect(@color.green).to eq(11)
27
+ expect(@color.blue).to eq(12)
28
+ end
29
+
30
+ it 'should set from hex' do
31
+ @color.set('#112233')
32
+ expect(@color.red).to eq(17)
33
+ expect(@color.green).to eq(34)
34
+ expect(@color.blue).to eq(51)
35
+ @color.set('_112233')
36
+ expect(@color.red).to eq(17)
37
+ expect(@color.green).to eq(34)
38
+ expect(@color.blue).to eq(51)
39
+ @color.set('#123')
40
+ expect(@color.red).to eq(17)
41
+ expect(@color.green).to eq(34)
42
+ expect(@color.blue).to eq(51)
43
+ end
44
+
45
+ it 'should set from string' do
46
+ @color.set('red')
47
+ expect(@color.red).to eq(64)
48
+ expect(@color.green).to eq(0)
49
+ expect(@color.blue).to eq(0)
50
+ @color.set('green')
51
+ expect(@color.red).to eq(0)
52
+ expect(@color.green).to eq(64)
53
+ expect(@color.blue).to eq(0)
54
+ @color.set('blue')
55
+ expect(@color.red).to eq(0)
56
+ expect(@color.green).to eq(0)
57
+ expect(@color.blue).to eq(64)
58
+ @color.set('white')
59
+ expect(@color.red).to eq(64)
60
+ expect(@color.green).to eq(64)
61
+ expect(@color.blue).to eq(64)
62
+ @color.set('magenta')
63
+ expect(@color.red).to eq(64)
64
+ expect(@color.green).to eq(0)
65
+ expect(@color.blue).to eq(64)
66
+ @color.set('cyan')
67
+ expect(@color.red).to eq(0)
68
+ expect(@color.green).to eq(64)
69
+ expect(@color.blue).to eq(64)
70
+ @color.set('yellow')
71
+ expect(@color.red).to eq(64)
72
+ expect(@color.green).to eq(64)
73
+ expect(@color.blue).to eq(0)
74
+ @color.set('purple')
75
+ expect(@color.red).to eq(32)
76
+ expect(@color.green).to eq(32)
77
+ expect(@color.blue).to eq(32)
78
+ @color.set('nothing')
79
+ expect(@color.red).to eq(0)
80
+ expect(@color.green).to eq(0)
81
+ expect(@color.blue).to eq(0)
82
+ end
83
+
84
+ it 'should raise error when undefined color' do
85
+ expect { @color.set('#FF') }.to raise_error ArgumentError
86
+ expect { @color.set('#FFFF') }.to raise_error ArgumentError
87
+ expect { @color.set('#FFFFF') }.to raise_error ArgumentError
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,152 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'libusb'
4
+ require 'usblamp/controller'
5
+
6
+ # Usblamp module
7
+ module Usblamp
8
+ describe 'Controller' do
9
+ before(:each) do
10
+ @controller = Controller.new
11
+ end
12
+
13
+ it 'should test init' do
14
+ expect(@controller.device).to be(nil)
15
+ expect(@controller.color).to be(nil)
16
+ end
17
+
18
+ it 'should test const' do
19
+ expect(Controller::VENDOR_ID).to eq(0x1d34)
20
+ expect(Controller::PRODUCT_ID).to eq(0x0004)
21
+ expect(Controller::INIT_PACKET1)
22
+ .to eq([31, 1, 41, 0, 184, 84, 44, 3])
23
+ expect(Controller::INIT_PACKET2)
24
+ .to eq([0, 1, 41, 0, 184, 84, 44, 4])
25
+ expect(@controller.device).to eq(nil)
26
+ end
27
+
28
+ it 'should raise when no supported devices found' do
29
+ device = double(devices: [])
30
+ allow(LIBUSB::Context).to receive(:new).and_return(device)
31
+ expect { @controller.open }.to raise_error(RuntimeError)
32
+ end
33
+
34
+ it 'should open device' do
35
+ handle = spy(open: true,
36
+ kernel_driver_active?: true,
37
+ detach_kernel_driver: true,
38
+ control_transfer: 8)
39
+ device = double(devices: [double(open: handle)])
40
+ allow(LIBUSB::Context).to receive(:new).and_return(device)
41
+ expect(@controller.open).to be_truthy
42
+ expect(handle).to have_received(:claim_interface).with(0)
43
+ expect(handle).to have_received(:control_transfer)
44
+ .with(bmRequestType: 33,
45
+ bRequest: 9,
46
+ wValue: 129,
47
+ wIndex: 0,
48
+ dataOut: [31, 1, 41, 0, 184, 84, 44, 3].map(&:chr).reduce(:+),
49
+ timeout: 10)
50
+ expect(handle).to have_received(:control_transfer)
51
+ .with(bmRequestType: 33,
52
+ bRequest: 9,
53
+ wValue: 129,
54
+ wIndex: 0,
55
+ dataOut: [0, 1, 41, 0, 184, 84, 44, 4].map(&:chr).reduce(:+),
56
+ timeout: 10)
57
+ end
58
+
59
+ it 'should fail send transfer' do
60
+ device = double(control_transfer: 5)
61
+ @controller.device = device
62
+ expect(@controller.send([0])).to be_falsy
63
+ end
64
+
65
+ it 'should test send_color parameter' do
66
+ expect { @controller.send_color('fake') }
67
+ .to raise_error(TypeError, 'Parameter must be an instance of Color')
68
+ end
69
+
70
+ it 'should send color' do
71
+ device = spy(control_transfer: 8)
72
+ @controller.device = device
73
+ expect(@controller.send_color(Color.new('blue'))).to be_truthy
74
+ expect(device).to have_received(:control_transfer)
75
+ .with(bmRequestType: 33,
76
+ bRequest: 9,
77
+ wValue: 129,
78
+ wIndex: 0,
79
+ dataOut: [0, 0, 64, 0, 0, 0, 0, 5].map(&:chr).reduce(:+),
80
+ timeout: 10)
81
+ end
82
+
83
+ it 'should switch off' do
84
+ device = spy(control_transfer: 8)
85
+ @controller.device = device
86
+ expect(@controller.switch_off).to be_truthy
87
+ expect(device).to have_received(:control_transfer)
88
+ .with(bmRequestType: 33,
89
+ bRequest: 9,
90
+ wValue: 129,
91
+ wIndex: 0,
92
+ dataOut: "\x00\x00\x00\x00\x00\x00\x00\x05",
93
+ timeout: 10)
94
+ end
95
+
96
+ it 'should parse args to create color' do
97
+ args = { c: 'red' }
98
+ result = @controller.parse(args)
99
+ expect(result).to be_a(Color)
100
+ expect(result.red).to eq(64)
101
+ expect(result.green).to eq(0)
102
+ expect(result.blue).to eq(0)
103
+
104
+ args = { r: 10, g: 10, b: 10 }
105
+ result = @controller.parse(args)
106
+ expect(result).to be_a(Color)
107
+ expect(result.red).to eq(10)
108
+ expect(result.green).to eq(10)
109
+ expect(result.blue).to eq(10)
110
+ end
111
+
112
+ it 'should fade in' do
113
+ device = spy(control_transfer: 8)
114
+ @controller.color = Color.new('black')
115
+ @controller.device = device
116
+ allow(@controller).to receive(:sleep).and_return(true)
117
+ expect(@controller.fade_in(2, Color.new('blue'))).to be_truthy
118
+ (1..64).each do |i|
119
+ expect(device).to have_received(:control_transfer)
120
+ .with(bmRequestType: 33,
121
+ bRequest: 9,
122
+ wValue: 129,
123
+ wIndex: 0,
124
+ dataOut: [0, 0, i, 0, 0, 0, 0, 5].map(&:chr).reduce(:+),
125
+ timeout: 10)
126
+ end
127
+ end
128
+
129
+ it 'should blink' do
130
+ device = spy(control_transfer: 8)
131
+ @controller.device = device
132
+ allow(@controller).to receive(:sleep).and_return(true)
133
+ expect(@controller.blink(2, Color.new('blue'))).to be_truthy
134
+ (1..2).each do
135
+ expect(device).to have_received(:control_transfer).twice
136
+ .with(bmRequestType: 33,
137
+ bRequest: 9,
138
+ wValue: 129,
139
+ wIndex: 0,
140
+ dataOut: [0, 0, 64, 0, 0, 0, 0, 5].map(&:chr).reduce(:+),
141
+ timeout: 10)
142
+ expect(device).to have_received(:control_transfer).twice
143
+ .with(bmRequestType: 33,
144
+ bRequest: 9,
145
+ wValue: 129,
146
+ wIndex: 0,
147
+ dataOut: [0, 0, 0, 0, 0, 0, 0, 5].map(&:chr).reduce(:+),
148
+ timeout: 10)
149
+ end
150
+ end
151
+ end
152
+ end