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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +112 -0
- data/Rakefile +6 -0
- data/brewby.gemspec +20 -0
- data/examples/basic_control.rb +24 -0
- data/examples/brewby_recipe.rb +35 -0
- data/examples/config.json +21 -0
- data/examples/recipe_loader.rb +28 -0
- data/lib/brewby.rb +6 -0
- data/lib/brewby/application.rb +88 -0
- data/lib/brewby/heating_element.rb +61 -0
- data/lib/brewby/inputs.rb +15 -0
- data/lib/brewby/inputs/ds18b20.rb +51 -0
- data/lib/brewby/inputs/test.rb +14 -0
- data/lib/brewby/outputs.rb +15 -0
- data/lib/brewby/outputs/gpio.rb +40 -0
- data/lib/brewby/outputs/test.rb +23 -0
- data/lib/brewby/step_loader.rb +27 -0
- data/lib/brewby/steps.rb +8 -0
- data/lib/brewby/steps/dsl/step.rb +48 -0
- data/lib/brewby/steps/temp_control.rb +111 -0
- data/lib/brewby/temp_sensor.rb +13 -0
- data/lib/brewby/timed.rb +50 -0
- data/lib/brewby/version.rb +3 -0
- data/spec/application_spec.rb +49 -0
- data/spec/heating_element_spec.rb +44 -0
- data/spec/inputs/ds18b20_spec.rb +39 -0
- data/spec/inputs_spec.rb +8 -0
- data/spec/outputs/gpio_spec.rb +65 -0
- data/spec/outputs_spec.rb +8 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/step_loader_spec.rb +18 -0
- data/spec/steps/dsl/step_spec.rb +61 -0
- data/spec/steps/temp_control_spec.rb +187 -0
- data/spec/support/sample_recipe.rb +32 -0
- data/spec/support/virtual_view.rb +31 -0
- data/spec/timed_spec.rb +110 -0
- metadata +151 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module Brewby
|
2
|
+
module Inputs
|
3
|
+
class DS18B20
|
4
|
+
attr_accessor :name, :hardware_id, :device_path
|
5
|
+
def initialize options = {}
|
6
|
+
@name = options[:name]
|
7
|
+
@device_path = options[:device_path] || "/sys/bus/w1/devices"
|
8
|
+
@hardware_id = options[:hardware_id] || find_hardware_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_hardware_id
|
12
|
+
w1_devices[0].gsub("#{device_path}/",'')
|
13
|
+
end
|
14
|
+
|
15
|
+
def w1_devices
|
16
|
+
Dir["#{device_path}/28*"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def read
|
20
|
+
raw = read_raw
|
21
|
+
|
22
|
+
if tempC = parse(raw)
|
23
|
+
tempF = to_fahrenheit tempC
|
24
|
+
else
|
25
|
+
tempC
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse raw_data
|
30
|
+
if temp_data = raw_data.match(/t=([0-9]+)/)
|
31
|
+
temp_data[1].to_f / 1000
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_fahrenheit temp
|
38
|
+
((temp * 1.8) + 32).round(3)
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_raw
|
42
|
+
File.read device_file
|
43
|
+
end
|
44
|
+
|
45
|
+
def device_file
|
46
|
+
"/sys/bus/w1/devices/#{@hardware_id}/w1_slave"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Brewby
|
2
|
+
module Outputs
|
3
|
+
class GPIO
|
4
|
+
attr_reader :gpio_path, :pin
|
5
|
+
def initialize options = {}
|
6
|
+
@pin = options[:pin]
|
7
|
+
@gpio_path = options[:gpio_path] || '/sys/class/gpio'
|
8
|
+
|
9
|
+
initialize_gpio_pin
|
10
|
+
initialize_gpio_direction
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_gpio_pin
|
14
|
+
unless File.exists? File.join(gpio_path, "gpio#{pin}", "value")
|
15
|
+
File.write File.join(gpio_path, "export"), pin
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_gpio_direction
|
20
|
+
File.write File.join(gpio_path, "gpio#{pin}", "direction"), 'out'
|
21
|
+
end
|
22
|
+
|
23
|
+
def on
|
24
|
+
write(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def off
|
28
|
+
write(0)
|
29
|
+
end
|
30
|
+
|
31
|
+
def on?
|
32
|
+
'1' == File.read(File.join(gpio_path, "gpio#{pin}", "value"))
|
33
|
+
end
|
34
|
+
|
35
|
+
def write value
|
36
|
+
File.write File.join(gpio_path, "gpio#{pin}", "value"), value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Brewby
|
2
|
+
module Outputs
|
3
|
+
class Test
|
4
|
+
attr_accessor :name
|
5
|
+
def initialize options = {}
|
6
|
+
@on = false
|
7
|
+
@name = options[:name]
|
8
|
+
end
|
9
|
+
|
10
|
+
def on
|
11
|
+
@on = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def off
|
15
|
+
@on = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def on?
|
19
|
+
@on
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Brewby
|
2
|
+
class StepLoader
|
3
|
+
def initialize application
|
4
|
+
@application = application
|
5
|
+
end
|
6
|
+
|
7
|
+
def load_file file
|
8
|
+
instance_eval File.read(file), file
|
9
|
+
end
|
10
|
+
|
11
|
+
def recipe name, &block
|
12
|
+
@application.name = name
|
13
|
+
yield self
|
14
|
+
end
|
15
|
+
|
16
|
+
def step name, &block
|
17
|
+
step = generate_step name, &block
|
18
|
+
@application.steps.push step
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate_step name, &block
|
22
|
+
step_generator = Brewby::Steps::DSL::Step.new name, @application
|
23
|
+
step_generator.instance_eval &block
|
24
|
+
step_generator.create!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/brewby/steps.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Brewby
|
2
|
+
module Steps
|
3
|
+
module DSL
|
4
|
+
class Step
|
5
|
+
attr_reader :step_class, :options
|
6
|
+
def initialize name, application
|
7
|
+
@application = application
|
8
|
+
@options = {}
|
9
|
+
@options[:name] = name
|
10
|
+
end
|
11
|
+
|
12
|
+
def type symbol, options = {}
|
13
|
+
@step_class = case symbol
|
14
|
+
when :temp_control
|
15
|
+
Brewby::Steps::TempControl
|
16
|
+
end
|
17
|
+
@options.merge!(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def mode m
|
21
|
+
@options[:mode] = m
|
22
|
+
end
|
23
|
+
|
24
|
+
def target t
|
25
|
+
@options[:target] = t
|
26
|
+
end
|
27
|
+
|
28
|
+
def hold_duration d
|
29
|
+
@options[:duration] = d
|
30
|
+
end
|
31
|
+
|
32
|
+
def input i
|
33
|
+
@options[:input] = i
|
34
|
+
end
|
35
|
+
|
36
|
+
def output o
|
37
|
+
@options[:output] = o
|
38
|
+
end
|
39
|
+
|
40
|
+
def create!
|
41
|
+
@options[:input] = @application.inputs.find { |i| i.name.to_sym == @options[:input] }
|
42
|
+
@options[:output] = @application.outputs.find { |o| o.name.to_sym == @options[:output] }
|
43
|
+
@step_class.new @options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'brewby/temp_sensor'
|
2
|
+
require 'brewby/heating_element'
|
3
|
+
require 'brewby/timed'
|
4
|
+
require 'temper'
|
5
|
+
|
6
|
+
module Brewby
|
7
|
+
module Steps
|
8
|
+
class TempControl
|
9
|
+
attr_reader :input, :output, :pid, :target, :duration, :mode, :last_reading, :threshold_reached, :name
|
10
|
+
|
11
|
+
include Brewby::Timed
|
12
|
+
|
13
|
+
def initialize options = {}
|
14
|
+
@mode = options[:mode] || :manual
|
15
|
+
@output = 0
|
16
|
+
@pulse_range = options[:pulse_range] || 5000
|
17
|
+
|
18
|
+
@input = options[:input]
|
19
|
+
@output = options[:output]
|
20
|
+
@threshold_reached = false
|
21
|
+
@name = options[:name]
|
22
|
+
@last_reading = 0.0
|
23
|
+
|
24
|
+
if automatic_control?
|
25
|
+
configure_automatic_control options
|
26
|
+
else
|
27
|
+
set_power_level options[:power_level] || 1.0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure_automatic_control options
|
32
|
+
@target = options[:target]
|
33
|
+
@duration = options[:duration] || 1
|
34
|
+
|
35
|
+
@pid = Temper::PID.new maximum: @pulse_range
|
36
|
+
@pid.tune 44, 165, 4
|
37
|
+
@pid.setpoint = @target
|
38
|
+
end
|
39
|
+
|
40
|
+
def manual_control?
|
41
|
+
!automatic_control?
|
42
|
+
end
|
43
|
+
|
44
|
+
def automatic_control?
|
45
|
+
@mode == :auto
|
46
|
+
end
|
47
|
+
|
48
|
+
def read_input
|
49
|
+
reading = input.read
|
50
|
+
@last_reading = reading if reading
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_power_level level
|
54
|
+
set_pulse_width (level * @pulse_range).to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
def calculate_power_level
|
58
|
+
if read_input
|
59
|
+
set_pulse_width pid.control @last_reading
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_pulse_width width
|
64
|
+
if width
|
65
|
+
output.pulse_width = width
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def power_level
|
70
|
+
output.pulse_width / @pulse_range.to_f
|
71
|
+
end
|
72
|
+
|
73
|
+
def step_iteration
|
74
|
+
if automatic_control?
|
75
|
+
calculate_power_level
|
76
|
+
output.pulse
|
77
|
+
check_temp_threshold unless threshold_reached
|
78
|
+
check_step_completion
|
79
|
+
else
|
80
|
+
read_input if input
|
81
|
+
output.pulse
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def check_step_completion
|
86
|
+
if threshold_reached && Time.now.to_i > @step_finishes_at
|
87
|
+
stop_timer
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def time_remaining
|
92
|
+
if @step_finishes_at
|
93
|
+
@step_finishes_at - Time.now.to_i
|
94
|
+
else
|
95
|
+
duration_in_seconds
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def check_temp_threshold
|
100
|
+
if last_reading >= target
|
101
|
+
@threshold_reached = true
|
102
|
+
@step_finishes_at = Time.now.to_i + duration_in_seconds
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def duration_in_seconds
|
107
|
+
@duration * 60
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/brewby/timed.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Brewby
|
2
|
+
module Timed
|
3
|
+
attr_reader :start_time, :end_time
|
4
|
+
|
5
|
+
def started?
|
6
|
+
!@start_time.nil?
|
7
|
+
end
|
8
|
+
|
9
|
+
def ended?
|
10
|
+
!@end_time.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def in_progress?
|
14
|
+
started? && !ended?
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_timer
|
18
|
+
@start_time = Time.now
|
19
|
+
end
|
20
|
+
|
21
|
+
def stop_timer
|
22
|
+
@end_time = Time.now
|
23
|
+
end
|
24
|
+
|
25
|
+
def elapsed
|
26
|
+
started? ? (@end_time || Time.now) - @start_time : 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def timer_for seconds
|
30
|
+
time = seconds > 0 ? time_from_seconds(seconds) : [0, 0, 0]
|
31
|
+
|
32
|
+
"%0.2d:%0.2d:%0.2d" % time
|
33
|
+
end
|
34
|
+
|
35
|
+
def countdown_for seconds
|
36
|
+
sign = seconds > 0 ? "" : "+"
|
37
|
+
|
38
|
+
sign + timer_for(seconds.abs)
|
39
|
+
end
|
40
|
+
|
41
|
+
def time_from_seconds seconds
|
42
|
+
hours = seconds / 3600
|
43
|
+
seconds -= (hours * 3600)
|
44
|
+
minutes = seconds / 60
|
45
|
+
seconds -= minutes * 60
|
46
|
+
|
47
|
+
[hours, minutes, seconds]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Brewby::Application do
|
4
|
+
before do
|
5
|
+
@output = {
|
6
|
+
pin: 1,
|
7
|
+
pulse_range: 5000
|
8
|
+
}
|
9
|
+
|
10
|
+
Brewby::Application.any_instance.stub(:configure_view)
|
11
|
+
@application = Brewby::Application.new adapter: :test, outputs: [@output], inputs: [{}, {hardware_id: '28-ba1c9d2e48'}]
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should have one output' do
|
15
|
+
@application.outputs.size.should == 1
|
16
|
+
@application.outputs.first.should be_instance_of Brewby::HeatingElement
|
17
|
+
@application.outputs.first.adapter.should be_instance_of Brewby::Outputs::Test
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should have two inputs' do
|
21
|
+
@application.inputs.size.should == 2
|
22
|
+
@application.inputs.each do |input|
|
23
|
+
input.should be_instance_of Brewby::Inputs::Test
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
context 'adding steps' do
|
29
|
+
before do
|
30
|
+
@application.add_step :temp_control, mode: :auto, mode: :auto, target: 155.0, duration: 15
|
31
|
+
@step = @application.steps.first
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'creates a step object with passed configuration options' do
|
35
|
+
@step.should be_instance_of Brewby::Steps::TempControl
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'passes an input and an output to the step' do
|
39
|
+
@step.input.should == @application.inputs.first
|
40
|
+
@step.output.should == @application.outputs.first
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'allows the step to specify the input/output objects to use' do
|
44
|
+
@application.add_step :temp_control, mode: :auto, mode: :auto, target: 155.0, duration: 15, input: @application.inputs.last
|
45
|
+
@step = @application.steps.last
|
46
|
+
@step.input.should == @application.inputs.last
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|