pifan 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 68ad632c8933be7ae9aee5b0d04ed6fa916ebabf81001f37863afba54c40cb5d
4
+ data.tar.gz: 84363ab0a93c667e16895e38d094f3509fd1de532f22b2ef107c3e7dffbff228
5
+ SHA512:
6
+ metadata.gz: a1e3cd11d5dd543b1710d81068945d23afd0c8142d97da39980cbf26ad69f4272728171adc17519e5930b4b6291ef159c521a223ec3ea8a057a094b00118edf5
7
+ data.tar.gz: 34fde7d79f1821ed8561f355a2e17bd3e6a8c7b8f4ba03bc5498d481c1de1476264d45d487f9ad8f02020d1b86347c2fb984cc19cf1b221eaa0818fc450e27f2
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor/
@@ -0,0 +1,26 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ NewCops: enable
4
+ Exclude:
5
+ - 'vendor/**/*'
6
+
7
+ Layout/LineLength:
8
+ Max: 120
9
+
10
+ Style/Documentation:
11
+ Enabled: false
12
+
13
+ Metrics/MethodLength:
14
+ Max: 20
15
+
16
+ Metrics/AbcSize:
17
+ Max: 20
18
+
19
+ Metrics/ClassLength:
20
+ Max: 120
21
+
22
+ Metrics/BlockLength:
23
+ Exclude:
24
+ - 'Rakefile'
25
+ - '**/*.rake'
26
+ - '*.gemspec'
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in pifan.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Pifan - Control your Raspberry Pi fan
2
+ Copyright (C) 2021 Michal Kimle
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU General Public License as published by
6
+ the Free Software Foundation, either version 3 of the License, or
7
+ (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,67 @@
1
+ # Pifan
2
+ **Control your Raspberry Pi fan**
3
+
4
+ Pifan is a daemon adjusting your fan's strength based on your Raspberry Pi's temperature.
5
+
6
+ The whole project was inspired and based on this [instructable](https://www.instructables.com/PWM-Regulated-Fan-Based-on-CPU-Temperature-for-Ras/). Many thanks to the author [Aerandir14](https://www.instructables.com/member/Aerandir14/)!
7
+ ## Hardware configuration
8
+ The most basic configuration is depicted on the following schematics:
9
+
10
+ <img src="schematics/pifan-breadboard.png" width="800"/>
11
+ <br/>
12
+ <img src="schematics/pifan-schematic.png" width="800"/>
13
+
14
+ This is the most common setup with **5V** fan rated for **200mA**. If you have different components, read through the [instructable](https://www.instructables.com/PWM-Regulated-Fan-Based-on-CPU-Temperature-for-Ras/), you can find instructions on how to calculate the correct values for your setup.
15
+
16
+ ## Installation
17
+ ### Arch Linux
18
+ You can install the [`pifan`](https://aur.archlinux.org/packages/pifan) package from AUR.
19
+ ```bash
20
+ yay -S pifan
21
+ ```
22
+
23
+ ### RubyGems.org
24
+ To install the most recent stable version:
25
+ ```bash
26
+ gem install pifan
27
+ ```
28
+
29
+ ### Source (development)
30
+ **Installation from source should never be your first choice! Especially, if you are not familiar with RVM, Bundler, Rake and other dev tools for Ruby!**
31
+
32
+ **However, if you wish to contribute to my project, this is the right way to start.**
33
+
34
+ To build and install the bleeding edge version from master:
35
+ ```bash
36
+ git clone git://gitlab.com/amarthadan/pifan.git
37
+ cd pifan
38
+ gem install bundler
39
+ bundle install
40
+ ```
41
+
42
+ ## Configuration
43
+ Configuration is read from `/etc/pifan/pifan.yml` (file is automatically created when installed [via package method](#arch-linux)). Sample (and also the default) configuration file can be found at [`config/pifan.yml`](config/pifan.yml).
44
+
45
+ Pifan can be also controlled via CLI options. Run
46
+ ```bash
47
+ pifan -h
48
+ ```
49
+ to list all the available options.
50
+
51
+ ## Usage
52
+ You can run Pifan manually via `pifan` command. This is particularly useful in order to figure out the correct configuration for your setup. Once you know the correct values, you can use [`systemd/pifan.service`](systemd/pifan.service) systemd unit to control Pifan as a daemon (service unit is automatically available when installed [via package method](#arch-linux)).
53
+
54
+ ```bash
55
+ sudo systemctl enable pifan
56
+ sudo systemctl start pifan
57
+ ```
58
+
59
+ ## Development & contribution
60
+ Contributions are very welcome! To setup the project for development follow the [installation from source](#source-development) instructions.
61
+
62
+ Contribution flow:
63
+ 1. Fork it (https://gitlab.com/amarthadan/pifan/fork)
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create a new Merge Request
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop/rake_task'
4
+ require 'bundler/gem_tasks'
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'pifan'
5
+
6
+ SIGINT = 2
7
+ SIGTERM = 15
8
+ SIGNALS = [SIGTERM, SIGINT].freeze
9
+
10
+ cli = Pifan::CLI.new
11
+ config = Pifan::Config.new
12
+
13
+ cli.parse
14
+ options = cli.run
15
+ config = config.read
16
+
17
+ parameters = config.merge(options)
18
+
19
+ Pifan::Logging.level = :debug if parameters[:debug]
20
+
21
+ Pifan::Logging.logger.debug "Starting pifan #{Pifan::VERSION}"
22
+ Pifan::Logging.logger.debug "Configuration: #{parameters}"
23
+
24
+ abort 'Refresh time has to be at least 1 second' if parameters[:refresh_time] < 1
25
+ if parameters[:temperature_steps].length != parameters[:speed_steps].length
26
+ abort 'Temperature and speed steps arrays have to be of the same length'
27
+ end
28
+
29
+ begin
30
+ process = Pifan::Process.new parameters
31
+ process.start
32
+ rescue SignalException => e
33
+ raise e unless SIGNALS.include? e.signo
34
+
35
+ Pifan::Logging.logger.debug 'Running cleanup'
36
+ process.cleanup
37
+ Pifan::Logging.logger.debug 'Terminating'
38
+ end
@@ -0,0 +1,9 @@
1
+ control-pin: 15 # Controll PIN
2
+ refresh-time: 1 # [s] Time to wait between each refresh
3
+ pwm-frequency: 25 # [Hz]
4
+ temperature-steps: # [°C]
5
+ - 50
6
+ - 70
7
+ speed-steps: # [%]
8
+ - 0
9
+ - 100
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+
5
+ module Pifan
6
+ autoload :Logging, 'pifan/logging'
7
+ autoload :CLI, 'pifan/cli'
8
+ autoload :Config, 'pifan/config'
9
+ autoload :Process, 'pifan/process'
10
+ end
11
+
12
+ require 'pifan/version'
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-option'
4
+
5
+ module Pifan
6
+ class CLI
7
+ include TTY::Option
8
+
9
+ usage do
10
+ no_command
11
+
12
+ desc 'Control your Raspberry Pi fan'
13
+ end
14
+
15
+ flag :help do
16
+ short '-h'
17
+ long '--help'
18
+ desc 'Print usage'
19
+ end
20
+
21
+ flag :version do
22
+ short '-v'
23
+ long '--version'
24
+ desc 'Print version'
25
+ end
26
+
27
+ flag :debug do
28
+ short '-d'
29
+ long '--debug'
30
+ desc 'Run in debug mode'
31
+ default false
32
+ end
33
+
34
+ option :control_pin do
35
+ short '-p'
36
+ long '--control-pin pin'
37
+ desc 'Pin to control Raspberry Pi fan with'
38
+ optional
39
+ convert :int
40
+ end
41
+
42
+ option :refresh_time do
43
+ short '-t'
44
+ long '--refresh-time time'
45
+ desc 'Time to wait between each refresh in seconds'
46
+ optional
47
+ convert :int
48
+ end
49
+
50
+ option :pwm_frequency do
51
+ short '-f'
52
+ long '--pwm-frequency frequency'
53
+ desc 'PWM frequency in Hz'
54
+ optional
55
+ convert :int
56
+ end
57
+
58
+ option :temperature_steps do
59
+ long '--temperature-steps steps'
60
+ desc 'Temperature steps in °C'
61
+ optional
62
+ convert :ints
63
+ end
64
+
65
+ option :speed_steps do
66
+ long '--speed-steps steps'
67
+ desc 'Speed steps in percents'
68
+ optional
69
+ convert :ints
70
+ end
71
+
72
+ def run
73
+ if params[:help]
74
+ print help
75
+ exit
76
+ elsif params[:version]
77
+ $stdout.puts Pifan::VERSION
78
+ exit
79
+ else
80
+ params.to_h
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-config'
4
+
5
+ module Pifan
6
+ class Config
7
+ def initialize
8
+ @config = TTY::Config.new
9
+ @config.filename = 'pifan'
10
+ @config.extname = '.yml'
11
+ @config.append_path('/etc/pifan')
12
+ @config.append_path("#{File.dirname(__FILE__)}/../../config")
13
+ end
14
+
15
+ def read
16
+ @config.read.deep_transform_keys { |key| key.underscore.to_sym }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-logger'
4
+
5
+ module Pifan
6
+ module Logging
7
+ class << self
8
+ def level
9
+ @level ||= :error
10
+ end
11
+
12
+ def logger
13
+ @logger ||= TTY::Logger.new do |config|
14
+ config.level = level
15
+ end
16
+ end
17
+
18
+ attr_writer :logger, :level
19
+ end
20
+
21
+ def self.included(base)
22
+ class << base
23
+ def logger
24
+ Logging.logger
25
+ end
26
+ end
27
+ end
28
+
29
+ def logger
30
+ Logging.logger
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rpi_gpio'
4
+
5
+ CPU_TEMP_FILENAME = '/sys/class/thermal/thermal_zone0/temp'
6
+
7
+ module Pifan
8
+ class Process
9
+ include Pifan::Logging
10
+
11
+ def initialize(parameters)
12
+ @control_pin = parameters[:control_pin]
13
+ @refresh_time = parameters[:refresh_time]
14
+ @pwm_frequency = parameters[:pwm_frequency]
15
+ @temp_steps = parameters[:temperature_steps]
16
+ @speed_steps = parameters[:speed_steps]
17
+
18
+ RPi::GPIO.set_numbering :bcm
19
+ RPi::GPIO.setup @control_pin, as: :output, initialize: :low
20
+ @fan = RPi::GPIO::PWM.new(@control_pin, @pwm_frequency)
21
+ @fan.start(0)
22
+ end
23
+
24
+ def start
25
+ hyst = 1
26
+ cpu_temp_old = 0
27
+ fan_speed_old = 0
28
+
29
+ # Run at full speed for 2 secs to avoid not starting at small speed
30
+ @fan.duty_cycle = 100
31
+ sleep 2
32
+
33
+ loop do
34
+ cpu_temp_current = cpu_temp
35
+ logger.debug("New temperature: #{cpu_temp_current}")
36
+
37
+ if (cpu_temp_current - cpu_temp_old).abs > hyst
38
+ fan_speed_current = fan_speed
39
+ if fan_speed_current != fan_speed_old
40
+ logger.debug("New fan speed: #{fan_speed_current}")
41
+ @fan.duty_cycle = fan_speed_current
42
+ fan_speed_old = fan_speed_current
43
+ end
44
+
45
+ cpu_temp_old = cpu_temp_current
46
+ end
47
+
48
+ sleep @refresh_time
49
+ end
50
+ end
51
+
52
+ def cleanup
53
+ RPi::GPIO.clean_up
54
+ end
55
+
56
+ private
57
+
58
+ def cpu_temp
59
+ File.read(CPU_TEMP_FILENAME).to_f / 1000
60
+ end
61
+
62
+ def fan_speed
63
+ if cpu_temp <= @temp_steps.first
64
+ @speed_steps.first
65
+ elsif cpu_temp >= @temp_steps.last
66
+ @speed_steps.last
67
+ else
68
+ more_index = @temp_steps.find_index { |n| cpu_temp < n }
69
+ less_index = more_index - 1
70
+
71
+ speed_interpolation less_index, more_index
72
+ end
73
+ end
74
+
75
+ def speed_interpolation(less_index, more_index)
76
+ ((@speed_steps[more_index] - @speed_steps[less_index]) / \
77
+ (@temp_steps[more_index] - @temp_steps[less_index]) * \
78
+ (cpu_temp - @temp_steps[less_index]) + \
79
+ @speed_steps[less_index]).round 1
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pifan
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'pifan/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'pifan'
9
+ spec.version = Pifan::VERSION
10
+ spec.authors = ['Michal Kimle']
11
+ spec.email = ['kimle.michal@gmail.com']
12
+
13
+ spec.summary = 'Control your Raspberry Pi fan'
14
+ spec.description = "Pifan is a daemon adjusting your fan's strength based on your Raspberry Pi's temperature."
15
+ spec.homepage = 'https://gitlab.com/amarthadan/pifan'
16
+ spec.license = 'GPL-3.0-only'
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_development_dependency 'bundler', '~> 2.1'
27
+ spec.add_development_dependency 'pry', '~> 0.13'
28
+ spec.add_development_dependency 'rake', '~> 13.0'
29
+ spec.add_development_dependency 'rubocop', '~> 0.92'
30
+
31
+ spec.add_runtime_dependency 'activesupport', '~> 6.0'
32
+ spec.add_runtime_dependency 'rpi_gpio', '~> 0.5'
33
+ spec.add_runtime_dependency 'tty-config', '~> 0.4'
34
+ spec.add_runtime_dependency 'tty-logger', '~> 0.5'
35
+ spec.add_runtime_dependency 'tty-option', '~> 0.1'
36
+
37
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
38
+ end
@@ -0,0 +1,10 @@
1
+ [Unit]
2
+ Description=Pifan Daemon
3
+
4
+ [Service]
5
+ ExecStart=/usr/bin/pifan
6
+ RestartSec=1
7
+ Restart=always
8
+
9
+ [Install]
10
+ WantedBy=multi-user.target
metadata ADDED
@@ -0,0 +1,189 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pifan
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Michal Kimle
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-01-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.13'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.13'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.92'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.92'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '6.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rpi_gpio
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.5'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: tty-config
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.4'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.4'
111
+ - !ruby/object:Gem::Dependency
112
+ name: tty-logger
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.5'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.5'
125
+ - !ruby/object:Gem::Dependency
126
+ name: tty-option
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.1'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.1'
139
+ description: Pifan is a daemon adjusting your fan's strength based on your Raspberry
140
+ Pi's temperature.
141
+ email:
142
+ - kimle.michal@gmail.com
143
+ executables:
144
+ - pifan
145
+ extensions: []
146
+ extra_rdoc_files: []
147
+ files:
148
+ - ".gitignore"
149
+ - ".rubocop.yml"
150
+ - Gemfile
151
+ - LICENSE
152
+ - README.md
153
+ - Rakefile
154
+ - bin/pifan
155
+ - config/pifan.yml
156
+ - lib/pifan.rb
157
+ - lib/pifan/cli.rb
158
+ - lib/pifan/config.rb
159
+ - lib/pifan/logging.rb
160
+ - lib/pifan/process.rb
161
+ - lib/pifan/version.rb
162
+ - pifan.gemspec
163
+ - schematics/pifan-breadboard.png
164
+ - schematics/pifan-schematic.png
165
+ - systemd/pifan.service
166
+ homepage: https://gitlab.com/amarthadan/pifan
167
+ licenses:
168
+ - GPL-3.0-only
169
+ metadata: {}
170
+ post_install_message:
171
+ rdoc_options: []
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: 2.3.0
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ requirements: []
185
+ rubygems_version: 3.1.4
186
+ signing_key:
187
+ specification_version: 4
188
+ summary: Control your Raspberry Pi fan
189
+ test_files: []