...

понедельник, 8 декабря 2014 г.

Зимовка кактусов с онлайн контролем температуры


#########################################################################################

import io, os, re, traceback
import BaseHTTPServer, urlparse, base64
import dateutil.parser
import matplotlib, numpy
import scipy.interpolate
from matplotlib import pylab
from itertools import groupby
from datetime import datetime, timedelta

HOST = "stepan.local"
PORT = 8080
USERNAME = "cactus"
PASSWORD = "forever"

LOGFILE = "cactuslog.txt"
CMDFILE = "cactuscmd.txt"

FONT = "Arial"
FONT_SIZE = 12

GRAPH_STEP_SEC = 300
STATS_DAYS_NUM = 7
SMOOTH_WINDOW = 3

MAGIC = 10101

# time difference in seconds between real time and log time
LOG_TIME_OFFSET_SEC = 3600

OFF, ON, AUTO = 0, 1, 2

#########################################################################################

class CactusHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
if not self.authorize(): return

url = urlparse.urlparse(self.path)
query = urlparse.parse_qs(url.query)

pending = False
if "mode" in query and "hfrom" in query and "hto" in query:
pending = True
try:
mode = int(query["mode"][0])
heaterFrom = float(query["hfrom"][0])
heaterTo = float(query["hto"][0])
self.update_params(mode, heaterFrom, heaterTo)
except:
traceback.print_exc()

if self.path in [ "/cactus.png", "/favicon.ico" ]:
self.send_image(self.path)
else:
self.send_page(pending)
self.wfile.close()

def authorize(self):
if self.headers.getheader("Authorization") == None:
return self.send_auth()
else:
auth = self.headers.getheader("Authorization")
code = re.match(r"Basic (\S+)", auth)
if not code: return self.send_auth()
data = base64.b64decode(code.groups(0)[0])
code = re.match(r"(.*):(.*)", data)
if not code: return self.send_auth()
user, password = code.groups(0)[0], code.groups(0)[1]
if user != USERNAME or password != PASSWORD:
return self.send_auth()
return True

def send_auth(self):
self.send_response(401)
self.send_header("WWW-Authenticate", "Basic realm=\"Cactus\"")
self.send_header("Content-type", "text/html")
self.end_headers()
self.send_default()
self.wfile.close()
return False

