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

1"""Bed and extruder temperatures.""" 

2from __future__ import annotations 

3 

4import math 

5from dataclasses import dataclass, field 

6 

7 

8@dataclass(frozen=True) 

9class _Temp: 

10 """Temperature. 

11 

12 Temperature can have three states:: 

13 

14 - nan -> Off, not heating. 

15 - Positive number -> Heating temperature [°C]. 

16 - Disabled -> Temperature is skipped / not set / not waited. 

17 """ 

18 

19 _value: float 

20 enabled: bool = field(default=True) 

21 

22 @classmethod 

23 def create(cls, value: float | None) -> _Temp: 

24 """Create a new instance of the class with given temperature. 

25 

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) 

34 

35 @classmethod 

36 def klipper(cls, value: float | None) -> _Temp: 

37 """Create a new instance of the class with Klipper style temperature. 

38 

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) 

49 

50 @property 

51 def value(self) -> float: 

52 """Temperature value. 

53 

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 

61 

62 def is_on(self) -> bool: 

63 """Check whether temperature is on, i.e., set to some temperature. 

64 

65 :return: True when on. False when off or not enabled. 

66 """ 

67 return self.enabled and not math.isnan(self._value) 

68 

69 def is_off(self) -> bool: 

70 """Check whether temperature is off. 

71 

72 :return: True when off. False when on or not enabled. 

73 """ 

74 return self.enabled and math.isnan(self._value) 

75 

76 def enable(self, enable: bool) -> _Temp: 

77 """Set the temperatures enabled or disabled. 

78 

79 :param enable: Enable temperature. 

80 :return: New instance with given usage data. 

81 """ 

82 return _Temp(_value=self._value, enabled=enable) 

83 

84 def __add__(self, other: _Temp) -> _Temp: 

85 """Add two, enabled temperatures together. 

86 

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) 

91 

92 def __sub__(self, other: _Temp) -> _Temp: 

93 """Subtract two, enabled temperatures. 

94 

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) 

99 

100 def __eq__(self, other: object) -> bool: 

101 """Check whether two temperatures are exactly equal. 

102 

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) 

110 

111 def __str__(self) -> str: 

112 """Show temperature values in degrees or 'off' when not set, or 'NA' when not enabled. 

113 

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" 

119 

120 def __hash__(self) -> int: 

121 """Hash the object based on the inner state. 

122 

123 :return: Hash integer. 

124 """ 

125 return hash((self._value, self.enabled)) 

126 

127 

128@dataclass(frozen=True) 

129class Temps: 

130 """Bed and extruder setting. 

131 

132 :param bed: Bed temperature. 

133 :param extruder: Extruder temperature. 

134 """ 

135 

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. 

139 

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)) 

145 

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. 

149 

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)) 

155 

156 bed: _Temp 

157 extruder: _Temp 

158 

159 @property 

160 def bed_temp(self) -> float: 

161 """Bed temperature. 

162 

163 :return: Bed temperature. 

164 """ 

165 return self.bed.value 

166 

167 @property 

168 def extruder_temp(self) -> float: 

169 """Extruder temperature. 

170 

171 :return: Extruder temperature. 

172 """ 

173 return self.extruder.value 

174 

175 def enable(self, bed_enable: bool, extruder_enable: bool) -> Temps: 

176 """Set bed or extruder temperatures enabled or disabled. 

177 

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)) 

183 

184 def both_on(self) -> bool: 

185 """Check whether both bed and extruder temperatures are on, i.e., set to some temperature. 

186 

187 :return: True if both temperatures are set. 

188 """ 

189 return self.bed.is_on() and self.extruder.is_on() 

190 

191 def is_none_off(self) -> bool: 

192 """Check whether both bed and extruder temperatures are on or not enabled, i.e. not off. 

193 

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() 

197 

198 def both_off(self) -> bool: 

199 """Check whether both temperatures are off. 

200 

201 :return: True if both temperatures are off. 

202 """ 

203 return self.bed.is_off() and self.extruder.is_off() 

204 

205 def __add__(self, other: Temps) -> Temps: 

206 """Add two, enabled temperatures together. 

207 

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) 

212 

213 def __sub__(self, other: Temps) -> Temps: 

214 """Subtract two, enabled temperatures. 

215 

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) 

220 

221 def __eq__(self, other: object) -> bool: 

222 """Check whether two temperatures are exactly equal. 

223 

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 

230 

231 def __str__(self) -> str: 

232 """Show temperature values in degrees or 'off' when not set, or 'NA' when not enabled. 

233 

234 :return: String, e.g. bed=200C extruder=60C. 

235 """ 

236 return f"bed={self.bed} extruder={self.extruder}" 

237 

238 def __hash__(self) -> int: 

239 """Hash the object based on bed and extruder temperatures. 

240 

241 :return: Hash integer. 

242 """ 

243 return hash((self.bed, self.extruder))