Skip to content

Commit 489359d

Browse files
author
Daniel Todor
committed
Some code refactored
1 parent 3588351 commit 489359d

File tree

5 files changed

+62
-53
lines changed

5 files changed

+62
-53
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
dist
22
rpi_gpio_devices.egg-info
3+
__pycache__
4+
.coverage

src/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .base import BaseDevice, SwitchableDevice, PWMDevice
22
from .button import Button
3-
from .fan import Fan
3+
from .fan import Fan, SM
44
from .led import LED
55
from .pwmled import PWMLED
66
from .switch import Switch

src/base.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
from datetime import datetime
2+
23
import RPi.GPIO as gpio
34

45

5-
class BaseDevice(object):
6+
class BaseDevice:
67
"""
78
Base class for other devices or classes
89
910
-:param gpiomode: Gpio pin numbering mode. "board" or "bcm"
1011
-:param gpiowarnings: Gpio configuration warnings
1112
-:param verbose: Verbose mode
1213
"""
13-
def __init__(self, gpiomode="board", gpiowarnings=False, verbose=False):
14+
def __init__(self, gpiomode='board', gpiowarnings=False, verbose=False):
1415
self.verbose = verbose
1516
# GPIO basic settings
16-
if gpiomode == "board":
17+
if gpiomode == 'board':
1718
gpio.setmode(gpio.BOARD)
18-
elif gpiomode == "bcm":
19+
elif gpiomode == 'bcm':
1920
gpio.setmode(gpio.BCM)
2021
gpio.setwarnings(gpiowarnings)
2122

@@ -26,7 +27,7 @@ def message(self, val):
2627

2728
def cleanup(self):
2829
""" Cleanup GPIOs """
29-
self.message("Cleanup GPIOs")
30+
self.message('Cleanup GPIOs...')
3031
gpio.cleanup()
3132

3233

@@ -55,15 +56,17 @@ def turn_on(self):
5556
""" Turn on the device """
5657
if self.is_on():
5758
return
58-
self.message("Device turned on")
59+
self.message('Device turned on.')
60+
self.turned_on_at = datetime.now()
5961
self.on = True
6062
gpio.output(self.power, 1)
6163

6264
def turn_off(self):
6365
""" Turn off the device """
6466
if self.is_off():
6567
return
66-
self.message("Device turned off")
68+
self.message('Device turned off.')
69+
self.turned_on_at = False
6770
self.on = False
6871
gpio.output(self.power, 0)
6972

@@ -79,19 +82,19 @@ def ontime(self):
7982
if self.is_off():
8083
return 0
8184
difference = datetime.now() - self.turned_on_at
82-
ontime = int(difference.total_seconds() / 60)
83-
self.message("Ontime {} minute(s)".format(ontime))
85+
ontime = int(difference.total_seconds())
86+
self.message(f'Ontime {ontime} seconds.')
8487
return ontime
8588

8689

8790
class PWMDevice(SwitchableDevice):
8891
""" Device that can be controlled with PWM signals
8992
90-
-:param power: Pin used for giving power supply to the fan
9193
-:param pwm: Pin used for pwm control
94+
-:param power: Pin used for giving power supply (optional)
9295
-:param frequency: Frequency used for the pwm signal
9396
"""
94-
def __init__(self, power, pwm, frequency=100, **kwargs):
97+
def __init__(self, pwm, power=False, frequency=100, **kwargs):
9598
super().__init__(power, **kwargs)
9699
self.frequency = frequency
97100
self.duty_cycle = 0 # Duty cycle between 0.0 and 100.0
@@ -104,7 +107,7 @@ def turn_on(self):
104107
""" Turn on the power and pwm on the lowest setting """
105108
if self.is_on():
106109
return
107-
self.message("Device turned on")
110+
self.message('Device turned on.')
108111
self.duty_cycle = 0
109112
self.turned_on_at = datetime.now()
110113
if self.pwm:
@@ -117,7 +120,7 @@ def turn_off(self):
117120
""" Turn off the power and pwm """
118121
if self.is_off():
119122
return
120-
self.message("Device turned off")
123+
self.message('Device turned off.')
121124
self.duty_cycle = 0
122125
self.turned_on_at = False
123126
if self.pwm:
@@ -133,14 +136,14 @@ def set_duty_cycle(self, percent, z_off=True):
133136
-:param z_off: Turn off at zero
134137
"""
135138
if not self.pwm:
136-
raise ValueError("No pin provided for outputting PWM signal")
139+
raise ValueError('No pin provided for outputting PWM signal!')
137140
if z_off and percent == 0:
138141
self.turn_off()
139142
return
140143
if self.is_off():
141144
self.turn_on()
142145
elif self.duty_cycle == percent:
143146
return
144-
self.message("Duty cycle set to {}%".format(percent))
147+
self.message(f'Duty cycle set to {percent}%.')
145148
self.duty_cycle = percent
146149
self.pwm.ChangeDutyCycle(percent)

src/button.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import time
2+
23
import RPi.GPIO as gpio
4+
35
from .base import BaseDevice
46

57

@@ -10,24 +12,20 @@ class Button(BaseDevice):
1012
-:param debounce_time: Debounce time when checking button state (ms)
1113
-:param polarity: When you press the button, will the pin be connected to LOW or HIGH?
1214
"""
13-
def __init__(self, pin, debounce_time=200, polarity='LOW', **kwargs):
15+
def __init__(self, pin, debounce_time=200, polarity=gpio.LOW, **kwargs):
1416
super().__init__(**kwargs)
1517
self.debounce_time = debounce_time / 1000
1618
self.polarity = polarity
1719
self.pin = pin
18-
gpio.setup(pin, gpio.IN, pull_up_down={'HIGH': gpio.PUD_DOWN, 'LOW': gpio.PUD_UP}[polarity])
20+
gpio.setup(pin, gpio.IN, pull_up_down={'0': gpio.PUD_UP, '1': gpio.PUD_DOWN}[str(polarity)])
1921

