Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
aIexus committed Nov 4, 2022
0 parents commit 65c24d3
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 0 deletions.
163 changes: 163 additions & 0 deletions fan_chassis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/usr/bin/python3

### IMPORTANT ###
# you must enable pwm chanels in /boot/config.txt
# see https://github.com/Pioreactor/rpi_hardware_pwm

# python modules
from rpi_hardware_pwm import HardwarePWM
import time
import signal
import sys
import os
import sys, getopt
import json

# Configuration
PWM_CHANNEL = 0 # PWM channel used to drive PWM fan (gpio18 = channel 0)
WAIT_TIME = 5 # [s] Time to wait between each refresh
PWM_FREQ = 65 # [Hz] 10kHz for Noctua PWM control

# Configurable temperature and fan speed
MIN_TEMP = 55
MAX_TEMP = 75
FAN_LOW = 20
FAN_HIGH = 100
FAN_OFF = 0
FAN_MAX = 100

# logging and metrics (enable = 1)
VERBOSE = 0
NODE_EXPORTER = 0
JSON_EXPORTER = 0

# parse input arguments
try:
opts, args = getopt.getopt(sys.argv[1:],"hv",["min-temp=","max-temp=","fan-low=","fan-high=","wait-time=","help","pwm-channel=","pwm-freq=","verbose","node-exporter","json-exporter"])
except getopt.GetoptError:
print('fan.py [--min-temp=40] [--max-temp=70] [--fan-low=20] [--fan-high=100] [--wait-time=1] [--pwm-channel=0] [--pwm-freq=10000] [--node-exporter] [--json-exporter] [-v|--verbose] [-h|--help]')
sys.exit(2)
for opt, arg in opts:
if opt in ("-h", "--help"):
print('fan.py [--min-temp=40] [--max-temp=70] [--fan-low=20] [--fan-high=100] [--wait-time=1] [--pwm-channel=0] [--pwm-freq=10000] [--node-exporter] [--json-exporter] [-v|--verbose] [-h|--help]')
sys.exit()
elif opt in ("-v", "--verbose"):
VERBOSE = 1
elif opt in ("--min-temp"):
MIN_TEMP = int(arg)
elif opt in ("--max-temp"):
MAX_TEMP = int(arg)
elif opt in ("--fan-low"):
FAN_LOW = int(arg)
elif opt in ("--fan-high"):
FAN_HIGH = int(arg)
elif opt in ("--wait-time"):
WAIT_TIME = int(arg)
elif opt in ("--pwm-channel"):
PWM_CHANNEL = int(arg)
elif opt in ("--pwm-freq"):
PWM_FREQ = int(arg)
elif opt in ("--node-exporter"):
NODE_EXPORTER = 1
elif opt in ("--json-exporter"):
JSON_EXPORTER = 1
print("")
print("MIN_TEMP:",MIN_TEMP)
print("MAX_TEMP:",MAX_TEMP)
print("FAN_LOW:",FAN_LOW)
print("FAN_HIGH:",FAN_HIGH)
print("WAIT_TIME:",WAIT_TIME)
print("PWM_CHANNEL:",PWM_CHANNEL)
print("PWM_FREQ:",PWM_FREQ)
print("VERBOSE:",VERBOSE)
print("NODE_EXPORTER:",NODE_EXPORTER)
print("JSON_EXPORTER:",JSON_EXPORTER)
print("")

# Get CPU's temperature
def getCpuTemperature():
res = os.popen('cat /sys/devices/virtual/thermal/thermal_zone0/temp').readline()
temp = (float(res)/1000)
return temp

# Set fan speed
def setFanSpeed(speed,temp):
pwm.change_duty_cycle(speed)

# print fan speed and temperature
if VERBOSE == 1:
print("fan speed: ",int(speed)," temp: ",temp)

