Public Access
added POC for temperature logging
This commit is contained in:
1 parent
d48f96e498
commit
5338304ee8
6 files changed
+768
-7
No files matched your search
@@ -3,6 +3,8 @@ import re
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
from .temperature import TemperatureController, register_controller
|
||||
|
||||
|
||||
DEBUG=False
|
||||
|
||||
@@ -40,7 +42,7 @@ def checksum(message):
|
||||
bcc = (reduce(operator.xor, list(map(ord,message))))
|
||||
return chr(bcc)
|
||||
|
||||
class Eurotherm(object):
|
||||
class Eurotherm(TemperatureController):
|
||||
def __init__(self, serial_device, baudrate = 19200):
|
||||
self.device = standard_device
|
||||
# timeout: 110 ms to get all answers.
|
||||
@@ -100,6 +102,39 @@ class Eurotherm(object):
|
||||
def get_setpoint_temperature(self):
|
||||
return self.read_param('SL')
|
||||
|
||||
# TemperatureController interface implementation
|
||||
|
||||
def get_temperature(self) -> float:
|
||||
"""Read current temperature in Celsius."""
|
||||
temp = self.read_param('PV')
|
||||
if temp is None:
|
||||
return 0.0
|
||||
try:
|
||||
return float(temp)
|
||||
except (ValueError, TypeError):
|
||||
return 0.0
|
||||
|
||||
def get_setpoint(self) -> float:
|
||||
"""Read target setpoint temperature in Celsius."""
|
||||
temp = self.read_param('SL')
|
||||
if temp is None:
|
||||
return 0.0
|
||||
try:
|
||||
return float(temp)
|
||||
except (ValueError, TypeError):
|
||||
return 0.0
|
||||
|
||||
def set_setpoint(self, temperature: float) -> bool:
|
||||
"""Set target temperature in Celsius."""
|
||||
return self.write_param('SL', str(temperature))
|
||||
|
||||
def close(self):
|
||||
"""Close the serial connection."""
|
||||
if hasattr(self, 's') and self.s is not None:
|
||||
if self.s.is_open:
|
||||
self.s.close()
|
||||
self.s = None
|
||||
|
||||
if __name__ == '__main__':
|
||||
import time
|
||||
delta=5
|
||||
@@ -118,3 +153,6 @@ if __name__ == '__main__':
|
||||
f.write(l)
|
||||
f.flush()
|
||||
f.write('# MARK -- %s --\n'%(time.asctime()))
|
||||
|
||||
# Register Eurotherm with the factory
|
||||
register_controller('eurotherm', Eurotherm)
|
||||
@@ -0,0 +1,143 @@
|
||||
# -*- coding: iso-8859-1 -*-
|
||||
"""
|
||||
Temperature controller interface and base classes.
|
||||
|
||||
This module provides a consistent interface for temperature controllers
|
||||
to enable pluggable temperature monitoring systems.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
# Plugin registry for temperature controllers
|
||||
_controller_registry = {}
|
||||
|
||||
|
||||
def register_controller(name: str, controller_class):
|
||||
"""
|
||||
Register a temperature controller implementation.
|
||||
|
||||
Args:
|
||||
name: Unique identifier for this controller type
|
||||
controller_class: Class that implements TemperatureController
|
||||
"""
|
||||
_controller_registry[name] = controller_class
|
||||
|
||||
|
||||
def create_controller(name: str, *args, **kwargs):
|
||||
"""
|
||||
Create a temperature controller instance by name.
|
||||
|
||||
Args:
|
||||
name: Registered controller type name
|
||||
*args: Positional arguments to pass to controller constructor
|
||||
**kwargs: Keyword arguments to pass to controller constructor
|
||||
|
||||
Returns:
|
||||
TemperatureController: Instance of the requested controller
|
||||
|
||||
Raises:
|
||||
ValueError: If controller name is not registered and cannot be imported
|
||||
"""
|
||||
# If not found, try to auto-import the module
|
||||
if name not in _controller_registry:
|
||||
# Map known controller names to their modules
|
||||
module_map = {
|
||||
'simulated': 'damaris.tools.temperature_simulator',
|
||||
'eurotherm': 'damaris.tools.eurotherm',
|
||||
}
|
||||
if name in module_map:
|
||||
try:
|
||||
# Import the module which will register the controller
|
||||
import importlib
|
||||
importlib.import_module(module_map[name])
|
||||
except ImportError as e:
|
||||
raise ValueError(f"Failed to import controller '{name}': {e}. Available: {list(_controller_registry.keys())}")
|
||||
|
||||
# Check again after import
|
||||
if name not in _controller_registry:
|
||||
raise ValueError(f"Unknown temperature controller: {name}. Available: {list(_controller_registry.keys())}")
|
||||
|
||||
return _controller_registry[name](*args, **kwargs)
|
||||
|
||||
|
||||
def get_controller_names() -> list:
|
||||
"""
|
||||
Get list of available controller names.
|
||||
|
||||
Returns:
|
||||
list: List of registered controller type names
|
||||
"""
|
||||
return list(_controller_registry.keys())
|
||||
|
||||
|
||||
# Pre-register built-in controllers by importing their modules
|
||||
# This ensures they're available without explicit imports
|
||||
try:
|
||||
from . import eurotherm
|
||||
except ImportError as e:
|
||||
# Eurotherm may require serial module which might not be installed
|
||||
# That's OK, it will be imported on-demand if needed
|
||||
pass
|
||||
|
||||
try:
|
||||
from . import temperature_simulator
|
||||
except ImportError as e:
|
||||
# Simulator should always work (no external dependencies)
|
||||
# But if it fails, it will be imported on-demand
|
||||
pass
|
||||
|
||||
|
||||
class TemperatureController(ABC):
|
||||
"""
|
||||
Abstract base class for temperature controllers.
|
||||
|
||||
All temperature controller implementations (Eurotherm, Lakeshore, etc.)
|
||||
should inherit from this class and implement the abstract methods.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_temperature(self) -> float:
|
||||
"""
|
||||
Read current temperature in Celsius.
|
||||
|
||||
Returns:
|
||||
float: Current temperature in degrees Celsius.
|
||||
|
||||
Raises:
|
||||
IOError: If communication with the device fails.
|
||||
RuntimeError: If the device is not ready or returns invalid data.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_setpoint(self) -> float:
|
||||
"""
|
||||
Read the target setpoint temperature in Celsius.
|
||||
|
||||
Returns:
|
||||
float: Target temperature in degrees Celsius.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_setpoint(self, temperature: float) -> bool:
|
||||
"""
|
||||
Set the target temperature setpoint.
|
||||
|
||||
Args:
|
||||
temperature: Target temperature in degrees Celsius.
|
||||
|
||||
Returns:
|
||||
bool: True if the setpoint was successfully set, False otherwise.
|
||||
"""
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Clean up resources (e.g., close serial connections).
|
||||
|
||||
This method should be called when the controller is no longer needed.
|
||||
The default implementation does nothing; subclasses should override
|
||||
if they have resources to clean up.
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,126 @@
|
||||
# -*- coding: iso-8859-1 -*-
|
||||
"""
|
||||
Simulated temperature controller for testing.
|
||||
|
||||
Provides a temperature controller that simulates real temperature readings
|
||||
with configurable parameters including sinusoidal variation, noise, and missing values.
|
||||
"""
|
||||
|
||||
import time
|
||||
import math
|
||||
import random
|
||||
import threading
|
||||
|
||||
from .temperature import TemperatureController, register_controller
|
||||
|
||||
|
||||
class SimulatedTemperatureController(TemperatureController):
|
||||
"""
|
||||
A simulated temperature controller that generates realistic temperature readings.
|
||||
|
||||
This controller is useful for testing and development without requiring
|
||||
physical hardware. It simulates temperature readings with:
|
||||
- Base temperature around 290K (17C)
|
||||
- Sinusoidal variation with amplitude of 10K
|
||||
- 5% random noise
|
||||
- 10% probability of missing values (returns previous value)
|
||||
- 60 second period for the sine wave
|
||||
|
||||
Attributes:
|
||||
base_temp: Base temperature in Kelvin (default: 290.0)
|
||||
amplitude: Amplitude of sinusoidal variation in Kelvin (default: 10.0)
|
||||
period: Period of sine wave in seconds (default: 60.0)
|
||||
noise_level: Fraction of random noise (default: 0.05 = 5%)
|
||||
missing_probability: Probability of missing value (default: 0.10 = 10%)
|
||||
"""
|
||||
|
||||
def __init__(self, base_temp: float = 290.0, amplitude: float = 10.0,
|
||||
period: float = 60.0, noise_level: float = 0.05,
|
||||
missing_probability: float = 0.10):
|
||||
"""
|
||||
Initialize the simulated temperature controller.
|
||||
|
||||
Args:
|
||||
base_temp: Base temperature in Kelvin (default: 290.0)
|
||||
amplitude: Amplitude of sinusoidal variation in Kelvin (default: 10.0)
|
||||
period: Period of sine wave in seconds (default: 60.0)
|
||||
noise_level: Fraction of random noise (default: 0.05 = 5%)
|
||||
missing_probability: Probability of missing value (default: 0.10 = 10%)
|
||||
"""
|
||||
self.base_temp = float(base_temp)
|
||||
self.amplitude = float(amplitude)
|
||||
self.period = float(period)
|
||||
self.noise_level = float(noise_level)
|
||||
self.missing_probability = float(missing_probability)
|
||||
|
||||
self._start_time = time.time()
|
||||
self._lock = threading.RLock()
|
||||
self._setpoint = self.base_temp
|
||||
self._last_temp = self.base_temp
|
||||
self._last_reading_time = self._start_time
|
||||
|
||||
def get_temperature(self) -> float:
|
||||
"""
|
||||
Get simulated current temperature in Kelvin.
|
||||
|
||||
Returns:
|
||||
float: Simulated temperature in Kelvin
|
||||
"""
|
||||
with self._lock:
|
||||
elapsed = time.time() - self._start_time
|
||||
current_time = time.time()
|
||||
|
||||
# Calculate ideal sinusoidal temperature
|
||||
temp = self.base_temp + self.amplitude * math.sin(2 * math.pi * elapsed / self.period)
|
||||
|
||||
# Add 5% random noise
|
||||
noise = temp * self.noise_level * (random.random() * 2 - 1) # -5% to +5%
|
||||
temp += noise
|
||||
|
||||
# 10% chance of missing value (return last good value)
|
||||
if random.random() < self.missing_probability:
|
||||
temp = self._last_temp
|
||||
else:
|
||||
self._last_temp = temp
|
||||
self._last_reading_time = current_time
|
||||
|
||||
return temp
|
||||
|
||||
def get_setpoint(self) -> float:
|
||||
"""
|
||||
Get the target setpoint temperature in Kelvin.
|
||||
|
||||
Returns:
|
||||
float: Target temperature in Kelvin
|
||||
"""
|
||||
with self._lock:
|
||||
return self._setpoint
|
||||
|
||||
def set_setpoint(self, temperature: float) -> bool:
|
||||
"""
|
||||
Set the target temperature setpoint.
|
||||
|
||||
Args:
|
||||
temperature: Target temperature in Kelvin
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
with self._lock:
|
||||
self._setpoint = float(temperature)
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
"""Clean up resources."""
|
||||
pass
|
||||
|
||||
def reset(self):
|
||||
"""Reset the simulation to initial state."""
|
||||
with self._lock:
|
||||
self._start_time = time.time()
|
||||
self._last_temp = self.base_temp
|
||||
self._last_reading_time = self._start_time
|
||||
|
||||
|
||||
# Register the simulated controller with the factory
|
||||
register_controller('simulated', SimulatedTemperatureController)
|
||||
Reference in new issue
Block a user