pidom 0.1.1

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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 107f8c813c306a9d5c5bdb5af8fb037b1e784a77bbd73738bedd9ba1fe554121
4
+ data.tar.gz: 37dc361c167c0b721c708fbd1226c2468704db779571a5fea404512bd8f44605
5
+ SHA512:
6
+ metadata.gz: 797456e83d89a5ba94d39f024c20ecd8317fb4d9beb4a9b71e1080e9da8242a7c98db580d75c382e4cdd9421c154686de45505740a732a8959df9f306e24453a
7
+ data.tar.gz: 2bf7b486c4c1b7ce1309f0917048fb3284bbd2ecc4526e4ce4d32898fc79efabbac2f76440593bfe5d164fcd61dfbc72a444877de8d418067f787f306dca74b1
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,10 @@
1
+ # Offense count: 3
2
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
3
+ # AllowedNames: io, id, to, by, on, in, at, ip
4
+ Naming/UncommunicativeMethodParamName:
5
+ Exclude:
6
+ - 'lib/temper.rb'
7
+
8
+ Metrics/BlockLength:
9
+ Exclude:
10
+ - 'spec/temper_spec.rb'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pidom.gemspec
4
+ gemspec
@@ -0,0 +1,56 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # guard 'rake', :task => 'build' do
19
+ # watch(%r{^my_file.rb})
20
+ # end
21
+
22
+ guard :rubocop do
23
+ watch(/.+\.rb$/)
24
+ watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
25
+ end
26
+
27
+ # Note: The cmd option is now required due to the increasing number of ways
28
+ # rspec may be run, below are examples of the most common uses.
29
+ # * bundler: 'bundle exec rspec'
30
+ # * bundler binstubs: 'bin/rspec'
31
+ # * spring: 'bin/rspec' (This will use spring if running and you have
32
+ # installed the spring binstubs per the docs)
33
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
34
+ # * 'just' rspec: 'rspec'
35
+ guard :rspec, cmd: 'bundle exec rspec' do
36
+ require 'guard/rspec/dsl'
37
+ dsl = Guard::RSpec::Dsl.new(self)
38
+
39
+ # Feel free to open issues for suggestions and improvements
40
+
41
+ # RSpec files
42
+ rspec = dsl.rspec
43
+ watch(rspec.spec_helper) { rspec.spec_dir }
44
+ watch(rspec.spec_support) { rspec.spec_dir }
45
+ watch(rspec.spec_files)
46
+
47
+ # Ruby files
48
+ ruby = dsl.ruby
49
+ dsl.watch_spec_files_for(ruby.lib_files)
50
+
51
+ # Turnip features and steps
52
+ watch(%r{^spec/acceptance/(.+)\.feature$})
53
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
54
+ Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance'
55
+ end
56
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrew Nordman, Marcos Piccinini
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,85 @@
1
+ # Pidom
2
+
3
+ Temperature controlling, easy way. It uses an improved PID algorithm to
4
+ decrease overshoots and regulate based on continued inputs.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'pidom'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install pidom
19
+
20
+ ## Usage
21
+
22
+ To start, create an instance of Pidom::PID. The PID algorithm can be configured with
23
+ custom minimum and maximum values for ease of integration with external control systems
24
+ (PWM-controlled heating elements, for example). Once created, run `Pidom::PID#control`
25
+ in your control loop, feeding it sensor data.
26
+
27
+ ## Proportional on Measurement
28
+
29
+ All thanks to
30
+
31
+ http://brettbeauregard.com/blog/category/pid/
32
+
33
+ Also:
34
+
35
+ https://controlguru.com/pid-control-and-derivative-on-measurement/
36
+
37
+ ### Minimum Interval Calculation
38
+
39
+ The algorithm being used is minimum interval and will not recalibrate until the time interval
40
+ has passed before recalibrating. This helps mitigate excess compensation and inconsistent
41
+ adjustment. The update interval is also configurable in Pidom with the `interval` option.
42
+
43
+ ### Directional Control
44
+
45
+ When handling cooling-based temperature control, negative values are a pain for
46
+ translation. To assist with this, Pidom uses a directional control parameter. The two
47
+ possible states are `:direct` and `:reverse`. When using `:reverse`, negative values
48
+ are inverted.
49
+
50
+ ### Tuning
51
+
52
+ Pidom's PID is manually tuned with the `tune` method, which takes a Kp, Ki, and Kd value. By
53
+ default, Pidom will set them to 1.0. `Pidom::PID` also can take the kp, ki, and kd options
54
+ in the constructor call.
55
+
56
+ ## Example
57
+
58
+ ``` ruby
59
+ require 'pidom'
60
+
61
+ pidom = Pidom::PID.new(interval: 1000, minimum: 0, maximum: 1000, direction: :direct)
62
+ pidom.tune(9.0, 25.0, 6.0) # Set Kp, Ki, and Kd
63
+ pidom.setpoint = 100.0 # Set target temperature
64
+
65
+ while input = read_sensor() # Replace read_sensor with your external system
66
+ output = pidom.control(input)
67
+ # output is a value betwen minimum and maximum. This can be used for thresholds or
68
+ # PWM-based control
69
+ end
70
+ ```
71
+
72
+ For more examples check out the [examples](examples/) directory.
73
+
74
+ ## Temper
75
+
76
+ This is a rewrite of 'temper-control' gem with PonM algorithm.
77
+
78
+
79
+ ## Contributing
80
+
81
+ 1. Fork it
82
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
83
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
84
+ 4. Push to the branch (`git push origin my-new-feature`)
85
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,30 @@
1
+ ###############
2
+ #
3
+ # This example uses WiringPi-Ruby (https://github.com/WiringPi/WiringPi-Ruby)
4
+ # to read sensor data from GPIO 7, feed it to Pidom, and adjust the heat
5
+ # control via PWM based on the resulting power level returned from Pidom.
6
+ # It is controlled for 60s.
7
+ #
8
+ ###############
9
+ require 'wiringpi'
10
+
11
+ # Setup GPIO for sensor data and heat control
12
+ io = WiringPi::GPIO.new
13
+ SENSOR_PIN = 7
14
+ PWM_PIN = 11
15
+ io.mode SENSOR_PIN, WiringPi::INPUT
16
+ io.mode PWN_PIN, WiringPi::PWM_OUTPUT
17
+
18
+ # Setup PID
19
+
20
+ pid = Pidom::PID.new kp: 1.0, ki: 1.0, kd: 1.0, maximum: 100
21
+ pid.setpoint = 175.5 # Target temp - Degrees Fahrenheit
22
+
23
+ # Control temp for 60 seconds
24
+ stop = Time.now + 60
25
+
26
+ while Time.now < stop
27
+ temperature = io.read SENSOR_PIN
28
+ adjusted_power_level = pid.control temperature
29
+ io.pwmWrite PWM_PIN, adjusted_power_level
30
+ end
@@ -0,0 +1,92 @@
1
+ require 'pidom/version'
2
+
3
+ module Pidom
4
+ # Le PID
5
+ class PID
6
+ attr_accessor :kp, :ki, :kd, :pom, :setpoint, :direction, :output
7
+
8
+ def initialize(options = {})
9
+ @interval = options[:interval] || 1000
10
+ @last_time = 0.0
11
+ @last_input = 0.0
12
+ @output_sum = 0.0
13
+ @output_max = options[:maximum] || 1000
14
+ @output_min = options[:minimum] || 0
15
+
16
+ @kp = options.delete(:kp)
17
+ @ki = options.delete(:ki)
18
+ @kd = options.delete(:kd)
19
+
20
+ self.pom = options[:pom] ? true : false
21
+ self.mode = options[:mode] || :auto
22
+ self.direction = options[:direction] || :direct
23
+ end
24
+
25
+ def control(input)
26
+ return unless @auto # manual mode
27
+
28
+ now = Time.now.to_f
29
+ time_change = (now - @last_time) * 1000
30
+ return unless time_change >= @interval
31
+
32
+ error = @setpoint - input
33
+ dinput = input - @last_input
34
+
35
+ # Calculate Sum
36
+ @output_sum += ki * error
37
+
38
+ # Add Proportional on Measurement
39
+ @output_sum -= kp * dinput if pom
40
+
41
+ @output_sum = @output_max if @output_sum > @output_max
42
+ @output_sum = @output_min if @output_sum < @output_min
43
+
44
+ # Add Proportional on Error
45
+ @output = pom ? 0 : kp * error
46
+
47
+ # Finish PID
48
+ @output += @output_sum - kd * dinput
49
+
50
+ @output = @output_max if @output > @output_max
51
+ @output = @output_min if @output < @output_min
52
+
53
+ @last_time = now
54
+ @last_input = input
55
+
56
+ @output
57
+ end
58
+
59
+ def tune(kp, ki, kd, pom = nil)
60
+ return if kp < 0 || ki < 0 || kd < 0
61
+
62
+ @pom = pom == true
63
+ interval_seconds = @interval / 1000.0
64
+
65
+ @kp = kp
66
+ @ki = ki * interval_seconds
67
+ @kd = kd / interval_seconds
68
+
69
+ return if @direction == :direct
70
+ @kp = 0 - @kp
71
+ @ki = 0 - @ki
72
+ @kd = 0 - @kd
73
+ end
74
+
75
+ def update_interval(new_interval)
76
+ return unless new_interval > 0
77
+ ratio = new_interval / @interval
78
+
79
+ @ki *= ratio
80
+ @kd /= ratio
81
+ @interval = new_interval
82
+ end
83
+
84
+ def mode=(mode)
85
+ @auto = mode == :auto
86
+ end
87
+
88
+ def mode
89
+ @auto ? :auto : :manual
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,3 @@
1
+ module Pidom
2
+ VERSION = '0.1.1'.freeze
3
+ end
@@ -0,0 +1,20 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'pidom/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'pidom'
7
+ gem.version = Pidom::VERSION
8
+ gem.authors = ['Andrew Nordman', 'Marcos Piccinini']
9
+ gem.email = ['pidr@pidr.com']
10
+ gem.description = 'PID Control Library'
11
+ gem.summary = 'Temperature/motion controller based on the PID PonM algorithm'
12
+ gem.homepage = 'https://github.com/nofxx/pidom'
13
+
14
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ['lib']
18
+
19
+ gem.add_development_dependency 'rspec'
20
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pidom::PID do
4
+ let(:controller) { Pidom::PID.new }
5
+
6
+ before do
7
+ controller.setpoint = 100.0
8
+ controller.tune 1.0, 1.0, 1.0
9
+ end
10
+ subject { controller }
11
+
12
+ it { expect(subject.kp).to eq 1.0 }
13
+ it { expect(subject.ki).to eq 1.0 }
14
+ it { expect(subject.kd).to eq 1.0 }
15
+ it { expect(subject.pom).to be_falsey }
16
+ it { expect(subject.mode).to eq :auto }
17
+ it { expect(subject.direction).to eq :direct }
18
+
19
+ context 'reverse' do
20
+ before do
21
+ controller.direction = :reverse
22
+ controller.tune 1.0, 1.0, 1.0
23
+ end
24
+ subject { controller }
25
+
26
+ it { expect(subject.kp).to eq(-1.0) }
27
+ it { expect(subject.ki).to eq(-1.0) }
28
+ it { expect(subject.kd).to eq(-1.0) }
29
+ it { expect(subject.mode).to eq :auto }
30
+ it { expect(subject.direction).to eq :reverse }
31
+ end
32
+
33
+ # TODO..Mock Time?
34
+ context 'computing data PoE' do
35
+ before do
36
+ controller.control 50.0
37
+ end
38
+
39
+ it { expect(subject.output).to eq 50.0 }
40
+ it { expect(subject.pom).to be_falsey }
41
+
42
+ it 'should calc it' do
43
+ controller.setpoint = 75.0
44
+ expect(subject.output).to eq 50.0
45
+ sleep 1
46
+ controller.control 50.0
47
+ expect(subject.output).to eq 100.0
48
+ sleep 1
49
+ controller.control 85.0
50
+ expect(subject.output).to eq 20.0
51
+ end
52
+ end
53
+
54
+ context 'computing data PoM' do
55
+ before do
56
+ controller.control 50.0
57
+ controller.pom = true
58
+ end
59
+
60
+ it { expect(subject.output).to eq 50.0 }
61
+ it { expect(subject.pom).to be_truthy }
62
+
63
+ it 'should calc it' do
64
+ controller.setpoint = 75.0
65
+ expect(subject.output).to eq 50.0
66
+ sleep 1
67
+ controller.control 50.0
68
+ expect(subject.output).to eq 75.0
69
+ sleep 1
70
+ controller.control 72.0
71
+ expect(subject.output).to eq 34.0
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.push File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'pidom'
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pidom
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Nordman
8
+ - Marcos Piccinini
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-08-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ description: PID Control Library
29
+ email:
30
+ - pidr@pidr.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - ".rubocop.yml"
37
+ - Gemfile
38
+ - Guardfile
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - examples/wiringpi.rb
43
+ - lib/pidom.rb
44
+ - lib/pidom/version.rb
45
+ - pidom.gemspec
46
+ - spec/pidom_spec.rb
47
+ - spec/spec_helper.rb
48
+ homepage: https://github.com/nofxx/pidom
49
+ licenses: []
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 2.7.6
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Temperature/motion controller based on the PID PonM algorithm
71
+ test_files:
72
+ - spec/pidom_spec.rb
73
+ - spec/spec_helper.rb