Mercurial > templog
comparison py/tempserver.py @ 293:d15dda1b1f76
merge
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Sat, 06 Jul 2019 18:29:45 +0800 |
parents | 26eee8591f61 |
children | 20c89630be6c |
comparison
equal
deleted
inserted
replaced
292:28eb733cb803 | 293:d15dda1b1f76 |
---|---|
3 import sys | 3 import sys |
4 import os | 4 import os |
5 import logging | 5 import logging |
6 import time | 6 import time |
7 import signal | 7 import signal |
8 | 8 import asyncio |
9 import gevent | 9 import argparse |
10 import gevent.monkey | 10 |
11 import lockfile.pidlockfile | 11 import lockfile.pidlockfile |
12 import daemon | 12 import daemon |
13 | 13 |
14 import utils | 14 import utils |
15 from utils import L,D,EX,W | 15 from utils import L,D,EX,W |
16 import fridge | 16 import fridge |
17 import config | 17 import config |
18 import sensor_ds18b20 | 18 import sensor |
19 import params | 19 import params |
20 import uploader | 20 import uploader |
21 import configwaiter | |
21 | 22 |
22 | 23 |
23 class Tempserver(object): | 24 class Tempserver(object): |
24 def __init__(self): | 25 def __init__(self, test_mode): |
25 self.readings = [] | 26 self.readings = [] |
26 self.current = (None, None) | 27 self.current = (None, None) |
27 self.fridge = None | 28 self.fridge = None |
28 self._wakeup = gevent.event.Event() | 29 self._wakeup = asyncio.Event() |
29 | 30 self._test_mode = test_mode |
30 # don't patch os, fork() is used by daemonize | |
31 gevent.monkey.patch_all(os=False, thread=False) | |
32 | 31 |
33 def __enter__(self): | 32 def __enter__(self): |
34 self.params = params.Params() | 33 self.params = params.Params() |
35 self.fridge = fridge.Fridge(self) | 34 self.fridge = fridge.Fridge(self) |
36 self.uploader = uploader.Uploader(self) | 35 self.uploader = uploader.Uploader(self) |
36 self.configwaiter = configwaiter.ConfigWaiter(self) | |
37 self.params.load() | 37 self.params.load() |
38 self.set_sensors(sensor_ds18b20.DS18B20s(self)) | 38 self.set_sensors(sensor.make_sensor(self)) |
39 gevent.signal(signal.SIGHUP, self._reload_signal) | 39 asyncio.get_event_loop().add_signal_handler(signal.SIGHUP, self.reload_signal) |
40 return self | 40 return self |
41 | 41 |
42 def __exit__(self, exc_type, exc_value, traceback): | 42 def __exit__(self, exc_type, exc_value, traceback): |
43 L("Exiting, cleanup handler"); | 43 L("Exiting, cleanup handler"); |
44 self.fridge.off() | 44 self.fridge.off() |
48 if self.fridge is None: | 48 if self.fridge is None: |
49 raise Exception("Tempserver.run() must be within 'with Tempserver() as server'") | 49 raise Exception("Tempserver.run() must be within 'with Tempserver() as server'") |
50 | 50 |
51 # XXX do these go here or in __enter_() ? | 51 # XXX do these go here or in __enter_() ? |
52 self.start_time = self.now() | 52 self.start_time = self.now() |
53 self.fridge.start() | 53 runloops = [ |
54 self.sensors.start() | 54 self.fridge.run(), |
55 self.uploader.start() | 55 self.sensors.run(), |
56 | 56 self.uploader.run(), |
57 # won't return. | 57 self.configwaiter.run(), |
58 while True: | 58 ] |
59 try: | 59 |
60 gevent.sleep(60) | 60 loop = asyncio.get_event_loop() |
61 except KeyboardInterrupt: | 61 try: |
62 break | 62 loop.run_until_complete(asyncio.gather(*runloops)) |
63 except KeyboardInterrupt: | |
64 print('\nctrl-c') | |
65 finally: | |
66 # loop.close() seems necessary otherwise get warnings about signal handlers | |
67 loop.close() | |
63 | 68 |
64 def now(self): | 69 def now(self): |
65 return utils.monotonic_time() | 70 return utils.monotonic_time() |
66 | 71 |
67 def set_sensors(self, sensors): | 72 def set_sensors(self, sensors): |
91 if len(self.readings) > config.MAX_READINGS: | 96 if len(self.readings) > config.MAX_READINGS: |
92 self.readings = self.readings[-config.MAX_READINGS:] | 97 self.readings = self.readings[-config.MAX_READINGS:] |
93 | 98 |
94 def current_temps(self): | 99 def current_temps(self): |
95 """ returns (wort_temp, fridge_temp) tuple """ | 100 """ returns (wort_temp, fridge_temp) tuple """ |
101 D("current: %s" % str(self.current)) | |
96 return self.current | 102 return self.current |
97 | 103 |
104 @asyncio.coroutine | |
98 def sleep(self, timeout): | 105 def sleep(self, timeout): |
99 """ sleeps for timeout seconds, though wakes if the server's config is updated """ | 106 """ sleeps for timeout seconds, though wakes if the server's config is updated """ |
100 self._wakeup.wait(timeout) | 107 # XXX fixme - we should wake on _wakeup but asyncio Condition with wait_for is a bit broken? |
101 | 108 # https://groups.google.com/forum/#!topic/python-tulip/eSm7rZAe9LM |
102 def _reload_signal(self): | 109 # For now we just sleep, ignore the _wakeup |
103 try: | 110 try: |
104 self.params.load() | 111 yield from asyncio.wait_for(self._wakeup.wait(), timeout=timeout) |
105 L("Reloaded.") | 112 except asyncio.TimeoutError: |
113 pass | |
114 | |
115 def reload_signal(self, no_file = False): | |
116 try: | |
117 if not no_file: | |
118 self.params.load() | |
119 L("Reloaded.") | |
106 self._wakeup.set() | 120 self._wakeup.set() |
107 self._wakeup.clear() | 121 self._wakeup.clear() |
108 except self.Error, e: | 122 except Error as e: |
109 W("Problem reloading: %s" % str(e)) | 123 W("Problem reloading: %s" % str(e)) |
110 | 124 |
111 def setup_logging(): | 125 def test_mode(self): |
126 return self._test_mode | |
127 | |
128 def setup_logging(debug = False): | |
129 level = logging.INFO | |
130 if debug: | |
131 level = logging.DEBUG | |
112 logging.basicConfig(format='%(asctime)s %(message)s', | 132 logging.basicConfig(format='%(asctime)s %(message)s', |
113 datefmt='%m/%d/%Y %I:%M:%S %p', | 133 datefmt='%m/%d/%Y %I:%M:%S %p', |
114 level=logging.INFO) | 134 level=level) |
115 | 135 #logging.getLogger("asyncio").setLevel(logging.DEBUG) |
116 def start(): | 136 |
117 with Tempserver() as server: | 137 def start(test_mode): |
138 with Tempserver(test_mode) as server: | |
118 server.run() | 139 server.run() |
119 | 140 |
120 def main(): | 141 def main(): |
121 setup_logging() | 142 parser = argparse.ArgumentParser() |
143 parser.add_argument('--hup', action='store_true') | |
144 parser.add_argument('--new', action='store_true') | |
145 parser.add_argument('-D', '--daemon', action='store_true') | |
146 parser.add_argument('-d', '--debug', action='store_true') | |
147 parser.add_argument('-t', '--test', action='store_true') | |
148 args = parser.parse_args() | |
149 | |
150 setup_logging(args.debug) | |
122 | 151 |
123 heredir = os.path.abspath(os.path.dirname(__file__)) | 152 heredir = os.path.abspath(os.path.dirname(__file__)) |
124 pidpath = os.path.join(heredir, 'tempserver.pid') | 153 pidpath = os.path.join(heredir, 'tempserver.pid') |
125 pidf = lockfile.pidlockfile.PIDLockFile(pidpath, threaded=False) | 154 pidf = lockfile.pidlockfile.PIDLockFile(pidpath, threaded=False) |
126 do_hup = '--hup' in sys.argv | 155 |
156 | |
127 try: | 157 try: |
128 pidf.acquire(1) | 158 pidf.acquire(1) |
129 pidf.release() | 159 pidf.release() |
130 except (lockfile.AlreadyLocked, lockfile.LockTimeout), e: | 160 except (lockfile.AlreadyLocked, lockfile.LockTimeout) as e: |
131 pid = pidf.read_pid() | 161 pid = pidf.read_pid() |
132 if do_hup: | 162 if args.hup: |
133 try: | 163 try: |
134 os.kill(pid, signal.SIGHUP) | 164 os.kill(pid, signal.SIGHUP) |
135 print>>sys.stderr, "Sent SIGHUP to process %d" % pid | 165 print("Sent SIGHUP to process %d" % pid, file=sys.stderr) |
136 sys.exit(0) | 166 sys.exit(0) |
137 except OSError: | 167 except OSError: |
138 print>>sys.stderr, "Process %d isn't running?" % pid | 168 print("Process %d isn't running?" % pid, file=sys.stderr) |
139 sys.exit(1) | 169 sys.exit(1) |
140 | 170 |
141 print>>sys.stderr, "Locked by PID %d" % pid | 171 print("Locked by PID %d" % pid, file=sys.stderr) |
142 | 172 |
143 stale = False | 173 stale = False |
144 if pid > 0: | 174 if pid > 0: |
145 if '--new' in sys.argv: | 175 if args.new: |
146 try: | 176 try: |
147 os.kill(pid, 0) | 177 os.kill(pid, 0) |
148 except OSError: | 178 except OSError: |
149 stale = True | 179 stale = True |
150 | 180 |
151 if not stale: | 181 if not stale: |
152 print>>sys.stderr, "Stopping old tempserver pid %d" % pid | 182 print("Stopping old tempserver pid %d" % pid, file=sys.stderr) |
153 os.kill(pid, signal.SIGTERM) | 183 os.kill(pid, signal.SIGTERM) |
154 time.sleep(2) | 184 time.sleep(2) |
155 pidf.acquire(0) | 185 pidf.acquire(0) |
156 pidf.release() | 186 pidf.release() |
157 else: | 187 else: |
162 except OSError: | 192 except OSError: |
163 stale = True | 193 stale = True |
164 | 194 |
165 if stale: | 195 if stale: |
166 # isn't still running, steal the lock | 196 # isn't still running, steal the lock |
167 print>>sys.stderr, "Unlinking stale lockfile %s for pid %d" % (pidpath, pid) | 197 print("Unlinking stale lockfile %s for pid %d" % (pidpath, pid), file=sys.stderr) |
168 pidf.break_lock() | 198 pidf.break_lock() |
169 | 199 |
170 if do_hup: | 200 if args.hup: |
171 print>>sys.stderr, "Doesn't seem to be running" | 201 print("Doesn't seem to be running", file=sys.stderr) |
172 sys.exit(1) | 202 sys.exit(1) |
173 | 203 |
174 if '--daemon' in sys.argv: | 204 if args.daemon: |
175 logpath = os.path.join(os.path.dirname(__file__), 'tempserver.log') | 205 logpath = os.path.join(os.path.dirname(__file__), 'tempserver.log') |
176 logf = open(logpath, 'a+') | 206 logf = open(logpath, 'a+') |
177 with daemon.DaemonContext(pidfile=pidf, stdout=logf, stderr = logf): | 207 with daemon.DaemonContext(pidfile=pidf, stdout=logf, stderr = logf): |
178 start() | 208 start(args.test) |
179 else: | 209 else: |
180 with pidf: | 210 with pidf: |
181 start() | 211 start(args.test) |
182 | 212 |
183 if __name__ == '__main__': | 213 if __name__ == '__main__': |
184 main() | 214 main() |