pifan 1.0.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,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: []