def send_default(self):
self.wfile.write("""




""".format(imageCode = "iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAA" + "AJ0lEQVQIW2NkwA7+M2IR/w8UY0SXAAuCFCNLwAWRJVAEYRIYgiAJALsgBgYb" + "CawOAAAAAElFTkSuQmCC")) def address_string(self): host, port = self.client_address[:2] return host def update_params(self, mode, heaterFrom, heaterTo): if max(mode, heaterFrom, heaterTo) >= MAGIC: print "invalid params values" return fout = open(CMDFILE, "w") fout.write("%d %d %.1f %.1f" % (MAGIC, mode, heaterFrom, heaterTo)) fout.close() def send_image(self, path): filename = os.path.basename(path) name, ext = os.path.splitext(filename) fimage = open(filename) self.send_response(200) format = { ".png" : "png", ".ico" : "x-icon" } self.send_header("Content-type", "image/" + format[ext]) self.send_header("Content-length", os.path.getsize(filename)) self.end_headers() self.wfile.write(fimage.read()) fimage.close() def fix_time(self, X): time = X[0].timetuple() if time.tm_hour == 0 and time.tm_min <= 11: X[0] -= timedelta(seconds = time.tm_min * 60 + time.tm_sec) time = X[-1].timetuple() if time.tm_hour == 23 and time.tm_min >= 49: offset = (60 - time.tm_min - 1) * 60 + (60 - time.tm_sec - 1) X[-1] += timedelta(seconds = offset) def send_page(self, pending): self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() data, flog = [ ], None while not flog: try: flog = open(LOGFILE) except: traceback.print_exc() mode, heater, heaterFrom, heaterTo = AUTO, 0, 5, 10 for s in flog: row = tuple(s.strip().split(",")) offset = timedelta(seconds = LOG_TIME_OFFSET_SEC) date = dateutil.parser.parse(row[0]) + offset temp = float(row[1]) if len(row) == 3: heater = int(row[2]) elif len(row) >= 3: mode, heater = int(row[2]), int(row[3]) heaterFrom, heaterTo = float(row[4]), float(row[5]) data.append((date, temp, heater)) nowDate = datetime.now().date() Yavg = [ [] for foo in numpy.arange(0, 24 * 3600, GRAPH_STEP_SEC) ] matplotlib.rc("font", family = FONT, size = FONT_SIZE) fig = pylab.figure(figsize = (964 / 100.0, 350 / 100.0), dpi = 100) ax = pylab.axes() for date, points in groupby(data, lambda foo: foo[0].date().isoformat()): X, Y, H = zip(*points) deltaDays = (nowDate - X[0].date()).days if deltaDays > STATS_DAYS_NUM: continue if len(X) == 1: continue # convert to same day data alpha = [1.0, 0.5, 0.3, 0][min(3, deltaDays)] X = Xsrc = [ datetime.combine(nowDate, foo.time()) for foo in X ] self.fix_time(X) # resample X and Y P = [ (foo - X[0]).seconds for foo in X ] Q = numpy.arange(0, P[-1], GRAPH_STEP_SEC) X = [ X[0] + timedelta(seconds = int(foo)) for foo in Q ] fresample = scipy.interpolate.interp1d(P, Y) Y = fresample(Q) # smooth Y Y = [ Y[0] ] * SMOOTH_WINDOW + list(Y) + [ Y[-1] ] * SMOOTH_WINDOW window = numpy.ones(SMOOTH_WINDOW * 2 + 1) / float(SMOOTH_WINDOW * 2 + 1) Y = numpy.convolve(Y, window, 'same') Y = Y[SMOOTH_WINDOW:-SMOOTH_WINDOW] fresample = scipy.interpolate.interp1d(Q, Y) # gather points for stats curve for i in range(len(Q)): Yavg[i].append(Y[i]) # plot stats curve if deltaDays == 3: self.fix_time(X) Ymin = [ min(foo or [0]) for foo in Yavg ][:len(X)] Ymax = [ max(foo or [0]) for foo in Yavg ][:len(X)] pylab.fill(X + list(reversed(X)), Ymax + list(reversed(Ymin)), color = "blue", alpha = 0.10) if alpha == 0: continue pylab.plot(X, Y, linewidth = 2, color = "blue", alpha = alpha) # draw heater for heater, points in groupby(zip(Xsrc, H), lambda foo: foo[1] != 0): XX, H = zip(*points) if heater: p = (XX[0] - X[0]).seconds x1, y1 = XX[0], fresample(p) if (XX[-1] - XX[0]).seconds > 600: x2 = XX[0] + timedelta(seconds = 600) y2 = fresample(p + 600) arrow = dict(facecolor = "red", width = 2, headwidth = 6, frac = 0.40, alpha = alpha, edgecolor = "red") ax.annotate("", xy = (x2, y2), xytext = (x1, y1), arrowprops = arrow) else: ax.plot(x1, y1, "ro", markersize = 4, mec = "red", alpha = alpha) ax.xaxis_date() ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M")) ax.xaxis.set_major_locator(matplotlib.dates.HourLocator()) ax.xaxis.grid(True, "major") ax.yaxis.grid(True, "major") ax.tick_params(axis = "both", which = "major", direction = "out", labelright = True) ax.tick_params(axis = "x", which = "major", labelsize = 8) ax.grid(which = "major", alpha = 1.0) fig.autofmt_xdate() pylab.tight_layout() image = io.BytesIO() pylab.savefig(image, format = "png") pylab.clf() image.seek(0) graph = "" % \ base64.b64encode(image.getvalue()) image.close() pending = pending or os.path.isfile(CMDFILE) self.wfile.write(re.sub(r"{\s", r"{{ ", re.sub(r"\s}", r" }}", """

Cactus Tracker


{graph}



Heater:



heat from to °C



""")).format( font = FONT, fontSize = FONT_SIZE, graph = graph, mode = mode, heaterFrom = heaterFrom, heaterTo = heaterTo, modeOff = (mode == OFF) and "selected" or "", modeOn = (mode == ON) and "selected" or "", modeAuto = (mode == AUTO) and "selected" or "", pending = pending and "20" or "1200", disabled = pending and "disabled=true" or "", transparent = pending and "pending" or "", heaterAuto = (mode != AUTO) and "hidden" or "")) ######################################################################################### server = BaseHTTPServer.HTTPServer((HOST, PORT), CactusHandler) server.serve_forever() #########################################################################################

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.



Комментариев нет:

Отправить комментарий