view py/tempserver.py @ 230:185621f47040

run 2to3
author Matt Johnston <matt@ucc.asn.au>
date Fri, 10 Apr 2015 23:59:16 +0800
parents 99255c501e02
children e39ed85d87a5
line wrap: on
line source

#!/home/matt/templog/venv/bin/python

import sys
import os
import logging
import time
import signal
import asyncio

import lockfile.pidlockfile
import daemon

import utils
from utils import L,D,EX,W
import fridge
import config
import sensor_ds18b20
import params
import uploader


class Tempserver(object):
    def __init__(self):
        self.readings = []
        self.current = (None, None)
        self.fridge = None
        self._wakeup = asyncio.Event()

    def __enter__(self):
        self.params = params.Params()
        self.fridge = fridge.Fridge(self)
        self.uploader = uploader.Uploader(self)
        self.params.load()
        self.set_sensors(sensor.make_sensor(self))
        asyncio.get_event_loop().add_signal_handler(signal.SIGHUP, self._reload_signal)
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        L("Exiting, cleanup handler");
        self.fridge.off()

    def run(self):

        if self.fridge is None:
            raise Exception("Tempserver.run() must be within 'with Tempserver() as server'")

        # XXX do these go here or in __enter_() ?
        self.start_time = self.now()
        tasks = (
            self.fridge.run(),
            self.sensors.run(),
            self.uploader.run(),
        )
        loop = asyncio.get_event_loop()
        try:
            loop.run_until_complete(asyncio.wait(tasks))
            # not reached
        except KeyboardInterrupt:
            pass

    def now(self):
        return utils.monotonic_time()

    def set_sensors(self, sensors):
        if hasattr(self, 'sensors'):
            self.sensors.kill()
        self.sensors = sensors
        self.wort_name = sensors.wort_name()
        self.fridge_name = sensors.fridge_name()

    def take_readings(self):
        ret = self.readings
        self.readings = []
        return ret

    def pushfront(self, readings):
        """ used if a caller of take_readings() fails """
        self.readings = readings + self.readings

    # a reading is a map of {sensorname: value}. temperatures
    # are float degrees
    def add_reading(self, reading):
        """ adds a reading at the current time """
        D("add_reading(%s)" % str(reading))
        self.readings.append( (reading, self.now()))
        self.current = (reading.get(self.wort_name, None),
                    reading.get(self.fridge_name, None))
        if len(self.readings) > config.MAX_READINGS:
            self.readings = self.readings[-config.MAX_READINGS:]

    def current_temps(self):
        """ returns (wort_temp, fridge_temp) tuple """
        return self.current

    def sleep(self, timeout):
        """ sleeps for timeout seconds, though wakes if the server's config is updated """
        asyncio.wait_for(self._wakeup, timeout=timeout)
        
    def _reload_signal(self):
        try:
            self.params.load()
            L("Reloaded.")
            self._wakeup.set()
            self._wakeup.clear()
        except Error as e:
            W("Problem reloading: %s" % str(e))

def setup_logging():
    logging.basicConfig(format='%(asctime)s %(message)s', 
            datefmt='%m/%d/%Y %I:%M:%S %p',
            level=logging.INFO)

def start():
    with Tempserver() as server:
        server.run()

def main():
    setup_logging()

    heredir = os.path.abspath(os.path.dirname(__file__))
    pidpath = os.path.join(heredir, 'tempserver.pid')
    pidf = lockfile.pidlockfile.PIDLockFile(pidpath, threaded=False)
    do_hup = '--hup' in sys.argv
    try:
        pidf.acquire(1)
        pidf.release()
    except (lockfile.AlreadyLocked, lockfile.LockTimeout) as e:
        pid = pidf.read_pid()
        if do_hup:
            try:
                os.kill(pid, signal.SIGHUP)
                print("Sent SIGHUP to process %d" % pid, file=sys.stderr)
                sys.exit(0)
            except OSError:
                print("Process %d isn't running?" % pid, file=sys.stderr)
                sys.exit(1)

        print("Locked by PID %d" % pid, file=sys.stderr)
    
        stale = False
        if pid > 0:
            if '--new' in sys.argv:
                try:
                    os.kill(pid, 0)
                except OSError:
                    stale = True

                if not stale:
                    print("Stopping old tempserver pid %d" % pid, file=sys.stderr)
                    os.kill(pid, signal.SIGTERM)
                    time.sleep(2)
                    pidf.acquire(0)
                    pidf.release()
            else:
                try:
                    os.kill(pid, 0)
                    # must still be running PID
                    raise e
                except OSError:
                    stale = True

        if stale:
            # isn't still running, steal the lock
            print("Unlinking stale lockfile %s for pid %d" % (pidpath, pid), file=sys.stderr)
            pidf.break_lock()

    if do_hup:
        print("Doesn't seem to be running", file=sys.stderr)
        sys.exit(1)

    if '--daemon' in sys.argv:
        logpath = os.path.join(os.path.dirname(__file__), 'tempserver.log')
        logf = open(logpath, 'a+')
        with daemon.DaemonContext(pidfile=pidf, stdout=logf, stderr = logf):
            start()
    else:
        with pidf:
            start()

if __name__ == '__main__':
    main()