log: route task/component status output through logging, not print()
AttributeChange (the common base of every ATask and component ABC) now sets self.log = logging.getLogger(type(self).__name__), so components no longer need to hand-type their own name into each message. Wired logging.basicConfig() in server/brewpi.py with a bare "%(name)s: %(message)s" formatter - Tee (see prior commit) still supplies the "<date>T<time>:" prefix, so lines read "<date>T<time>:<component>: <message>" without double-stamping, and third-party loggers (websockets, asyncio) now get the same formatting for free. Converted the print() call sites that were standing in for this in tasks/ and components/ (leaving __main__ demo blocks and explicit debug-dump helpers alone).
This commit is contained in:
@@ -45,7 +45,7 @@ class HeaterHendi(AHeater):
|
|||||||
# tick loop (with self.device.open(): activate(True)/(False)) and
|
# tick loop (with self.device.open(): activate(True)/(False)) and
|
||||||
# from a client's Connect/Disconnect command, neither of which
|
# from a client's Connect/Disconnect command, neither of which
|
||||||
# expect activate() itself to ever raise.
|
# expect activate() itself to ever raise.
|
||||||
print(f"HeaterHendi: comm error in activate({enable}): {e}")
|
self.log.error(f"comm error in activate({enable}): {e}")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
def is_activated(self):
|
def is_activated(self):
|
||||||
@@ -55,7 +55,7 @@ class HeaterHendi(AHeater):
|
|||||||
s = self.hendi.isRemoteEnable()
|
s = self.hendi.isRemoteEnable()
|
||||||
return '1' in s
|
return '1' in s
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"HeaterHendi: comm error in is_activated: {e}")
|
self.log.error(f"comm error in is_activated: {e}")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ class HeaterHendi(AHeater):
|
|||||||
self.hendi.setPowerWatts(power)
|
self.hendi.setPowerWatts(power)
|
||||||
self.power_eff = self.hendi.getPowerWatts() if power > 0 else 0
|
self.power_eff = self.hendi.getPowerWatts() if power > 0 else 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"HeaterHendi: comm error in process: {e}")
|
self.log.error(f"comm error in process: {e}")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
self.power_eff = 0
|
self.power_eff = 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import logging
|
||||||
import time
|
import time
|
||||||
import serial
|
import serial
|
||||||
import sys
|
import sys
|
||||||
@@ -11,6 +12,7 @@ class HendiException(Exception):
|
|||||||
|
|
||||||
class HendiCtrl:
|
class HendiCtrl:
|
||||||
def __init__(self, port, speed, debug=False):
|
def __init__(self, port, speed, debug=False):
|
||||||
|
self.log = logging.getLogger(type(self).__name__)
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.prompt = b':'
|
self.prompt = b':'
|
||||||
self.sw_id = None
|
self.sw_id = None
|
||||||
@@ -48,7 +50,7 @@ class HendiCtrl:
|
|||||||
try:
|
try:
|
||||||
self.ser.open()
|
self.ser.open()
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
print(f"HendiCtrl: connect failed: {e}")
|
self.log.error(f"connect failed: {e}")
|
||||||
return False
|
return False
|
||||||
# The port itself is what "connected" means - a failed version query
|
# The port itself is what "connected" means - a failed version query
|
||||||
# (e.g. the device sitting in its ungraceful-disconnect lockout, see
|
# (e.g. the device sitting in its ungraceful-disconnect lockout, see
|
||||||
@@ -57,9 +59,9 @@ class HendiCtrl:
|
|||||||
try:
|
try:
|
||||||
self.sw_id = self.getSoftwareIdentifier()
|
self.sw_id = self.getSoftwareIdentifier()
|
||||||
self.sw_ver = self.getSoftwareVersion()
|
self.sw_ver = self.getSoftwareVersion()
|
||||||
print(f"{self.sw_id}, F/W-Version: {self.sw_ver}")
|
self.log.info(f"{self.sw_id}, F/W-Version: {self.sw_ver}")
|
||||||
except HendiException:
|
except HendiException:
|
||||||
print("HendiCtrl: Hendi not found")
|
self.log.warning("Hendi not found")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
@@ -91,7 +93,7 @@ class HendiCtrl:
|
|||||||
self.ser.rts = True
|
self.ser.rts = True
|
||||||
|
|
||||||
def firmware_update(self, filename):
|
def firmware_update(self, filename):
|
||||||
print("Start firmware update")
|
self.log.info("Start firmware update")
|
||||||
self.enter_bootloader()
|
self.enter_bootloader()
|
||||||
self.ser.readline()
|
self.ser.readline()
|
||||||
|
|
||||||
@@ -122,7 +124,7 @@ class HendiCtrl:
|
|||||||
self.ser.flushInput()
|
self.ser.flushInput()
|
||||||
data = s.encode()
|
data = s.encode()
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print(f"HendiCtrl: -> {data!r}")
|
self.log.debug(f"-> {data!r}")
|
||||||
self.ser.write(data + b'\r')
|
self.ser.write(data + b'\r')
|
||||||
|
|
||||||
def __read(self):
|
def __read(self):
|
||||||
@@ -132,7 +134,7 @@ class HendiCtrl:
|
|||||||
# Read answer line
|
# Read answer line
|
||||||
answer_raw = self.ser.readline()
|
answer_raw = self.ser.readline()
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print(f"HendiCtrl: <- echo={echo!r} answer={answer_raw!r}")
|
self.log.debug(f"<- echo={echo!r} answer={answer_raw!r}")
|
||||||
answer = answer_raw.decode('utf-8').replace('\r', '').replace('\n', '')
|
answer = answer_raw.decode('utf-8').replace('\r', '').replace('\n', '')
|
||||||
if ':' in answer:
|
if ':' in answer:
|
||||||
result = answer.split(':')
|
result = answer.split(':')
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import time
|
import time
|
||||||
import serial
|
import serial
|
||||||
import enum
|
import enum
|
||||||
@@ -68,6 +69,7 @@ class StatusError(enum.IntFlag):
|
|||||||
|
|
||||||
class Pololu1376:
|
class Pololu1376:
|
||||||
def __init__(self, serial_port):
|
def __init__(self, serial_port):
|
||||||
|
self.log = logging.getLogger(type(self).__name__)
|
||||||
# Deferred-open pyserial idiom: serial_port is constructed with no
|
# Deferred-open pyserial idiom: serial_port is constructed with no
|
||||||
# port set (see components/actor/stirrerpololu1376.py), so it isn't
|
# port set (see components/actor/stirrerpololu1376.py), so it isn't
|
||||||
# actually opened until connect() is called - a missing/unplugged
|
# actually opened until connect() is called - a missing/unplugged
|
||||||
@@ -88,7 +90,7 @@ class Pololu1376:
|
|||||||
self.ser.close()
|
self.ser.close()
|
||||||
self.ser.open()
|
self.ser.open()
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
print(f"Pololu1376: connect failed: {e}")
|
self.log.error(f"connect failed: {e}")
|
||||||
return False
|
return False
|
||||||
# set_output_flow_control() requires the port to already be open -
|
# set_output_flow_control() requires the port to already be open -
|
||||||
# can only happen here, after open() above, not at construction time.
|
# can only happen here, after open() above, not at construction time.
|
||||||
@@ -96,10 +98,10 @@ class Pololu1376:
|
|||||||
try:
|
try:
|
||||||
self.stop()
|
self.stop()
|
||||||
self.firmware_version = self.get_firmware_version()
|
self.firmware_version = self.get_firmware_version()
|
||||||
print("Pololu1376 F/W-Version:", self.firmware_version)
|
self.log.info("F/W-Version: {}".format(self.firmware_version))
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Pololu1376: connect failed: {e}")
|
self.log.error(f"connect failed: {e}")
|
||||||
self.ser.close()
|
self.ser.close()
|
||||||
self.firmware_version = None
|
self.firmware_version = None
|
||||||
return False
|
return False
|
||||||
@@ -142,7 +144,7 @@ class Pololu1376:
|
|||||||
rsp = self.cmd("GO")
|
rsp = self.cmd("GO")
|
||||||
errors = self.get_status_errors()
|
errors = self.get_status_errors()
|
||||||
if errors:
|
if errors:
|
||||||
print(f"Pololu1376: ERROR - still in fail-safe after GO, active errors: {errors!r}")
|
self.log.error(f"still in fail-safe after GO, active errors: {errors!r}")
|
||||||
return rsp
|
return rsp
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class StirrerPololu1376(AStirrer):
|
|||||||
if not self.connected:
|
if not self.connected:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
print("activate {}".format(enable))
|
self.log.debug("activate {}".format(enable))
|
||||||
if enable:
|
if enable:
|
||||||
self.drv.go()
|
self.drv.go()
|
||||||
else:
|
else:
|
||||||
@@ -58,7 +58,7 @@ class StirrerPololu1376(AStirrer):
|
|||||||
# loop (with self.device.open(): activate(True)/(False)) and
|
# loop (with self.device.open(): activate(True)/(False)) and
|
||||||
# from a client's Connect/Disconnect command, neither of which
|
# from a client's Connect/Disconnect command, neither of which
|
||||||
# expect activate() itself to ever raise.
|
# expect activate() itself to ever raise.
|
||||||
print(f"StirrerPololu1376: comm error in activate({enable}): {e}")
|
self.log.error(f"comm error in activate({enable}): {e}")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
def _on_set_speed(self, speed):
|
def _on_set_speed(self, speed):
|
||||||
@@ -66,9 +66,9 @@ class StirrerPololu1376(AStirrer):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.drv.motor_forward(speed)
|
self.drv.motor_forward(speed)
|
||||||
print("Set speed to {} %".format(speed))
|
self.log.debug("Set speed to {} %".format(speed))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"StirrerPololu1376: comm error in _on_set_speed: {e}")
|
self.log.error(f"comm error in _on_set_speed: {e}")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
def _on_process(self):
|
def _on_process(self):
|
||||||
@@ -77,12 +77,12 @@ class StirrerPololu1376(AStirrer):
|
|||||||
try:
|
try:
|
||||||
errors = self.drv.get_status_errors()
|
errors = self.drv.get_status_errors()
|
||||||
if errors:
|
if errors:
|
||||||
print(f"StirrerPololu1376: motor controller reports errors: {errors!r}")
|
self.log.warning(f"motor controller reports errors: {errors!r}")
|
||||||
if StatusError.SAFE_START_VIOLATION in errors:
|
if StatusError.SAFE_START_VIOLATION in errors:
|
||||||
print("Recover after safe start violation!")
|
self.log.warning("Recover after safe start violation!")
|
||||||
self.drv.go()
|
self.drv.go()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"StirrerPololu1376: comm error in _on_process: {e}")
|
self.log.error(f"comm error in _on_process: {e}")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class TempSensor_max31865(ATemperatureSensor):
|
|||||||
# temperature()'s own except handler already retries via
|
# temperature()'s own except handler already retries via
|
||||||
# reopen() on the next tick, same recovery path as a later
|
# reopen() on the next tick, same recovery path as a later
|
||||||
# runtime failure.
|
# runtime failure.
|
||||||
print(f"TempSensor_max31865: could not open SPI at startup: {e}")
|
self.log.error(f"could not open SPI at startup: {e}")
|
||||||
|
|
||||||
def _open_spi(self):
|
def _open_spi(self):
|
||||||
self.spi.open(0, 0)
|
self.spi.open(0, 0)
|
||||||
@@ -56,7 +56,7 @@ class TempSensor_max31865(ATemperatureSensor):
|
|||||||
try:
|
try:
|
||||||
self._open_spi()
|
self._open_spi()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"TempSensor_max31865: reopen failed: {e}")
|
self.log.error(f"reopen failed: {e}")
|
||||||
|
|
||||||
def read_reg(self, addr):
|
def read_reg(self, addr):
|
||||||
reg = self.spi.xfer([addr, 0xFF])
|
reg = self.spi.xfer([addr, 0xFF])
|
||||||
@@ -98,7 +98,7 @@ class TempSensor_max31865(ATemperatureSensor):
|
|||||||
# nothing restarts a dead ATask). reopen() gives the SPI bus a
|
# nothing restarts a dead ATask). reopen() gives the SPI bus a
|
||||||
# chance to recover from a wedged state without a full process/
|
# chance to recover from a wedged state without a full process/
|
||||||
# Pi reboot - see docs/pi_deployment_notes.md.
|
# Pi reboot - see docs/pi_deployment_notes.md.
|
||||||
print(f"TempSensor_max31865: comm error reading sensor, reopening SPI: {e}")
|
self.log.error(f"comm error reading sensor, reopening SPI: {e}")
|
||||||
self.reopen()
|
self.reopen()
|
||||||
return self.temp
|
return self.temp
|
||||||
|
|
||||||
|
|||||||
+22
-14
@@ -2,6 +2,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
@@ -20,20 +21,23 @@ from tasks import TaskManager, TempSensorTask, HeaterTask, PotTask, TcTask, Stir
|
|||||||
|
|
||||||
import argparse as ap
|
import argparse as ap
|
||||||
|
|
||||||
|
log = logging.getLogger('brewpi')
|
||||||
|
|
||||||
|
|
||||||
class Tee:
|
class Tee:
|
||||||
"""Duplicates writes to multiple streams - used to mirror stdout/stderr
|
"""Duplicates writes to multiple streams - used to mirror stdout/stderr
|
||||||
(everything the rest of the codebase already reaches via print()) into
|
into a log file under logs/ as well as the console, without every
|
||||||
a log file under logs/ as well as the console, without having to touch
|
writer needing to know about either.
|
||||||
every print() call site.
|
|
||||||
|
|
||||||
Also stamps every line with a "<date>T<time>:" prefix (existing print()
|
Also stamps every line with a "<date>T<time>:" prefix. Every task/
|
||||||
call sites already start their message with "Component: ..." - see e.g.
|
component logs via logging (see utils/value.py's AttributeChange, which
|
||||||
tasks/stirrer.py - so the combined line reads
|
gives self.log to all of them), configured below with a bare
|
||||||
"<date>T<time>:<component>: <message>"), so log entries can be
|
"%(name)s:%(message)s" formatter - Tee supplies the timestamp instead,
|
||||||
correlated with the JSON sample logs (ServerLogTask/SudLogTask) without
|
so the combined line reads "<date>T<time>:<component>:<message>" (see
|
||||||
relying on journalctl's own timestamps, which aren't present in the
|
logging.basicConfig() call below) without double-stamping. This lets
|
||||||
mirrored log files themselves."""
|
log entries be correlated with the JSON sample logs (ServerLogTask/
|
||||||
|
SudLogTask) without relying on journalctl's own timestamps, which
|
||||||
|
aren't present in the mirrored log files themselves."""
|
||||||
|
|
||||||
def __init__(self, *streams):
|
def __init__(self, *streams):
|
||||||
self.streams = streams
|
self.streams = streams
|
||||||
@@ -92,7 +96,11 @@ if __name__ == '__main__':
|
|||||||
latest_log_file = open(latest_log_path, "w", buffering=1)
|
latest_log_file = open(latest_log_path, "w", buffering=1)
|
||||||
sys.stdout = Tee(sys.stdout, log_file, latest_log_file)
|
sys.stdout = Tee(sys.stdout, log_file, latest_log_file)
|
||||||
sys.stderr = Tee(sys.stderr, log_file, latest_log_file)
|
sys.stderr = Tee(sys.stderr, log_file, latest_log_file)
|
||||||
print("Logging to {}".format(log_path))
|
# No "%(asctime)s" here - Tee (above) already stamps every mirrored
|
||||||
|
# line with "<date>T<time>:", so this only contributes the
|
||||||
|
# "<component>:<message>" part (see Tee's own docstring).
|
||||||
|
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(name)s:%(message)s')
|
||||||
|
log.info("Logging to {}".format(log_path))
|
||||||
|
|
||||||
config = json.load(open(args.config))
|
config = json.load(open(args.config))
|
||||||
dispatcher = MessageDispatcher()
|
dispatcher = MessageDispatcher()
|
||||||
@@ -246,7 +254,7 @@ if __name__ == '__main__':
|
|||||||
http_handler = functools.partial(SimpleHTTPRequestHandler, directory=web_dir)
|
http_handler = functools.partial(SimpleHTTPRequestHandler, directory=web_dir)
|
||||||
http_server = ThreadingHTTPServer(("0.0.0.0", args.http_port), http_handler)
|
http_server = ThreadingHTTPServer(("0.0.0.0", args.http_port), http_handler)
|
||||||
threading.Thread(target=http_server.serve_forever, daemon=True).start()
|
threading.Thread(target=http_server.serve_forever, daemon=True).start()
|
||||||
print("Serving {} on http://0.0.0.0:{}".format(web_dir, args.http_port))
|
log.info("Serving {} on http://0.0.0.0:{}".format(web_dir, args.http_port))
|
||||||
|
|
||||||
# systemd's `stop`/`restart` send SIGTERM, whose default disposition is to
|
# systemd's `stop`/`restart` send SIGTERM, whose default disposition is to
|
||||||
# kill the process immediately - skipping the finally: block below
|
# kill the process immediately - skipping the finally: block below
|
||||||
@@ -259,7 +267,7 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nShutting down...")
|
log.info("Shutting down...")
|
||||||
finally:
|
finally:
|
||||||
# Cancel all tasks so their finally blocks (e.g. HeaterTask's/
|
# Cancel all tasks so their finally blocks (e.g. HeaterTask's/
|
||||||
# StirrerTask's `with device.open()`) get a chance to run before we
|
# StirrerTask's `with device.open()`) get a chance to run before we
|
||||||
@@ -278,4 +286,4 @@ if __name__ == '__main__':
|
|||||||
stirrer.activate(False)
|
stirrer.activate(False)
|
||||||
http_server.shutdown()
|
http_server.shutdown()
|
||||||
loop.close()
|
loop.close()
|
||||||
print("Server stopped.")
|
log.info("Server stopped.")
|
||||||
|
|||||||
+2
-2
@@ -178,11 +178,11 @@ class HeaterTask(ATask):
|
|||||||
try:
|
try:
|
||||||
self.device.process()
|
self.device.process()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"HeaterTask: comm error, marking disconnected: {e}")
|
self.log.error(f"comm error, marking disconnected: {e}")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
await asyncio.sleep(self.interval)
|
await asyncio.sleep(self.interval)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"HeaterTask: unexpected error, recovering: {e}")
|
self.log.error(f"unexpected error, recovering: {e}")
|
||||||
try:
|
try:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
+1
-1
@@ -143,4 +143,4 @@ class ServerLogTask(ATask):
|
|||||||
for name in (filename, self._latest_filename()):
|
for name in (filename, self._latest_filename()):
|
||||||
with open(os.path.join(self.path, name), 'w') as f:
|
with open(os.path.join(self.path, name), 'w') as f:
|
||||||
json.dump(log_data, f, indent='\t')
|
json.dump(log_data, f, indent='\t')
|
||||||
print('Server log: wrote {} samples to {}'.format(len(self._samples), filename))
|
self.log.info('Wrote {} samples to {}'.format(len(self._samples), filename))
|
||||||
|
|||||||
+2
-2
@@ -88,11 +88,11 @@ class StirrerTask(ATask):
|
|||||||
try:
|
try:
|
||||||
self.device.process()
|
self.device.process()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"StirrerTask: comm error, marking disconnected: {e}")
|
self.log.error(f"comm error, marking disconnected: {e}")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
await asyncio.sleep(self.interval)
|
await asyncio.sleep(self.interval)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"StirrerTask: unexpected error, recovering: {e}")
|
self.log.error(f"unexpected error, recovering: {e}")
|
||||||
try:
|
try:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
+1
-1
@@ -634,7 +634,7 @@ class SudTask(ATask):
|
|||||||
await self.msg_handler.send(data)
|
await self.msg_handler.send(data)
|
||||||
|
|
||||||
async def on_process(self):
|
async def on_process(self):
|
||||||
print("{}: Started with interval {} s".format(self.msg_handler.get_key(), self.interval))
|
self.log.info("Started with interval {} s".format(self.interval))
|
||||||
|
|
||||||
self.sud.set_on_changed('step', self.on_step_changed)
|
self.sud.set_on_changed('step', self.on_step_changed)
|
||||||
self.sud.set_on_changed('state', self.on_state_changed)
|
self.sud.set_on_changed('state', self.on_state_changed)
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ class TempSensorTask(ATask):
|
|||||||
try:
|
try:
|
||||||
self.sensor.temperature()
|
self.sensor.temperature()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"TempSensorTask: comm error priming sensor: {e}")
|
self.log.error(f"comm error priming sensor: {e}")
|
||||||
self.sensor.set_on_changed("temp", ChangedFloat(self.on_temp_changed, prec=1).set)
|
self.sensor.set_on_changed("temp", ChangedFloat(self.on_temp_changed, prec=1).set)
|
||||||
|
|
||||||
def on_temp_changed(self, value):
|
def on_temp_changed(self, value):
|
||||||
@@ -44,5 +44,5 @@ class TempSensorTask(ATask):
|
|||||||
try:
|
try:
|
||||||
self.sensor.temperature()
|
self.sensor.temperature()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"TempSensorTask: comm error reading sensor: {e}")
|
self.log.error(f"comm error reading sensor: {e}")
|
||||||
await asyncio.sleep(self.interval)
|
await asyncio.sleep(self.interval)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class Value:
|
class Value:
|
||||||
@@ -53,6 +54,12 @@ class ChangedInteger(object):
|
|||||||
class AttributeChange(object):
|
class AttributeChange(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.callbacks = {}
|
self.callbacks = {}
|
||||||
|
# Common ancestor of every task (tasks/task.py's ATask) and component
|
||||||
|
# (Connectable/APid/APlant/ATemperatureSensor) - giving it a logger
|
||||||
|
# here makes self.log available everywhere via one change, named
|
||||||
|
# after the concrete subclass so log lines self-identify their
|
||||||
|
# component without each call site typing it out.
|
||||||
|
self.log = logging.getLogger(type(self).__name__)
|
||||||
|
|
||||||
def set_on_changed(self, key, on_changed):
|
def set_on_changed(self, key, on_changed):
|
||||||
if key not in self.callbacks:
|
if key not in self.callbacks:
|
||||||
|
|||||||
Reference in New Issue
Block a user