brewby 0.1.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.
@@ -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