brewby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewby::HeatingElement do
4
+ def millis_from_now i
5
+ (Time.now.to_i + i) * 1000
6
+ end
7
+
8
+ before do
9
+ adapter = Brewby::Outputs::Test.new
10
+ @element = Brewby::HeatingElement.new(adapter, pulse_range: 5000)
11
+ @element.pulse_width = 3000
12
+ end
13
+
14
+ describe 'relay pulsing' do
15
+ it 'turn on the relay when first pulsed' do
16
+ @element.should be_off
17
+ @element.pulse
18
+ @element.should be_on
19
+ end
20
+
21
+ it 'turns on the relay while within pulse width' do
22
+ @element.should be_off
23
+ @element.instance_variable_set(:@pulse_range_end, millis_from_now(4))
24
+ @element.pulse
25
+ @element.should be_on
26
+ end
27
+
28
+ it 'turns off the relay when time exceeds pulse width' do
29
+ @element.instance_variable_set(:@pulse_range_end, millis_from_now(5))
30
+ @element.pulse
31
+ @element.should be_on
32
+ @element.instance_variable_set(:@pulse_range_end, millis_from_now(1))
33
+ @element.pulse
34
+ @element.should be_off
35
+ end
36
+
37
+ it 'turns on the relay when time hits the next pulse range' do
38
+ @element.should be_off
39
+ @element.instance_variable_set(:@pulse_range_end, millis_from_now(-1))
40
+ @element.pulse
41
+ @element.should be_on
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewby::Inputs::DS18B20 do
4
+ it 'accepts a hardware_id' do
5
+ sensor = Brewby::Inputs::DS18B20.new hardware_id: 'something'
6
+ sensor.hardware_id.should == 'something'
7
+ end
8
+
9
+ it 'picks the first hardware_id it finds when no hardware_id is specified' do
10
+ Dir.mktmpdir do |dir|
11
+ File.write "#{dir}/28-12345", "1"
12
+ sensor = Brewby::Inputs::DS18B20.new device_path: dir
13
+ sensor.hardware_id.should == '28-12345'
14
+ end
15
+ end
16
+
17
+ context 'reading sensor data' do
18
+ before do
19
+ Brewby::Inputs::DS18B20.any_instance.stub(:read_raw) { "f5 00 4b 46 7f ff 0b 10 d7 : crc=d7 YES\nf5 00 4b 46 7f ff 0b 10 d7 t=15312" }
20
+ @sensor = Brewby::Inputs::DS18B20.new device_path: @device_dir, hardware_id: '28-12345'
21
+ end
22
+
23
+ it 'reads the temperaturee and returns it in fahrenheit' do
24
+ input = @sensor.read
25
+ input.should == 59.562
26
+ end
27
+
28
+ it 'parses the raw data to celsius' do
29
+ tempC = @sensor.parse @sensor.read_raw
30
+ tempC.should == 15.312
31
+ end
32
+
33
+ it 'returns nil when an error occurs when parsing' do
34
+ Brewby::Inputs::DS18B20.any_instance.stub(:read_raw) { "f5 00 4b 46 7f ff 0b 10 d7 : crc=d7 NO\nf5 00 4b 46 7f ff 0b 10 d7" }
35
+ input = @sensor.read
36
+ input.should be_nil
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewby::Inputs do
4
+ it 'determines input class based on adapter' do
5
+ Brewby::Inputs.adapter_class(:test).should == Brewby::Inputs::Test
6
+ Brewby::Inputs.adapter_class(:raspberry_pi).should == Brewby::Inputs::DS18B20
7
+ end
8
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewby::Outputs::GPIO do
4
+ before do
5
+ @gpio_dir = Dir.mktmpdir
6
+ Dir.mkdir "#{@gpio_dir}/gpio1"
7
+ File.write "#{@gpio_dir}/export", ""
8
+ File.write "#{@gpio_dir}/gpio1/direction", ""
9
+ end
10
+
11
+ after do
12
+ FileUtils.remove_entry @gpio_dir
13
+ end
14
+
15
+ context 'GPIO initialization' do
16
+ it 'initializes the GPIO pin via export' do
17
+ Brewby::Outputs::GPIO.new pin: 1, gpio_path: @gpio_dir
18
+ data = File.read "#{@gpio_dir}/export"
19
+ data.should == '1'
20
+ end
21
+
22
+ it 'initializes the GPIO pin direction' do
23
+ Brewby::Outputs::GPIO.new pin: 1, gpio_path: @gpio_dir
24
+ data = File.read "#{@gpio_dir}/gpio1/direction"
25
+ data.should == 'out'
26
+ end
27
+
28
+ it 'does not initialize the GPIO pin if already initialized' do
29
+ File.write "#{@gpio_dir}/gpio1/value", ""
30
+ Brewby::Outputs::GPIO.new pin: 1, gpio_path: @gpio_dir
31
+ data = File.read "#{@gpio_dir}/export"
32
+ data.should == ''
33
+ end
34
+ end
35
+
36
+ context 'output' do
37
+ before do
38
+ @output = Brewby::Outputs::GPIO.new pin: 1, gpio_path: @gpio_dir
39
+ File.write "#{@gpio_dir}/gpio1/value", ""
40
+ end
41
+
42
+ def pin_value
43
+ File.read "#{@gpio_dir}/gpio1/value"
44
+ end
45
+
46
+ it 'sets the pin value to 1 when on' do
47
+ @output.on
48
+ pin_value.should == '1'
49
+ end
50
+
51
+ it 'sets the pin value to 0 when off' do
52
+ @output.off
53
+ pin_value.should == '0'
54
+ end
55
+
56
+ it 'returns false when off' do
57
+ @output.should_not be_on
58
+ end
59
+
60
+ it 'returns true when on' do
61
+ @output.on
62
+ @output.should be_on
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewby::Outputs do
4
+ it 'determines output class based on adapter' do
5
+ Brewby::Outputs.adapter_class(:test).should == Brewby::Outputs::Test
6
+ Brewby::Outputs.adapter_class(:raspberry_pi).should == Brewby::Outputs::GPIO
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'brewby'
4
+ require 'pry'
5
+
6
+ load './spec/support/virtual_view.rb'
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewby::StepLoader do
4
+ before do
5
+ Brewby::Application.any_instance.stub(:render)
6
+ Brewby::Application.any_instance.stub(:configure_view)
7
+
8
+ @application = Brewby::Application.new adapter: :test,
9
+ outputs: [{ pin: 1, name: :hlt }, { pin: 2, name: :mlt }, { pin: 3, name: :bk }],
10
+ inputs: [{ name: :hlt}, { name: :mlt }, { name: :bk }]
11
+ @loader = Brewby::StepLoader.new @application
12
+ end
13
+
14
+ it 'reads a Brewby process file' do
15
+ @loader.load_file File.join(File.dirname(__FILE__), 'support', 'sample_recipe.rb')
16
+ @application.should have(4).steps
17
+ end
18
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewby::Steps::DSL::Step do
4
+ before do
5
+ @outputs = [
6
+ { name: :hlt, pin: 3 },
7
+ { name: :mlt, pin: 2 },
8
+ { name: :bk, pin: 1 }
9
+ ]
10
+
11
+ @inputs = [
12
+ { name: :bk },
13
+ { name: :mlt },
14
+ { name: :hlt }
15
+ ]
16
+
17
+ Brewby::Application.any_instance.stub(:render)
18
+ Brewby::Application.any_instance.stub(:configure_view)
19
+ @application = Brewby::Application.new adapter: :test, outputs: @outputs, inputs: @inputs
20
+ @step = Brewby::Steps::DSL::Step.new 'Test Step', @application
21
+ end
22
+
23
+ it 'accepts a type' do
24
+ @step.type :temp_control
25
+ @step.step_class.should == Brewby::Steps::TempControl
26
+ end
27
+
28
+ it 'accepts options on the type' do
29
+ @step.type :temp_control, mode: :auto, target: 155.0, duration: 60
30
+ @step.options[:mode].should == :auto
31
+ @step.options[:target].should == 155.0
32
+ @step.options[:duration].should == 60
33
+ end
34
+
35
+ it 'accepts a mode' do
36
+ @step.mode :manual
37
+ @step.options[:mode].should == :manual
38
+ end
39
+
40
+ it 'accepts a target' do
41
+ @step.target 155.0
42
+ @step.options[:target].should == 155.0
43
+ end
44
+
45
+ context 'creation' do
46
+ before do
47
+ @step.type :temp_control, mode: :manual, power_level: 1.0
48
+ @step.input :mlt
49
+ @step.output :bk
50
+ @created_step = @step.create!
51
+ end
52
+
53
+ it 'should translate the input correctly' do
54
+ @created_step.input.name.should == :mlt
55
+ end
56
+
57
+ it 'should translate the output correctly' do
58
+ @created_step.output.name.should == :bk
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,187 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewby::Steps::TempControl do
4
+ let(:sensor) { Brewby::TempSensor.new 1 }
5
+ let(:adapter) { Brewby::Outputs::Test.new }
6
+ let(:element) { Brewby::HeatingElement.new adapter, pulse_width: 5000 }
7
+ let(:step) { Brewby::Steps::TempControl.new mode: :manual, input: sensor, output: element }
8
+
9
+ it 'configures an input sensor' do
10
+ step.input.should be_instance_of Brewby::TempSensor
11
+ end
12
+
13
+ it 'configures an output sensor' do
14
+ step.output.should be_instance_of Brewby::HeatingElement
15
+ end
16
+
17
+ context 'automatic temperature control' do
18
+ before do
19
+ @step = Brewby::Steps::TempControl.new mode: :auto, target: 155.0,
20
+ duration: 15, output: element, input: sensor
21
+ end
22
+
23
+ it 'configures a PID controller' do
24
+ @step.pid.should be_instance_of Temper::PID
25
+ end
26
+
27
+ it 'sets a target temperature' do
28
+ @step.target.should == 155.0
29
+ @step.pid.setpoint.should == 155.0
30
+ end
31
+
32
+ it 'sets a temperature hold duration' do
33
+ @step.duration.should == 15
34
+ end
35
+
36
+ it 'defaults to a 1 minute temperature hold duration' do
37
+ step = Brewby::Steps::TempControl.new mode: :auto, target: 155.0
38
+ step.duration.should == 1
39
+ end
40
+
41
+ it 'returns true for automatic control' do
42
+ @step.automatic_control?.should be_true
43
+ end
44
+
45
+ it 'returns false for manual control' do
46
+ @step.manual_control?.should be_false
47
+ end
48
+
49
+ it 'calculates the output level based on PID levels' do
50
+ @step.calculate_power_level
51
+ @step.power_level.should == 1.0
52
+ @step.output.pulse_width.should == 5000
53
+ end
54
+
55
+ it 'does not explode with a faulty input' do
56
+ @step.stub(:read_input) { nil }
57
+ @step.calculate_power_level
58
+ @step.output.pulse_width.should == 0
59
+ end
60
+ end
61
+
62
+ context 'manual temperature control' do
63
+ before do
64
+ @step = Brewby::Steps::TempControl.new mode: :manual, power_level: 0.85, output: element, input: sensor
65
+ end
66
+
67
+ it 'does not create a PID controller' do
68
+ @step.pid.should be_nil
69
+ end
70
+
71
+ it 'returns true for manual mode' do
72
+ @step.should be_manual_control
73
+ end
74
+
75
+ it 'returns false for automatic control' do
76
+ @step.should_not be_automatic_control
77
+ end
78
+
79
+ it 'sets the power level' do
80
+ @step.power_level.should == 0.85
81
+ @step.output.pulse_width.should == 4250
82
+ end
83
+
84
+ it 'can have the power level set manually' do
85
+ @step.set_power_level 0.75
86
+ @step.power_level.should == 0.75
87
+ @step.output.pulse_width.should == 3750
88
+ end
89
+ end
90
+
91
+ context 'sensor input' do
92
+ before do
93
+ @step = Brewby::Steps::TempControl.new mode: :auto, target: 155.0,
94
+ duration: 15, input: sensor, output: element
95
+ end
96
+
97
+ it 'reads sensor input' do
98
+ @step.input.should_receive(:read) { 115.0 }
99
+ @step.read_input.should == 115.0
100
+ @step.last_reading.should == 115.0
101
+ end
102
+
103
+ it 'does not set last_reading if sensor input is faulty' do
104
+ @step.input.stub(:read) { nil }
105
+ @step.read_input.should be_nil
106
+ @step.last_reading.should == 0.0
107
+ end
108
+ end
109
+
110
+ describe 'step iteration' do
111
+ context 'with manual control' do
112
+ before do
113
+ @step = Brewby::Steps::TempControl.new mode: :manual, power_level: 0.85,
114
+ input: sensor, output: element
115
+ end
116
+
117
+ it 'pulses the element' do
118
+ @step.step_iteration
119
+ @step.output.should be_on
120
+ end
121
+
122
+ it 'takes a sensor reading' do
123
+ @step.input.stub(:read) { 115.0 }
124
+ @step.step_iteration
125
+ @step.last_reading.should == 115.0
126
+ end
127
+
128
+ it 'does not take a sensor reading if input does not exist' do
129
+ @step = Brewby::Steps::TempControl.new mode: :manual, power_level: 0.85, output: element
130
+ @step.step_iteration
131
+ @step.last_reading.should == 0.0
132
+ end
133
+ end
134
+
135
+ context 'with automatic control' do
136
+ before do
137
+ @step = Brewby::Steps::TempControl.new mode: :auto, target: 155.0,
138
+ duration: 15, input: sensor, output: element
139
+ end
140
+
141
+ it 'pulses the element' do
142
+ @step.output.should_receive(:pulse)
143
+ @step.step_iteration
144
+ end
145
+
146
+ it 'calculates the power level and adjusts the heating element' do
147
+ @step.pid.stub(:control) { 3000 }
148
+ @step.step_iteration
149
+ @step.output.pulse_width.should == 3000
150
+ end
151
+
152
+ it 'reads from the sensor and logs to last_reading' do
153
+ @step.input.stub(:read) { 125.0 }
154
+ @step.step_iteration
155
+ @step.last_reading.should == 125.0
156
+ end
157
+
158
+ context 'when temperature threshold is reached' do
159
+ before do
160
+ @step.input.stub(:read) { 156.0 }
161
+ @step.step_iteration
162
+ end
163
+
164
+ it 'sets the threshold as true' do
165
+ @step.threshold_reached.should be_true
166
+ end
167
+
168
+ it 'maintains threshold_reached even when temp drops below threshold' do
169
+ @step.input.stub(:read) { 145.0 }
170
+ @step.step_iteration
171
+ @step.threshold_reached.should be_true
172
+ end
173
+
174
+ it 'starts the clock on time remaining' do
175
+ (@step.time_remaining > 0).should be_true
176
+ (@step.time_remaining <= @step.duration_in_seconds).should be_true
177
+ end
178
+
179
+ it 'stops the step when temperature has hit target for duration' do
180
+ @step.instance_variable_set(:@step_finishes_at, Time.now.to_i - 10)
181
+ @step.step_iteration
182
+ @step.should be_ended
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end