Coverage for src / zooc / data / temps.py: 82%
86 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 21:45 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 21:45 +0000
1"""Bed and extruder temperatures."""
2from __future__ import annotations
4import math
5from dataclasses import dataclass, field
8@dataclass(frozen=True)
9class _Temp:
10 """Temperature.
12 Temperature can have three states::
14 - nan -> Off, not heating.
15 - Positive number -> Heating temperature [°C].
16 - Disabled -> Temperature is skipped / not set / not waited.
17 """
19 _value: float
20 enabled: bool = field(default=True)
22 @classmethod
23 def create(cls, value: float | None) -> _Temp:
24 """Create a new instance of the class with given temperature.
26 :param value: Temperature, nan to turn off or None when not enabled [°C].
27 :return: Temp object.
28 """
29 enabled = True
30 if value is None:
31 enabled = False
32 value = math.nan
33 return cls(_value=value, enabled=enabled)
35 @classmethod
36 def klipper(cls, value: float | None) -> _Temp:
37 """Create a new instance of the class with Klipper style temperature.
39 :param value: Temperature, zero to turn off or None when not enabled [°C].
40 :return: Temp object.
41 """
42 enabled = True
43 if value is None: 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true
44 enabled = False
45 value = math.nan
46 elif value == 0.0: 46 ↛ 47line 46 didn't jump to line 47 because the condition on line 46 was never true
47 value = math.nan
48 return cls(_value=value, enabled=enabled)
50 @property
51 def value(self) -> float:
52 """Temperature value.
54 :return: Temperature value, 0.0 when off or nan when not enabled [°C].
55 """
56 if self.enabled:
57 if math.isnan(self._value): 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true
58 return 0.0 # -> off -> 0.0
59 return self._value
60 return math.nan # not enabled -> nan
62 def is_on(self) -> bool:
63 """Check whether temperature is on, i.e., set to some temperature.
65 :return: True when on. False when off or not enabled.
66 """
67 return self.enabled and not math.isnan(self._value)
69 def is_off(self) -> bool:
70 """Check whether temperature is off.
72 :return: True when off. False when on or not enabled.
73 """
74 return self.enabled and math.isnan(self._value)
76 def enable(self, enable: bool) -> _Temp:
77 """Set the temperatures enabled or disabled.
79 :param enable: Enable temperature.
80 :return: New instance with given usage data.
81 """
82 return _Temp(_value=self._value, enabled=enable)
84 def __add__(self, other: _Temp) -> _Temp:
85 """Add two, enabled temperatures together.
87 :param other: Other temperature to add.
88 :return: New instance with added temperatures.
89 """
90 return _Temp(_value=self._value + other._value, enabled=self.enabled and other.enabled)
92 def __sub__(self, other: _Temp) -> _Temp:
93 """Subtract two, enabled temperatures.
95 :param other: Other temperature to add.
96 :return: New instance with subtracted temperatures.
97 """
98 return _Temp(self._value - other._value, enabled=self.enabled and other.enabled)
100 def __eq__(self, other: object) -> bool:
101 """Check whether two temperatures are exactly equal.
103 :param other: Other temperature to compare with.
104 :return: True if objects are exactly equal.
105 """
106 if not isinstance(other, _Temp): 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true
107 return NotImplemented
108 return (((math.isnan(self._value) and math.isnan(other._value)) or (self._value == other._value))
109 and self.enabled == other.enabled)
111 def __str__(self) -> str:
112 """Show temperature values in degrees or 'off' when not set, or 'NA' when not enabled.
114 :return: String, e.g. 200C or 'off' or 'NA'.
115 """
116 if self.enabled: 116 ↛ 118line 116 didn't jump to line 118 because the condition on line 116 was always true
117 return f"{self._value}C" if not math.isnan(self._value) else "off"
118 return "NA"
120 def __hash__(self) -> int:
121 """Hash the object based on the inner state.
123 :return: Hash integer.
124 """
125 return hash((self._value, self.enabled))
128@dataclass(frozen=True)
129class Temps:
130 """Bed and extruder setting.
132 :param bed: Bed temperature.
133 :param extruder: Extruder temperature.
134 """
136 @classmethod
137 def create(cls, bed: float | None, extruder: float | None) -> Temps:
138 """Create a new instance of the class with given bed and extruder temperatures.
140 :param bed: Bed temperature, nan to turn off or None if not enabled [°C].
141 :param extruder: Extruder temperature, nan to turn off or None if not enabled [°C].
142 :return: Temps object.
143 """
144 return cls(_Temp.create(value=bed), _Temp.create(value=extruder))
146 @classmethod
147 def klipper(cls, bed: float | None, extruder: float | None) -> Temps:
148 """Create a new instance of the class with given Klipper style bed and extruder temperatures.
150 :param bed: Bed temperature, zero to turn off or None if not enabled [°C].
151 :param extruder: Extruder temperature, zero to turn off or None if not enabled [°C].
152 :return: Temps object.
153 """
154 return cls(_Temp.klipper(value=bed), _Temp.klipper(value=extruder))
156 bed: _Temp
157 extruder: _Temp
159 @property
160 def bed_temp(self) -> float:
161 """Bed temperature.
163 :return: Bed temperature.
164 """
165 return self.bed.value
167 @property
168 def extruder_temp(self) -> float:
169 """Extruder temperature.
171 :return: Extruder temperature.
172 """
173 return self.extruder.value
175 def enable(self, bed_enable: bool, extruder_enable: bool) -> Temps:
176 """Set bed or extruder temperatures enabled or disabled.
178 :param bed_enable: Enable bed temperature.
179 :param extruder_enable: Enable extruder temperature.
180 :return: New instance with given usage data.
181 """
182 return Temps(bed=self.bed.enable(enable=bed_enable), extruder=self.extruder.enable(enable=extruder_enable))
184 def both_on(self) -> bool:
185 """Check whether both bed and extruder temperatures are on, i.e., set to some temperature.
187 :return: True if both temperatures are set.
188 """
189 return self.bed.is_on() and self.extruder.is_on()
191 def is_none_off(self) -> bool:
192 """Check whether both bed and extruder temperatures are on or not enabled, i.e. not off.
194 :return: True if both temperatures are set or not enabled.
195 """
196 return not self.bed.is_off() and not self.extruder.is_off()
198 def both_off(self) -> bool:
199 """Check whether both temperatures are off.
201 :return: True if both temperatures are off.
202 """
203 return self.bed.is_off() and self.extruder.is_off()
205 def __add__(self, other: Temps) -> Temps:
206 """Add two, enabled temperatures together.
208 :param other: Other temperature to add.
209 :return: New instance with added temperatures.
210 """
211 return Temps(bed=self.bed + other.bed, extruder=self.extruder + other.extruder)
213 def __sub__(self, other: Temps) -> Temps:
214 """Subtract two, enabled temperatures.
216 :param other: Other temperature to add.
217 :return: New instance with subtracted temperatures.
218 """
219 return Temps(bed=self.bed - other.bed, extruder=self.extruder - other.extruder)
221 def __eq__(self, other: object) -> bool:
222 """Check whether two temperatures are exactly equal.
224 :param other: Other temperature to compare with.
225 :return: True if objects are exactly equal.
226 """
227 if not isinstance(other, Temps):
228 return NotImplemented
229 return self.bed == other.bed and self.extruder == other.extruder
231 def __str__(self) -> str:
232 """Show temperature values in degrees or 'off' when not set, or 'NA' when not enabled.
234 :return: String, e.g. bed=200C extruder=60C.
235 """
236 return f"bed={self.bed} extruder={self.extruder}"
238 def __hash__(self) -> int:
239 """Hash the object based on bed and extruder temperatures.
241 :return: Hash integer.
242 """
243 return hash((self.bed, self.extruder))