# write fan metrics to file for node-exporter/prometheus
if NODE_EXPORTER == 1:
# Save a reference to the original standard output
original_stdout = sys.stdout
with open('/var/lib/node_exporter/fan-metrics.prom', 'w') as f:
# Change the standard output to the file we created.
sys.stdout = f
print('raspberry_fan_speed ',speed)
print('raspberry_fan_temp ',temp)
print('raspberry_fan_min_temp ',MIN_TEMP)
print('raspberry_fan_max_temp ',MAX_TEMP)
print('raspberry_fan_fan_low ',FAN_LOW)
print('raspberry_fan_fan_high ',FAN_HIGH)
print('raspberry_fan_wait_time ',WAIT_TIME)
print('raspberry_fan_pwm_channel ',PWM_CHANNEL)
print('raspberry_fan_freq ',PWM_FREQ)
# Reset the standard output to its original value
sys.stdout = original_stdout
f.close()
# write fan metrics to json file for Home Assistant
if JSON_EXPORTER == 1:
dFan = {
'fan_speed': speed,
'cpu_temp': temp,
'cpu_min_temp': MIN_TEMP,
'cpu_max_temp': MAX_TEMP,
'fan_speed_low': FAN_LOW,
'fan_speed_high': FAN_HIGH,
'scan_time': WAIT_TIME,
'pwm_channel': PWM_CHANNEL,
'fan_frequency': PWM_FREQ
}

jsonString = json.dumps(dFan)
jsonFile = open('/usr/share/hassio/homeassistant/files/chassis_fan.json','w')
jsonFile.write(jsonString)
jsonFile.close()
return()

# Handle fan speed
def handleFanSpeed():
temp = getCpuTemperature()

# Turn off the fan if temperature is below MIN_TEMP
if temp < MIN_TEMP:
setFanSpeed(FAN_OFF,temp)

# Set fan speed to MAXIMUM if the temperature is above MAX_TEMP
elif temp > MAX_TEMP:
setFanSpeed(FAN_MAX,temp)

# Caculate dynamic fan speed
else:
step = (FAN_HIGH - FAN_LOW)/(MAX_TEMP - MIN_TEMP)
delta = temp - MIN_TEMP
speed = FAN_LOW + ( round(delta) * step )
setFanSpeed(speed,temp)

return ()

try:
# Setup hardware PWM
pwm = HardwarePWM(pwm_channel=PWM_CHANNEL, hz=60)
pwm.change_frequency(PWM_FREQ)
pwm.start(FAN_LOW) # full duty cycle

# Handle fan speed every WAIT_TIME sec
while True:
handleFanSpeed()
time.sleep(WAIT_TIME)

except KeyboardInterrupt: # trap a CTRL+C keyboard interrupt
setFanSpeed(FAN_OFF,MIN_TEMP)
11 changes: 11 additions & 0 deletions test/x708_ac_power.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(6, GPIO.IN)

if GPIO.input(6): # if port 6 == 1
print ("1")
else: # if port 6 != 1
print ("2")

12 changes: 12 additions & 0 deletions test/x708bat_capacity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/python3

import struct
import smbus as smbus
import sys

bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
address = 0x36
read = bus.read_word_data(address, 4)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
print(round(swapped / 256))

25 changes: 25 additions & 0 deletions test/x708bat_short.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python

import struct
import smbus
import sys

bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)

address = 0x36
read = bus.read_word_data(address, 2)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]

voltage = swapped * 1.25 /1000/16


address = 0x36
read = bus.read_word_data(address, 4)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]

capacity = swapped/256

print ("******************")
print ("Voltage:%5.2fV" % voltage)
print ("Battery:%5i%%" % capacity)
print ("******************")
12 changes: 12 additions & 0 deletions test/x708bat_voltage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/python3

import struct
import smbus as smbus
import sys

bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
address = 0x36
read = bus.read_word_data(address, 2)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
print(round(swapped * 1.25 /1000/16,2))

11 changes: 11 additions & 0 deletions test/x708pld_short.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(6, GPIO.IN)

if GPIO.input(6): # if port 6 == 1
print ("---AC Power Loss OR Power Adapter Failure---")
else: # if port 6 != 1
print ("---AC Power OK,Power Adapter OK---")