2022
def is_pressed(self):
2123
""" True if the button is pressed """
22-
def HIGH(pin):
23-
if gpio.input(pin):
24-
return True
25-
return False
2624
def LOW(pin):
27-
if not gpio.input(pin):
28-
return True
29-
return False
30-
test = {'HIGH': HIGH, 'LOW': LOW}[self.polarity]
25+
return bool(not gpio.input(pin))
26+
def HIGH(pin):
27+
return bool(gpio.input(pin))
28+
test = {'0': LOW, '1': HIGH}[str(self.polarity)]
3129

3230
if test(self.pin):
3331
time.sleep(self.debounce_time)

src/fan.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
from time import sleep
22
from datetime import datetime
33
import subprocess
4+
from enum import IntEnum
5+
46
import RPi.GPIO as gpio
7+
58
from .base import PWMDevice
69

710

11+
class SM(IntEnum):
12+
""" Variable positions inside speed_mapping """
13+
TEMP = 0
14+
SPEED = 1
15+
16+
817
class Fan(PWMDevice):
918
""" Class for controlling a fan. You can use any fan that has at least 2 wires.
1019
If your fan has 3-4 wires (sense and PWM) you can control it with PWM signal,
@@ -16,23 +25,23 @@ class Fan(PWMDevice):
1625
-:param pwm: Pin used for PWM control
1726
-:param cycletime: Cycle time of auto fan control (s)
1827
-:param frequency: Frequency used for the pwm signal (hz)
19-
-:param idle_limit: If fan is on automode, and the temp is below min, only turn off after this limit is reached (m)
20-
-:param speed_map: This is used to match temperature readings to fan speed
28+
-:param idle_limit: If fan is on automode, and the temp is below min, only turn off after this limit is reached (s)
29+
-:param speed_mapping: This is used to match temperature readings to fan speed
2130
Values are series of tuples in form of: (from_temp, duty_cycle)
2231
-:param rpm_measurement_timeout: Timeout for the edge detection (ms)
2332
-:param rpm_measurement_edges: How many edges to record for the calculation
2433
-:param rpm_measurement_bouncetime: After an edge detection, the next edge will be ignored for this duration (ms)
2534
This value is in miliseconds, and should be less than the time for half revolution on maximum speed.
2635
Calculation: rev_per_sec = max_rpm / 60 --> 1000 / rev_per_sec / 2 = max_bouncetime
2736
"""
28-
def __init__(self, power=False, sense=False, pwm=False, cycletime=5, frequency=25000, idle_limit=5,
29-
speed_map=False, rpm_measurement_timeout=2000, rpm_measurement_edges=40,
37+
def __init__(self, power=False, sense=False, pwm=False, cycletime=5, frequency=25000, idle_limit=300,
38+
speed_mapping=False, rpm_measurement_timeout=2000, rpm_measurement_edges=40,
3039
rpm_measurement_bouncetime=10, **kwargs):
31-
super().__init__(power, pwm, frequency, **kwargs)
40+
super().__init__(pwm, power, frequency, **kwargs)
3241
self.idle_limit = idle_limit
33-
self.speed_map = speed_map or [
34-
(60, 1),
35-
(65, 30),
42+
self.speed_mapping = speed_mapping or [
43+
(20, 1),
44+
(40, 30),
3645
(70, 50),
3746
(75, 70),
3847
(80, 100)
@@ -46,7 +55,7 @@ def __init__(self, power=False, sense=False, pwm=False, cycletime=5, frequency=2
4655
if sense:
4756
gpio.setup(sense, gpio.IN, pull_up_down=gpio.PUD_UP)
4857
if not power and not pwm:
49-
raise ValueError("No pins provided for controlling the fan!")
58+
raise ValueError('No pins provided for controlling the fan!')
5059

5160
def set_speed(self, percent):
5261
""" Set fan speed.
@@ -63,38 +72,39 @@ def set_speed(self, percent):
6372

6473
def smart_set_speed(self, percent):
6574
""" Set fan speed with respect to other parameters """
66-
# If the desired speed is 0, but the fan not reached the idle limit, do nothing
75+
# If the desired speed is 0, but the fan not reached the idle limit, set idle speed
6776
if percent == 0 and self.is_on() and self.ontime() < self.idle_limit:
68-
return
77+
percent = 1
6978
self.set_speed(percent)
7079

7180
def read_hw_temperature(self):
7281
""" Read hardware temperatures """
73-
cpu_temp = round(int(subprocess.check_output(["cat", "/sys/class/thermal/thermal_zone0/temp"]).strip()) / 1000)
74-
self.message("Temperature reading {}c".format(cpu_temp))
82+
cpu_temp = round(int(subprocess.check_output(['cat', '/sys/class/thermal/thermal_zone0/temp']).strip()) / 1000)
83+
self.message(f'Temperature reading {cpu_temp}c.')
7584
return cpu_temp
7685

7786
def measure_rpm(self):
7887
""" Measure fan rpm.
7988
8089
Note: Measuring RPM from a script running under an OS (with many other things) can be inaccurate.
90+
You can improve it by fine tune the "rpm_measurement_..." variables for a given fan.
8191
"""
8292
if not self.sense:
83-
raise ValueError("No pin was provided for sensing rpm.")
93+
raise ValueError('No pin was provided for sensing rpm.')
8494
edges = self.rpm_measurement_edges
8595
start_time = datetime.now()
8696
for _ in range(edges):
8797
channel = gpio.wait_for_edge(self.sense, gpio.FALLING,
8898
timeout=self.rpm_measurement_timeout,
8999
bouncetime=self.rpm_measurement_bouncetime)
90100
if channel is None:
91-
self.message("RPM measurement timeout")
101+
self.message('RPM measurement timeout.')
92102
edges = 0
93103
break
94104
end_time = datetime.now()
95105
difference = (end_time - start_time).total_seconds()
96106
rpm = int((edges * (60 / difference)) / 2)
97-
self.message("Current rpm: {}".format(rpm))
107+
self.message(f'Current rpm: {rpm}')
98108
return rpm
99109

100110
def temp_to_speed(self, temp):
@@ -103,19 +113,15 @@ def temp_to_speed(self, temp):
103113
-:param temp: Temperature in celsius
104114
"""
105115
# If the temp is below the minimum level, we dont need further processing
106-
if temp < self.speed_map[0][0]:
107-
self.message("Temp below minimum")
116+
if temp < self.speed_mapping[0][SM.TEMP]:
117+
self.message('Temperature below minimum.')
108118
return 0
109119

110-
for i in range(len(self.speed_map)):
111-
current_map = self.speed_map[i]
112-
next_map = self.speed_map[i+1] if len(self.speed_map) > i+1 else (500, 100)
113-
if current_map[0] <= temp < next_map[0]:
114-
return current_map[1]
115-
116-
# If nothing was matched raise error
117-
self.cleanup()
118-
raise ValueError("Matching speed to temperature failed!")
120+
for i in range(len(self.speed_mapping)):
121+
current_mapping = self.speed_mapping[i]
122+
next_mapping = self.speed_mapping[i+1] if len(self.speed_mapping) > i+1 else (500, 100)
123+
if current_mapping[SM.TEMP] <= temp < next_mapping[SM.TEMP]:
124+
return current_mapping[SM.SPEED]
119125

120126
def auto_set(self, temp=False):
121127
""" Set fan speed automatically based on temperature and the speedmap

0 commit comments

Comments
 (0)