data_store: add support for temp monitors
Temperature Monitors may report null values as temperatures, thus special handling is needed. This commit also reworks temperature store updates to use the subscription cache rather than perform its own caching of "last temps". Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
797c62389d
commit
47f0f0c21e
@ -16,18 +16,23 @@ from typing import (
|
|||||||
Optional,
|
Optional,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List,
|
||||||
Tuple,
|
|
||||||
Deque,
|
Deque,
|
||||||
)
|
)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..confighelper import ConfigHelper
|
from ..confighelper import ConfigHelper
|
||||||
from ..common import WebRequest
|
from ..common import WebRequest
|
||||||
|
from ..klippy_connection import KlippyConnection
|
||||||
from .klippy_apis import KlippyAPI as APIComp
|
from .klippy_apis import KlippyAPI as APIComp
|
||||||
GCQueue = Deque[Dict[str, Any]]
|
GCQueue = Deque[Dict[str, Any]]
|
||||||
TempStore = Dict[str, Dict[str, Deque[float]]]
|
TempStore = Dict[str, Dict[str, Deque[Optional[float]]]]
|
||||||
|
|
||||||
TEMP_UPDATE_TIME = 1.
|
TEMP_UPDATE_TIME = 1.
|
||||||
|
|
||||||
|
def _round_null(val: Optional[float], ndigits: int) -> Optional[float]:
|
||||||
|
if val is None:
|
||||||
|
return val
|
||||||
|
return round(val, ndigits)
|
||||||
|
|
||||||
class DataStore:
|
class DataStore:
|
||||||
def __init__(self, config: ConfigHelper) -> None:
|
def __init__(self, config: ConfigHelper) -> None:
|
||||||
self.server = config.get_server()
|
self.server = config.get_server()
|
||||||
@ -35,9 +40,11 @@ class DataStore:
|
|||||||
self.gcode_store_size = config.getint('gcode_store_size', 1000)
|
self.gcode_store_size = config.getint('gcode_store_size', 1000)
|
||||||
|
|
||||||
# Temperature Store Tracking
|
# Temperature Store Tracking
|
||||||
self.last_temps: Dict[str, Tuple[float, ...]] = {}
|
kconn: KlippyConnection = self.server.lookup_component("klippy_connection")
|
||||||
|
self.subscription_cache = kconn.get_subscription_cache()
|
||||||
self.gcode_queue: GCQueue = deque(maxlen=self.gcode_store_size)
|
self.gcode_queue: GCQueue = deque(maxlen=self.gcode_store_size)
|
||||||
self.temperature_store: TempStore = {}
|
self.temperature_store: TempStore = {}
|
||||||
|
self.temp_monitors: List[str] = []
|
||||||
eventloop = self.server.get_event_loop()
|
eventloop = self.server.get_event_loop()
|
||||||
self.temp_update_timer = eventloop.register_timer(
|
self.temp_update_timer = eventloop.register_timer(
|
||||||
self._update_temperature_store)
|
self._update_temperature_store)
|
||||||
@ -67,75 +74,72 @@ class DataStore:
|
|||||||
except self.server.error as e:
|
except self.server.error as e:
|
||||||
logging.info(f"Error Configuring Sensors: {e}")
|
logging.info(f"Error Configuring Sensors: {e}")
|
||||||
return
|
return
|
||||||
sensors: List[str]
|
heaters: Dict[str, List[str]] = result.get("heaters", {})
|
||||||
sensors = result.get("heaters", {}).get("available_sensors", [])
|
sensors = heaters.get("available_sensors", [])
|
||||||
|
self.temp_monitors = heaters.get("available_monitors", [])
|
||||||
|
sensors.extend(self.temp_monitors)
|
||||||
|
|
||||||
if sensors:
|
if sensors:
|
||||||
# Add Subscription
|
# Add Subscription
|
||||||
sub: Dict[str, Optional[List[str]]] = {s: None for s in sensors}
|
sub: Dict[str, Optional[List[str]]] = {s: None for s in sensors}
|
||||||
try:
|
try:
|
||||||
status: Dict[str, Any]
|
status: Dict[str, Any]
|
||||||
status = await klippy_apis.subscribe_objects(
|
status = await klippy_apis.subscribe_objects(sub)
|
||||||
sub, self._set_current_temps
|
|
||||||
)
|
|
||||||
except self.server.error as e:
|
except self.server.error as e:
|
||||||
logging.info(f"Error subscribing to sensors: {e}")
|
logging.info(f"Error subscribing to sensors: {e}")
|
||||||
return
|
return
|
||||||
logging.info(f"Configuring available sensors: {sensors}")
|
logging.info(f"Configuring available sensors: {sensors}")
|
||||||
new_store: TempStore = {}
|
new_store: TempStore = {}
|
||||||
|
valid_fields = ("temperature", "target", "power", "speed")
|
||||||
for sensor in sensors:
|
for sensor in sensors:
|
||||||
fields = list(status.get(sensor, {}).keys())
|
reported_fields = [
|
||||||
|
f for f in list(status.get(sensor, {}).keys()) if f in valid_fields
|
||||||
|
]
|
||||||
|
if not reported_fields:
|
||||||
|
logging.info(f"No valid fields reported for sensor: {sensor}")
|
||||||
|
self.temperature_store.pop(sensor, None)
|
||||||
|
continue
|
||||||
if sensor in self.temperature_store:
|
if sensor in self.temperature_store:
|
||||||
new_store[sensor] = self.temperature_store[sensor]
|
new_store[sensor] = self.temperature_store[sensor]
|
||||||
|
for field in list(new_store[sensor].keys()):
|
||||||
|
if field not in reported_fields:
|
||||||
|
new_store[sensor].pop(field, None)
|
||||||
|
else:
|
||||||
|
initial_val: Optional[float]
|
||||||
|
initial_val = _round_null(status[sensor][field], 2)
|
||||||
|
new_store[sensor][field].append(initial_val)
|
||||||
else:
|
else:
|
||||||
new_store[sensor] = {
|
new_store[sensor] = {}
|
||||||
'temperatures': deque(maxlen=self.temp_store_size)}
|
for field in reported_fields:
|
||||||
for item in ["target", "power", "speed"]:
|
if field not in new_store[sensor]:
|
||||||
if item in fields:
|
initial_val = _round_null(status[sensor][field], 2)
|
||||||
new_store[sensor][f"{item}s"] = deque(
|
new_store[sensor][field] = deque(
|
||||||
maxlen=self.temp_store_size)
|
[initial_val], maxlen=self.temp_store_size
|
||||||
if sensor not in self.last_temps:
|
)
|
||||||
self.last_temps[sensor] = (0., 0., 0., 0.)
|
|
||||||
self.temperature_store = new_store
|
self.temperature_store = new_store
|
||||||
# Prune unconfigured sensors in self.last_temps
|
self.temp_update_timer.start(delay=1.)
|
||||||
for sensor in list(self.last_temps.keys()):
|
|
||||||
if sensor not in self.temperature_store:
|
|
||||||
del self.last_temps[sensor]
|
|
||||||
# Update initial temperatures
|
|
||||||
self._set_current_temps(status)
|
|
||||||
self.temp_update_timer.start()
|
|
||||||
else:
|
else:
|
||||||
logging.info("No sensors found")
|
logging.info("No sensors found")
|
||||||
self.last_temps = {}
|
|
||||||
self.temperature_store = {}
|
self.temperature_store = {}
|
||||||
|
self.temp_monitors = []
|
||||||
self.temp_update_timer.stop()
|
self.temp_update_timer.stop()
|
||||||
|
|
||||||
def _set_current_temps(self, data: Dict[str, Any], _: float = 0.) -> None:
|
|
||||||
for sensor in self.temperature_store:
|
|
||||||
if sensor in data:
|
|
||||||
last_val = self.last_temps[sensor]
|
|
||||||
self.last_temps[sensor] = (
|
|
||||||
round(data[sensor].get('temperature', last_val[0]), 2),
|
|
||||||
data[sensor].get('target', last_val[1]),
|
|
||||||
data[sensor].get('power', last_val[2]),
|
|
||||||
data[sensor].get('speed', last_val[3]))
|
|
||||||
|
|
||||||
def _update_temperature_store(self, eventtime: float) -> float:
|
def _update_temperature_store(self, eventtime: float) -> float:
|
||||||
# XXX - If klippy is not connected, set values to zero
|
for sensor_name, sensor in self.temperature_store.items():
|
||||||
# as they are unknown?
|
sdata: Dict[str, Any] = self.subscription_cache.get(sensor_name, {})
|
||||||
for sensor, vals in self.last_temps.items():
|
for field, store in sensor.items():
|
||||||
self.temperature_store[sensor]['temperatures'].append(vals[0])
|
store.append(_round_null(sdata.get(field, store[-1]), 2))
|
||||||
for val, item in zip(vals[1:], ["targets", "powers", "speeds"]):
|
|
||||||
if item in self.temperature_store[sensor]:
|
|
||||||
self.temperature_store[sensor][item].append(val)
|
|
||||||
return eventtime + TEMP_UPDATE_TIME
|
return eventtime + TEMP_UPDATE_TIME
|
||||||
|
|
||||||
async def _handle_temp_store_request(self,
|
async def _handle_temp_store_request(
|
||||||
web_request: WebRequest
|
self, web_request: WebRequest
|
||||||
) -> Dict[str, Dict[str, List[float]]]:
|
) -> Dict[str, Dict[str, List[Optional[float]]]]:
|
||||||
|
include_monitors = web_request.get_boolean("include_monitors", False)
|
||||||
store = {}
|
store = {}
|
||||||
for name, sensor in self.temperature_store.items():
|
for name, sensor in self.temperature_store.items():
|
||||||
store[name] = {k: list(v) for k, v in sensor.items()}
|
if not include_monitors and name in self.temp_monitors:
|
||||||
|
continue
|
||||||
|
store[name] = {f"{k}s": list(v) for k, v in sensor.items()}
|
||||||
return store
|
return store
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user