60 changes: 60 additions & 0 deletions x708battery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python
import struct
import smbus
import sys
import time
import json
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(13, GPIO.OUT)
GPIO.setwarnings(False)

def readVoltage(bus):
address = 0x36
read = bus.read_word_data(address, 2)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
voltage = round(swapped * 1.25 /1000/16,2)
return voltage

def readCapacity(bus):
address = 0x36
read = bus.read_word_data(address, 4)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
capacity = round(swapped/256)
return capacity

bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)

dBattery = {
'voltage': 0,
'capacity': 0
}

WAIT_TIME = 2 # [s] Sleep interval
BAT_LOW_VOLTAGE = 3.40 # [V] Low Level Battery Voltage value

while True:

nVoltage = readVoltage(bus)
nCapacity = readCapacity(bus)

# If values are changed - save to json file
if (dBattery['voltage'] != nVoltage or dBattery['capacity'] != nCapacity):
dBattery['voltage'] = nVoltage
dBattery['capacity'] = nCapacity
jsonString = json.dumps(dBattery)
jsonFile = open('/usr/share/hassio/homeassistant/files/ups_battery.json','w')
jsonFile.write(jsonString)
jsonFile.close()

# If nVoltage is less than BAT_LOW_VOLTAGE - SHUTDOWN
if nVoltage < BAT_LOW_VOLTAGE:
print("!!! Battery LOW !!!")
print("!!! Shutdown in 10 seconds !!!")
time.sleep(10)
GPIO.output(13, GPIO.HIGH)
time.sleep(3)
GPIO.output(13, GPIO.LOW)

time.sleep(WAIT_TIME)
70 changes: 70 additions & 0 deletions x708fan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env python3

import subprocess
import time
import json
from gpiozero import OutputDevice


ON_THRESHOLD = 60 # (degrees Celsius) Fan running at high speed at this temperature.
OFF_THRESHOLD = 50 # (degress Celsius) Fan running at low speed at this temperature.
SLEEP_INTERVAL = 5 # (seconds) How often we check the core temperature.
GPIO_PIN = 16 # Which GPIO pin you're using to control the fan.


def get_temp():
"""Get the core temperature.
Run a shell script to get the core temp and parse the output.
Raises:
RuntimeError: if response cannot be parsed.
Returns:
float: The core temperature in degrees Celsius.
"""
output = subprocess.run(['vcgencmd', 'measure_temp'], capture_output=True)
temp_str = output.stdout.decode()
try:
return float(temp_str.split('=')[1].split('\'')[0])
except (IndexError, ValueError):
raise RuntimeError('Could not parse temperature output.')

if __name__ == '__main__':
# Validate the on and off thresholds
if OFF_THRESHOLD >= ON_THRESHOLD:
raise RuntimeError('OFF_THRESHOLD must be less than ON_THRESHOLD')

fan = OutputDevice(GPIO_PIN)
temp = get_temp()

dFan = {
'fan_speed': fan.value,
'cpu_temp': temp,
'on_threshold': ON_THRESHOLD,
'off_threshold': OFF_THRESHOLD,
'sleep_interval': SLEEP_INTERVAL
}

while True:
temp = get_temp()

# Fan running at high speed fan if the temperature has reached the limit and the fan
# isn't already running.
# NOTE: `fan.value` returns 1 for "on" and 0 for "off"
if temp > ON_THRESHOLD and not fan.value:
fan.on()

# Run running at low speed if the fan is running and the temperature has dropped
# to 10 degrees below the limit.
elif fan.value and temp < OFF_THRESHOLD:
fan.off()

# Save changed values into json file
if (dFan['fan_speed'] != fan.value or dFan['cpu_temp'] != temp):
dFan['fan_speed'] = fan.value
dFan['cpu_temp'] = temp

jsonString = json.dumps(dFan)
jsonFile = open('/usr/share/hassio/homeassistant/files/ups_fan.json','w')
jsonFile.write(jsonString)
jsonFile.close()

time.sleep(SLEEP_INTERVAL)
Loading

0 comments on commit 65c24d3

Please sign in to comment.