Skip to content

Commit

Permalink
Add the control module
Browse files Browse the repository at this point in the history
  • Loading branch information
albus522 committed Nov 19, 2014
1 parent fee3d09 commit 491a30a
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 0 deletions.
4 changes: 4 additions & 0 deletions bin/therm
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application', __FILE__)
require APP_PATH
ThermControl.run
3 changes: 3 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class Application < Rails::Application
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.

# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)

# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
Expand Down
154 changes: 154 additions & 0 deletions lib/gpio.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Copyright (c) 2013, [Jason Whitehorn](https://github.com/jwhitehorn)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

module GPIO
extend self

# Defines an event block to be executed when an pin event occurs.
#
# == Parameters:
# options:
# Options hash. Options include `:pin`, `:invert` and `:trigger`.
#
def watch(options, &block)
Thread.new do
pin = Pin.new(options)
loop do
pin.wait_for_change
if block.arity > 0
block.call pin
else
pin.instance_exec &block
end
end
end.abort_on_exception = true
end

# Represents a GPIO pin on the Raspberry Pi
class Pin
attr_reader :pin, :last_value, :value, :direction, :invert

# Initializes a new GPIO pin.
#
# @param [Hash] options A hash of options
# @option options [Fixnum] :pin The pin number to initialize. Required.
# @option options [Symbol] :direction The direction of communication, either :in or :out. Defaults to :in.
# @option options [Boolean] :invert Indicates if the value read from the physical pin should be inverted. Defaults to false.
# @option options [Symbol] :trigger Indicates when the wait_for_change method will detect a change, either :rising, :falling, or :both edge triggers. Defaults to :both.
def initialize(options)
options = {direction: :in, invert: false, trigger: :both}.merge options
@pin = options[:pin]
@direction = options[:direction]
@invert = options[:invert]
@trigger = options[:trigger]

raise 'Invalid direction. Options are :in or :out' unless [:in, :out].include? @direction
raise 'Invalid trigger. Options are :rising, :falling, or :both' unless [:rising, :falling, :both].include? @trigger

curr_direction = File.read(direction_file).strip
if (@direction == :out && curr_direction == 'in') || (@direction == :in && curr_direction == 'out')
File.open(direction_file, 'w') {|f| f.write(@direction == :out ? 'out' : 'in') }
end

if @direction == :in
File.open(edge_file, 'w') {|f| f.write(@trigger.to_s) }
end

read
end

# If the pin has been initialized for output this method will set the logic level high.
def on
File.open(value_file, 'w') {|f| f.write('1') } if direction == :out
end

# Tests if the logic level is high.
def on?
!off?
end

# If the pin has been initialized for output this method will set the logic level low.
def off
File.open(value_file, 'w') {|f| f.write('0') } if direction == :out
end

# Tests if the logic level is low.
def off?
value == 0
end

# If the pin has been initialized for output this method will either raise or lower the logic level depending on `new_value`.
# @param [Object] new_value If false or 0 the pin will be set to off, otherwise on.
def update_value(new_value)
!new_value || new_value == 0 ? off : on
end

# Tests if the logic level has changed since the pin was last read.
def changed?
@last_value != @value
end

# blocks until a logic level change occurs. The initializer option `:trigger` modifies what edge this method will release on.
# continue is a lambda used to determine whether we should continue waiting after the 1 second IO timeout
def wait_for_change(continue=nil)
@fd ||= File.open(value_file, 'r')
@waiting = true
read
begin
ready = IO.select(nil, nil, [@fd], 1)
end while @waiting && !ready && (!continue || continue.call)
end

def break_wait_for_change
@waiting = false
end

# Reads the current value from the pin. Without calling this method first, `value`, `last_value` and `changed?` will not be updated.
# In short, you must call this method if you are curious about the current state of the pin.
def read
@last_value = @value

val = if @fd
@fd.rewind
@fd.read.to_i
else
File.read(value_file).to_i
end

@value = @invert ? (val ^ 1) : val
end

private

def value_file
"/sys/class/gpio/gpio#{pin}/value"
end

def edge_file
"/sys/class/gpio/gpio#{pin}/edge"
end

def direction_file
"/sys/class/gpio/gpio#{pin}/direction"
end
end
end
77 changes: 77 additions & 0 deletions lib/temp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
class Temp
DEVICE_DIR = '/sys/bus/w1/devices/'

def self.discover
Dir["#{DEVICE_DIR}28-*/w1_slave"].inject({}) do |hsh, path|
sensor = Sensor.new(path)
hsh[sensor.id] = sensor
hsh
end
end

def self.[](val)
(@sensors ||= discover)[val]
end

def self.[]=(name, val)
(@sensors ||= discover)[name] = val
end

def self.sensors
@sensors ||= discover
end

class ReadingFailed < StandardError; end

class Sensor
TEMP_REGEX = /^t=(\-?[0-9]+)$/
ID_REGEX = %r{/28-([^/]+)}
attr_reader :id

def self.from_id(id)
new("#{DEVICE_DIR}28-#{id}/w1_slave")
end

def initialize(path)
@path = path
@id = ID_REGEX.match(path)[1]
@value = -100.0
end

def c
read
@value
end

def f
read
(@value * 9 / 5) + 32
end

def read
@tries = 0
begin
output = File.read(@path)
parts = output.split
if parts.include?('YES')
if parts.detect {|p| TEMP_REGEX =~ p }
@value = $1.to_i / 1000.0
else
raise ReadingFailed, "No temp found #{Time.now} #{@id}\n#{output}\n\n"
end
else
raise ReadingFailed, "CRC Failed #{Time.now} #{@id}"
end
rescue ReadingFailed => error
@tries += 1

if @tries <= 5 && /^CRC/ =~ error.message
sleep 1
retry
else
raise
end
end
end
end
end
33 changes: 33 additions & 0 deletions lib/therm_control.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class ThermControl
SWING = 0.5
CONTROL_PIN = 17

def self.run
pin = GPIO::Pin.new(pin: CONTROL_PIN, direction: :out)
loop do
begin
set_temp = SetTemp.first
temp = Temp.discover.values.first.f

if set_temp.cooling?
if temp < (set_temp.temp - SWING)
pin.off
elsif temp > (set_temp.temp + SWING)
pin.on
end
else
if temp > (set_temp.temp + SWING)
pin.off
elsif temp < (set_temp.temp - SWING)
pin.on
end
end

sleep 5
rescue => e
puts e.inspect
sleep 5
end
end
end
end

0 comments on commit 491a30a

Please sign in to comment.