Commit cfc6c377 authored by Gary E. Miller's avatar Gary E. Miller

ntpheatusb: Heat up an NTP server with a USB relay.

parent 914fde0c
......@@ -2,9 +2,17 @@ The following tools are not production-ready. They are included only as
conveniences, examples or rudimentary starting points for other development
efforts.
cpu-temp-log, smartctl-temp-log, temper-temp-log and zone-temp-log
have been replaced by ntplogtemp. Those programs will stay in contrib for
a while to give users a chance to migrate.
ntpheat is a program to exercise a CPU until the CPU reachs a certain
temperature. The idea if to get the temperature around the system
oscillator to be near the zero TC point. Tested on RasPi wrapped
in bubble wrap. Formerly known as makeheat.
ntpheatusb is a program to control an external USB relayto maintain
a stable temperature. See the blog post "More Heat" for details.
The next 4 tools cpu-temp-log, smartctl-temp-log, temper-temp-log and
zone-temp-log have been replaced by ntplogtemp. Those programs will stay
in contrib for a while to give users a chance to migrate.
cpu-temp-log is a tool to use the output of 'sensors -u' and write the
motherboard temperatures to stdout. Useful to create a log that can be used
......@@ -24,8 +32,3 @@ zone-temp-log reads /sys/class/thermal/thermal_zone*/temp to find the CPU
temperature. Writes all temperatures found to stdout on one line, preceded by
the Unix UTC time in seconds. This is useful on any Linux system that
supports the /sys/class/thermal/thermal_zone*/temp interface.
ntpheat is a program to exercise a CPU until the CPU reachs a certain
temperature. The idea if to get the temperature around the system
oscillator to be near the zero TC point. Tested on RasPi wrapped
in bubble wrap. Formerly known as makeheat.
#!/usr/bin/env python
#
# generate some heat!
#
# Wrap your RasPi in a closed box. Get a usbrelay1 to control
# an incadescent light bulb in the box. Heat to 45C. profit.
#
# This code depends on the program 'usbrelay' to manage your usbrelay
# conencted device. Get it here: git@github.com:darrylb123/usbrelay.git
#
# makeheatusb will use a lot less CPU than ntpheat, and more directly
# heats the XTAL rather than the CPU.
#
# Avoid the desire to increase the wait time. The realy clocks twice
# per cycle, and those cycles add up. Minimize wear on your relay.
#
# Try the simple P controller (the defaul) before tyring the PID controller.
# the PID controller may take some fiddling with the constants to get
# it working better than the simple P controller.
import argparse
import subprocess
import sys
import time
try:
import ntp.util
except ImportError as e:
sys.stderr.write("makeheatusb: can't find Python NTP modules "
"-- check PYTHONPATH.\n%s\n" % e)
sys.exit(1)
def run_binary(cmd):
"""\
Run a binary
Return its output if good, None if bad
"""
try:
# sadly subprocess.check_output() is not in Python 2.6
# so use Popen()
# this throws an exception if not found
proc = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True)
output = proc.communicate()[0].split("\n")
if proc.returncode:
# non-zero return code, fail
print("Return %s" % proc.returncode)
return None
except ImportError as e:
sys.stderr.write("Unable to run %s binary\n" % cmd[0])
print(cmd)
sys.stderr.write("%s\n" % e)
return None
return output
class PID:
"""\
Discrete PID control
"""
def __init__(self, setpoint=0.0,
P=2.0, I=0.0, D=1.0,
Derivator=0, Integrator=0,
Integrator_max=100, Integrator_min=-100):
self.Kp = P
self.Ki = I
self.Kd = D
self.Derivator = Derivator
self.Integrator = Integrator
self.Integrator_max = Integrator_max
self.Integrator_min = Integrator_min
self.set_point = setpoint
self.error = 0.0
def __repr__(self):
return ("D_value=%s, I_value=%s" % (self.D_value, self.I_value))
def update(self, current_value):
"""
Calculate PID output value for given reference input and feedback
"""
self.error = self.set_point - current_value
self.P_value = self.Kp * self.error
self.D_value = self.Kd * (self.error - self.Derivator)
self.Derivator = self.error
self.Integrator = self.Integrator + self.error
if self.Integrator > self.Integrator_max:
self.Integrator = self.Integrator_max
elif self.Integrator < self.Integrator_min:
self.Integrator = self.Integrator_min
self.I_value = self.Integrator * self.Ki
PID = self.P_value + self.I_value + self.D_value
return PID
# Work with argvars
parser = argparse.ArgumentParser(description="make heat with USB relay")
parser.add_argument('-p', '--pid',
action="store_true",
dest='pid',
help="Use PID controller instead of simple P controller.")
parser.add_argument('-t', '--temp',
default=[45.0],
dest='target_temp',
help="Temperature to hold in C. Default is 45.0C",
nargs=1,
type=float)
parser.add_argument('-w', '--wait',
default=[60],
dest='wait',
help="Set delay time in seconds, default is 60",
nargs=1,
type=float)
parser.add_argument('-v', '--verbose',
action="store_true",
dest='verbose',
help="be verbose")
parser.add_argument('-V', '--version',
action="version",
version="makeheatusb %s" % ntp.util.stdversion())
args = parser.parse_args()
zone0 = '/sys/class/thermal/thermal_zone0/temp'
cnt = 0
temp_gate = args.target_temp[0]
period = float(args.wait[0])
# you will need to personalize these to your relay ID:
usbrelay_on = ['usbrelay', '959BI_1=1']
usbrelay_off = ['usbrelay', '959BI_1=0']
# to adjsut the PID variables
# set I and D to zero
#
# increase P until you get a small overshoot, and mostly damped response,
# to a large temp change
#
# then increase I until the persistent error goes away.
#
# if the temp oscillates then increase D
#
pid = PID(setpoint=temp_gate, P=35.0, I=10.0, D=5.0)
try:
while True:
# grab the needed output
output = run_binary(["temper-poll", "-c"])
try:
# make sure it is a temperature
temp = float(output[0])
except:
# bad data, exit
# FIXME, should try to recover?
print("temper read failed: %s" % output)
sys.exit(1)
# the +2- is to create and 80/20 band around the setpoint
p_val = pid.update(temp) + 20
p_val1 = p_val
if p_val > 100:
p_val1 = 100
elif p_val < 0:
p_val1 = 0
if temp > temp_gate:
perc_t = 0
elif temp < (temp_gate - 3):
perc_t = 100
else:
perc_t = ((temp_gate - temp) / 3) * 100
if args.pid:
# use PID controller
perc = p_val1
else:
# use P controller
perc = perc_t
if perc > 0:
output = run_binary(usbrelay_on)
time_on = period * (perc / 100)
time.sleep(time_on)
else:
time_on = 0
time_off = period - time_on
output = run_binary(usbrelay_off)
if args.verbose:
print("Temp %s, perc %.2f, p_val %s/%s"
% (temp, perc_t, p_val, p_val1))
print("on %s, off %s" % (time_on, time_off))
print(pid)
if 0 < time_off:
time.sleep(time_off)
except KeyboardInterrupt:
print("\nCaught ^C")
run_binary(usbrelay_off)
sys.exit(1)
# can't fall through ???
except IOError:
# exception catcher
# turn off the heat!
run_binary(usbrelay_off)
print("\nCaught exception, exiting\n")
sys.exit(1)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment