Coverage for teaser/logic/buildingobjects/thermalzone.py: 73%
436 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-04-29 16:01 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2025-04-29 16:01 +0000
1# created June 2015
2# by TEASER4 Development Team
4"""This module includes the ThermalZone class
5"""
6from __future__ import division
7import math
8import random
9import re
10import warnings
11from teaser.logic.buildingobjects.calculation.one_element import OneElement
12from teaser.logic.buildingobjects.calculation.two_element import TwoElement
13from teaser.logic.buildingobjects.calculation.three_element import ThreeElement
14from teaser.logic.buildingobjects.calculation.four_element import FourElement
15from teaser.logic.buildingobjects.calculation.five_element import FiveElement
18class ThermalZone(object):
19 """Thermal zone class.
21 This class is used to manage information and parameter calculation for
22 thermal zones. Each thermal zone has one specific calculation method,
23 which is specific to the used model (model_attr). For new model
24 implementation this attribute can be assigned to new classes.
26 Parameters
27 ----------
28 parent: Building()
29 The parent class of this object, the Building the zone belongs to.
30 Allows for better control of hierarchical structures. If not None it
31 adds this ThermalZone instance to Building.thermal_zones.
32 Default is None
34 Attributes
35 ----------
37 internal_id : float
38 Random id for the distinction between different zones.
39 name : str
40 Individual name.
41 area : float [m2]
42 Thermal zone area.
43 volume : float [m3]
44 Thermal zone volume.
45 outer_walls : list
46 List of OuterWall instances.
47 doors : list
48 List of Door instances.
49 rooftops : list
50 List of Rooftop instances.
51 ground_floors : list
52 List of GroundFloor instances.
53 windows : list
54 List of Window instances.
55 inner_walls : list
56 List of InnerWall instances.
57 floors : list
58 List of Floor instances.
59 ceilings: list
60 List of Ceiling instances.
61 interzonal_walls : list
62 List of InterzonalWall instances.
63 interzonal_floors : list
64 List of InterzonalFloor instances.
65 interzonal_ceilings: list
66 List of InterzonalCeiling instances.
67 use_conditions : UseConditions
68 Instance of UseConditions with all relevant information for the usage
69 of the thermal zone
70 model_attr : Union[OneElement, TwoElement, ThreeElement, FourElement,
71 FiveElement]
72 Instance of OneElement(), TwoElement(), ThreeElement(),
73 FourElement(), or FiveElement() that holds all calculation functions
74 and attributes needed for the specific model.
75 t_inside : float [K]
76 Normative indoor temperature for static heat load calculation.
77 The input of t_inside is ALWAYS in Kelvin
78 t_outside : float [K]
79 Normative outdoor temperature for static heat load calculation.
80 The input of t_inside is ALWAYS in Kelvin
81 t_ground : float [K]
82 Temperature directly at the outer side of ground floors for static
83 heat load calculation.
84 The input of t_ground is ALWAYS in Kelvin
85 t_ground_amplitude : float [K]
86 Temperature amplitude of the ground over the year
87 time_to_minimal_t_ground : int [s]
88 Time between simulation time 0 (not: start_time) and the minimum of
89 the ground temperature if the sine option for ground temperature is
90 chosen. Default: 6004800 (noon of Mar 11 as published by Virginia Tech
91 (https://www.builditsolar.com/Projects/Cooling/EarthTemperatures.htm)
92 for a depth of 5 ft)
93 density_air : float [kg/m3]
94 average density of the air in the thermal zone
95 heat_capac_air : float [J/K]
96 average heat capacity of the air in the thermal zone
97 number_of_floors : float
98 number of floors of the zone. If None, parent's number_of_floors is used
99 height_of_floors : float [m]
100 average height of floors of the zone. If None, parent's height_of_floors
101 is used
103 """
105 def __init__(self, parent=None):
106 """Constructor for ThermalZone
107 """
109 self.parent = parent
111 self.internal_id = random.random()
112 self.name = None
113 self._area = None
114 self._volume = None
115 self._outer_walls = []
116 self._doors = []
117 self._rooftops = []
118 self._ground_floors = []
119 self._windows = []
120 self._inner_walls = []
121 self._floors = []
122 self._ceilings = []
123 self._interzonal_walls = []
124 self._interzonal_floors = []
125 self._interzonal_ceilings = []
126 self._use_conditions = None
127 self._t_inside = 293.15
128 self._t_outside = 261.15
129 self.density_air = 1.25
130 self.heat_capac_air = 1002
131 self._t_ground = 286.15
132 self._t_ground_amplitude = 0
133 self.time_to_minimal_t_ground = 6004800
135 self._number_of_floors = None
136 self._height_of_floors = None
138 def calc_zone_parameters(
139 self,
140 number_of_elements=2,
141 merge_windows=False,
142 t_bt=5,
143 t_bt_layer=7
144 ):
145 """RC-Calculation for the thermal zone
147 Based on the input parameters (used model) this function instantiates
148 the corresponding calculation Class (e.g. TwoElement) and calculates
149 the zone parameters. Currently, the function is able to distinguishes
150 between the number of elements, we distinguish between:
152 - one element: all outer walls are aggregated into one element,
153 inner wall are neglected
154 - two elements: exterior and interior walls are aggregated
155 - three elements: like 2, but floor or roofs are aggregated
156 separately
157 - four elements: roofs and floors are aggregated separately
158 - five elements: includes borders to adjacent zones
160 For all four options we can choose if the thermal conduction through
161 the window is considered in a separate resistance or not.
163 Parameters
164 ----------
165 number_of_elements : int
166 defines the number of elements, that area aggregated, between 1
167 and 5, default is 2
169 merge_windows : bool
170 True for merging the windows into the outer walls, False for
171 separate resistance for window, default is False (Only
172 supported for IBPSA)
174 t_bt : float
175 Time constant according to VDI 6007 (default t_bt = 5)
176 t_bt_layer: float
177 Time constant according to VDI 6007 for aggragation of layers (default t_bt = 7)
178 """
180 if number_of_elements == 1:
181 self.model_attr = OneElement(
182 thermal_zone=self,
183 merge_windows=merge_windows,
184 t_bt=t_bt,
185 t_bt_layer=t_bt_layer)
186 self.model_attr.calc_attributes()
187 elif number_of_elements == 2:
188 self.model_attr = TwoElement(
189 thermal_zone=self,
190 merge_windows=merge_windows,
191 t_bt=t_bt,
192 t_bt_layer=t_bt_layer)
193 self.model_attr.calc_attributes()
194 elif number_of_elements == 3:
195 self.model_attr = ThreeElement(
196 thermal_zone=self,
197 merge_windows=merge_windows,
198 t_bt=t_bt,
199 t_bt_layer=t_bt_layer)
200 self.model_attr.calc_attributes()
201 elif number_of_elements == 4:
202 self.model_attr = FourElement(
203 thermal_zone=self,
204 merge_windows=merge_windows,
205 t_bt=t_bt,
206 t_bt_layer=t_bt_layer
207 )
208 self.model_attr.calc_attributes()
209 elif number_of_elements == 5:
210 self.model_attr = FiveElement(
211 thermal_zone=self,
212 merge_windows=merge_windows,
213 t_bt=t_bt)
214 self.model_attr.calc_attributes()
216 def find_walls(self, orientation, tilt):
217 """Returns all outer walls with given orientation and tilt
219 This function returns a list of all OuterWall elements with the
220 same orientation and tilt.
222 Parameters
223 ----------
224 orientation : float [degree]
225 Azimuth of the desired walls.
226 tilt : float [degree]
227 Tilt against the horizontal of the desired walls.
229 Returns
230 -------
231 elements : list
232 List of OuterWalls instances with desired orientation and tilt.
233 """
234 elements = []
235 for i in self.outer_walls:
236 if i.orientation == orientation and i.tilt == tilt:
237 elements.append(i)
238 else:
239 pass
240 return elements
242 def find_doors(self, orientation, tilt):
243 """Returns all outer walls with given orientation and tilt
245 This function returns a list of all Doors elements with the
246 same orientation and tilt.
248 Parameters
249 ----------
250 orientation : float [degree]
251 Azimuth of the desired walls.
252 tilt : float [degree]
253 Tilt against the horizontal of the desired walls.
255 Returns
256 -------
257 elements : list
258 List of Doors instances with desired orientation and tilt.
259 """
260 elements = []
261 for i in self.doors:
262 if i.orientation == orientation and i.tilt == tilt:
263 elements.append(i)
264 else:
265 pass
266 return elements
268 def find_rts(self, orientation, tilt):
269 """Returns all rooftops with given orientation and tilt
271 This function returns a list of all Rooftop elements with the
272 same orientation and tilt.
274 Parameters
275 ----------
276 orientation : float [degree]
277 Azimuth of the desired rooftops.
278 tilt : float [degree]
279 Tilt against the horizontal of the desired rooftops.
281 Returns
282 -------
283 elements : list
284 List of Rooftop instances with desired orientation and tilt.
285 """
286 elements = []
287 for i in self.rooftops:
288 if i.orientation == orientation and i.tilt == tilt:
289 elements.append(i)
290 else:
291 pass
292 return elements
294 def find_gfs(self, orientation, tilt):
295 """Returns all ground floors with given orientation and tilt
297 This function returns a list of all GroundFloor elements with the
298 same orientation and tilt.
300 Parameters
301 ----------
302 orientation : float [degree]
303 Azimuth of the desired ground floors.
304 tilt : float [degree]
305 Tilt against the horizontal of the desired ground floors.
307 Returns
308 -------
309 elements : list
310 List of GroundFloor instances with desired orientation and tilt.
311 """
312 elements = []
313 for i in self.ground_floors:
314 if i.orientation == orientation and i.tilt == tilt:
315 elements.append(i)
316 else:
317 pass
318 return elements
320 def find_wins(self, orientation, tilt):
321 """Returns all windows with given orientation and tilt
323 This function returns a list of all Window elements with the
324 same orientation and tilt.
326 Parameters
327 ----------
328 orientation : float [degree]
329 Azimuth of the desired windows.
330 tilt : float [degree]
331 Tilt against the horizontal of the desired windows.
333 Returns
334 -------
335 elements : list
336 List of Window instances with desired orientation and tilt.
337 """
338 elements = []
339 for i in self.windows:
340 if i.orientation == orientation and i.tilt == tilt:
341 elements.append(i)
342 else:
343 pass
344 return elements
346 def find_izes_outer(self, orientation=None, tilt=None, add_reversed=False):
347 """Returns all interzonal elements with given orientation and tilt
349 This function returns a list of all InterzonalWall, InterzonalCeiling
350 and InterzonalFloor elements that are to be lumped with outer elements
351 or exported as borders to adjacent zones, optionally reduced to those
352 with given orientation and tilt.
354 Parameters
355 ----------
356 orientation : float [degree]
357 Azimuth of the desired elements.
358 tilt : float [degree]
359 Tilt against the horizontal of the desired elements.
360 add_reversed : bool
361 also consider the elements with method_interzonal_export
362 'outer_reversed' (only for lumping to borders with adjacent zones)
364 Returns
365 -------
366 elements : list
367 List of InterzonalWall, InterzonalCeiling and InterzonalFloor
368 instances with desired orientation and tilt.
369 """
370 elements = []
371 for i in self.interzonal_elements:
372 if ((i.orientation == orientation or orientation is None)
373 and (i.tilt == tilt or tilt is None)):
374 if i.interzonal_type_export == 'outer_ordered' or (
375 add_reversed and i.interzonal_type_export == 'outer_reversed'
376 ):
377 elements.append(i)
378 else:
379 pass
380 return elements
382 def set_inner_wall_area(self):
383 """Sets the inner wall area according to zone area
385 Sets the inner wall area according to zone area size if type building
386 approach is used. This function covers Floors, Ceilings and InnerWalls.
387 Approximation approach depends on the building's
388 inner_wall_approximation_approach attribute.
390 """
392 ass_error_1 = "You need to specify parent for thermal zone"
394 assert self.parent is not None, ass_error_1
396 for floor in self.floors:
397 floor.area = ((self.number_of_floors - 1) / self.number_of_floors) \
398 * self.area
399 for ceiling in self.ceilings:
400 ceiling.area = ((self.number_of_floors - 1)
401 / self.number_of_floors) * self.area
403 typical_area = self.use_conditions.typical_length * \
404 self.use_conditions.typical_width
406 avg_room_nr = self.area / typical_area
408 approximation_approach \
409 = self.parent.inner_wall_approximation_approach
410 if approximation_approach not in (
411 'teaser_default',
412 'typical_minus_outer',
413 'typical_minus_outer_extended'
414 ):
415 warnings.warn(f'Inner wall approximation approach '
416 f'{approximation_approach} unknown. '
417 f'Falling back to teaser_default.')
418 approximation_approach = 'teaser_default'
419 if approximation_approach == 'typical_minus_outer':
420 wall_area = ((int(avg_room_nr)
421 + math.sqrt(avg_room_nr - int(avg_room_nr)))
422 * (2 * self.use_conditions.typical_length
423 * self.height_of_floors
424 + 2 * self.use_conditions.typical_width
425 * self.height_of_floors))
426 for other_verticals in self.outer_walls + self.interzonal_walls\
427 + self.windows + self.doors:
428 wall_area -= other_verticals.area
429 wall_area = max(0.01, wall_area)
430 elif approximation_approach == 'typical_minus_outer_extended':
431 wall_area = ((int(avg_room_nr)
432 + math.sqrt(avg_room_nr - int(avg_room_nr)))
433 * (2 * self.use_conditions.typical_length
434 * self.height_of_floors
435 + 2 * self.use_conditions.typical_width
436 * self.height_of_floors))
437 for other_verticals in self.outer_walls + self.interzonal_walls\
438 + self.doors:
439 wall_area -= other_verticals.area
440 for pot_vert_be in self.rooftops + self.windows:
441 wall_area -= pot_vert_be.area \
442 * math.sin(pot_vert_be.tilt * math.pi / 180)
443 wall_area -= max(0.0, sum(gf.area for gf in self.ground_floors)
444 - self.area)
445 wall_area = max(0.01, wall_area)
446 else:
447 wall_area = (avg_room_nr
448 * (self.use_conditions.typical_length
449 * self.height_of_floors
450 + 2 * self.use_conditions.typical_width
451 * self.height_of_floors))
453 for wall in self.inner_walls:
454 wall.area = wall_area
456 def set_volume_zone(self):
457 """Sets the zone volume according to area and height of floors
459 Sets the volume of a zone according area and height of floors
460 (building attribute).
461 """
463 ass_error_1 = "you need to specify parent for thermal zone"
465 assert self.parent is not None, ass_error_1
467 self.volume = self.area * self.height_of_floors
469 def retrofit_zone(
470 self,
471 type_of_retrofit=None,
472 window_type=None,
473 material=None):
474 """Retrofits all walls and windows in the zone.
476 Function call for all elements facing the ambient or ground.
477 Distinguishes if the parent building is a archetype of type 'iwu' or
478 'tabula_de'. If TABULA is used, it will use the pre-defined wall
479 constructions of TABULA.
481 This function covers OuterWall, Rooftop, GroundFloor and Window.
483 Parameters
484 ----------
485 type_of_retrofit : str
486 The classification of retrofit, if the archetype building
487 approach of TABULA is used.
488 window_type : str
489 Default: EnEv 2014
490 material : str
491 Default: EPS035
492 """
494 if type_of_retrofit is None:
495 type_of_retrofit = 'retrofit'
497 if type(self.parent).__name__ in [
498 "SingleFamilyHouse", "TerracedHouse", "MultiFamilyHouse",
499 "ApartmentBlock"]:
500 for wall_count in self.outer_walls \
501 + self.rooftops + self.ground_floors + self.doors + \
502 self.windows:
503 if "adv_retrofit" in wall_count.construction_data:
504 warnings.warn(
505 "already highest available standard"
506 + self.parent.name + wall_count.name)
507 elif "standard" in wall_count.construction_data:
508 wall_count.load_type_element(
509 year=self.parent.year_of_construction,
510 construction=wall_count.construction_data.replace(
511 "standard", type_of_retrofit))
512 else:
513 wall_count.load_type_element(
514 year=self.parent.year_of_construction,
515 construction=wall_count.construction_data.replace(
516 "retrofit", type_of_retrofit))
517 else:
519 for element_count in (
520 self.outer_walls
521 + self.rooftops
522 + self.ground_floors
523 + self.interzonal_elements
524 ):
525 element_count.retrofit_wall(
526 self.parent.year_of_retrofit,
527 material)
528 for win_count in self.windows:
529 win_count.replace_window(
530 self.parent.year_of_retrofit,
531 window_type)
533 def delete(self):
534 """Deletes the actual thermal zone safely.
536 This deletes the current thermal Zone and also refreshes the
537 thermal_zones list in the parent Building.
538 """
539 for index, tz in enumerate(self.parent.thermal_zones):
540 if tz.internal_id == self.internal_id:
541 self.parent.net_leased_area -= self.area
542 self.parent.thermal_zones.pop(index)
544 break
546 def add_element(self, building_element):
547 """Adds a building element to the corresponding list
549 This function adds a BuildingElement instance to the the list
550 depending on the type of the Building Element
552 Parameters
553 ----------
554 building_element : BuildingElement()
555 inherited objects of BuildingElement() instance of TEASER
557 """
559 ass_error_1 = ("building_element has to be an instance of OuterWall,"
560 " Rooftop, GroundFloor, Window, InnerWall, "
561 "Ceiling or Floor")
563 assert type(building_element).__name__ in (
564 "OuterWall", "Rooftop", "GroundFloor",
565 "InnerWall", "Ceiling", "Floor",
566 "InterzonalWall", "InterzonalCeiling", "InterzonalFloor",
567 "Window"), ass_error_1
569 if type(building_element).__name__ == "OuterWall":
570 self._outer_walls.append(building_element)
571 elif type(building_element).__name__ == "GroundFloor":
572 self._ground_floors.append(building_element)
573 elif type(building_element).__name__ == "Rooftop":
574 self._rooftops.append(building_element)
575 elif type(building_element).__name__ == "InnerWall":
576 self._inner_walls.append(building_element)
577 elif type(building_element).__name__ == "Ceiling":
578 self._ceilings.append(building_element)
579 elif type(building_element).__name__ == "Floor":
580 self._floors.append(building_element)
581 elif type(building_element).__name__ == "InterzonalWall":
582 self._interzonal_walls.append(building_element)
583 elif type(building_element).__name__ == "InterzonalCeiling":
584 self._interzonal_ceilings.append(building_element)
585 elif type(building_element).__name__ == "InterzonalFloor":
586 self._interzonal_floors.append(building_element)
587 elif type(building_element).__name__ == "Window":
588 self._windows.append(building_element)
590 @property
591 def parent(self):
592 return self.__parent
594 @parent.setter
595 def parent(self, value):
596 from teaser.logic.buildingobjects.building import Building
597 import inspect
598 if value is not None:
599 if inspect.isclass(Building):
600 self.__parent = value
601 self.__parent.thermal_zones.append(self)
603 @property
604 def name(self):
605 return self._name
607 @name.setter
608 def name(self, value):
609 regex = re.compile('[^a-zA-z0-9]')
610 if isinstance(value, str):
611 name = regex.sub('', value)
612 else:
613 try:
614 name = regex.sub('', str(value))
615 except ValueError:
616 print("Can't convert name to string")
618 # check if another zone with same name exists
619 tz_names = [tz._name for tz in self.parent.thermal_zones[:-1]]
620 if name in tz_names:
621 i = 1
622 while True:
623 name_add = f"{name}_{i}"
624 if name_add not in tz_names:
625 name = name_add
626 break
627 i += 1
628 self._name = name
630 @property
631 def outer_walls(self):
632 return self._outer_walls
634 @outer_walls.setter
635 def outer_walls(self, value):
636 if value is None:
637 self._outer_walls = []
639 @property
640 def doors(self):
641 return self._doors
643 @doors.setter
644 def doors(self, value):
645 if value is None:
646 self._doors = []
648 @property
649 def rooftops(self):
650 return self._rooftops
652 @rooftops.setter
653 def rooftops(self, value):
654 if value is None:
655 self._rooftops = []
657 @property
658 def ground_floors(self):
659 return self._ground_floors
661 @ground_floors.setter
662 def ground_floors(self, value):
663 if value is None:
664 self._ground_floors = []
666 @property
667 def ceilings(self):
668 return self._ceilings
670 @ceilings.setter
671 def ceilings(self, value):
672 if value is None:
673 self._ceilings = []
675 @property
676 def floors(self):
677 return self._floors
679 @floors.setter
680 def floors(self, value):
681 if value is None:
682 self._floors = []
684 @property
685 def inner_walls(self):
686 return self._inner_walls
688 @inner_walls.setter
689 def inner_walls(self, value):
691 if value is None:
692 self._inner_walls = []
694 @property
695 def interzonal_walls(self):
696 return self._interzonal_walls
698 @interzonal_walls.setter
699 def interzonal_walls(self, value):
701 if value is None:
702 self._interzonal_walls = []
704 @property
705 def interzonal_ceilings(self):
706 return self._interzonal_ceilings
708 @interzonal_ceilings.setter
709 def interzonal_ceilings(self, value):
711 if value is None:
712 self._interzonal_ceilings = []
714 @property
715 def interzonal_floors(self):
716 return self._interzonal_floors
718 @interzonal_floors.setter
719 def interzonal_floors(self, value):
721 if value is None:
722 self._interzonal_floors = []
724 @property
725 def interzonal_elements(self):
726 return self.interzonal_walls + self.interzonal_ceilings \
727 + self.interzonal_floors
729 @property
730 def windows(self):
731 return self._windows
733 @windows.setter
734 def windows(self, value):
736 if value is None:
737 self._windows = []
739 @property
740 def use_conditions(self):
741 return self._use_conditions
743 @use_conditions.setter
744 def use_conditions(self, value):
745 ass_error_1 = "Use condition has to be an instance of UseConditions()"
747 assert type(value).__name__ == "UseConditions", ass_error_1
749 if value is not None:
750 self._use_conditions = value
751 self.typical_length = value.typical_length
752 self.typical_width = value.typical_width
753 self._use_conditions = value
755 @property
756 def area(self):
757 return self._area
759 @area.setter
760 def area(self, value):
762 if isinstance(value, float):
763 pass
764 elif value is None:
765 pass
766 else:
767 try:
768 value = float(value)
769 except:
770 raise ValueError("Can't convert zone area to float")
772 if self.parent is not None:
773 if self._area is None:
774 if self.parent.net_leased_area is None:
775 self.parent.net_leased_area = 0.0
776 self._area = value
777 self.parent.net_leased_area += value
778 else:
779 self.parent.net_leased_area -= self._area
780 self.parent.net_leased_area += value
781 self._area = value
782 else:
783 self._area = value
785 @property
786 def number_of_floors(self):
787 if self._number_of_floors is None:
788 if self.parent is not None:
789 number_of_floors = self.parent.number_of_floors
790 else:
791 number_of_floors = None
792 else:
793 number_of_floors = self._number_of_floors
794 return number_of_floors
796 @number_of_floors.setter
797 def number_of_floors(self, value):
799 if isinstance(value, float):
800 pass
801 elif value is None:
802 pass
803 else:
804 try:
805 value = float(value)
806 except ValueError:
807 raise ValueError("Can't convert zone number_of_floors to float")
809 self._number_of_floors = value
811 @property
812 def height_of_floors(self):
813 if self._height_of_floors is None:
814 if self.parent is not None:
815 height_of_floors = self.parent.height_of_floors
816 else:
817 height_of_floors = None
818 else:
819 height_of_floors = self._height_of_floors
820 return height_of_floors
822 @height_of_floors.setter
823 def height_of_floors(self, value):
825 if isinstance(value, float):
826 pass
827 elif value is None:
828 pass
829 else:
830 try:
831 value = float(value)
832 except ValueError:
833 raise ValueError("Can't convert zone height_of_floors to float")
835 self._height_of_floors = value
837 @property
838 def volume(self):
839 return self._volume
841 @volume.setter
842 def volume(self, value):
844 if isinstance(value, float):
845 pass
846 elif value is None:
847 pass
848 else:
849 try:
850 value = float(value)
851 except ValueError:
852 raise ValueError("Can't convert zone volume to float")
854 if self.parent is not None:
855 if self._volume is None:
856 self._volume = value
857 self.parent.volume += value
858 else:
859 self.parent.volume -= self._volume
860 self.parent.volume += value
861 self._volume = value
862 else:
863 self._volume = value
865 @property
866 def t_inside(self):
867 return self._t_inside
869 @t_inside.setter
870 def t_inside(self, value):
871 if isinstance(value, float):
872 self._t_inside = value
873 elif value is None:
874 self._t_inside = value
875 else:
876 try:
877 value = float(value)
878 self._t_inside = value
879 except:
880 raise ValueError("Can't convert temperature to float")
882 @property
883 def t_outside(self):
884 return self._t_outside
886 @t_outside.setter
887 def t_outside(self, value):
889 if isinstance(value, float):
890 self._t_outside = value
891 elif value is None:
892 self._t_outside = value
893 else:
894 try:
895 value = float(value)
896 self._t_outside = value
897 except:
898 raise ValueError("Can't convert temperature to float")
900 @property
901 def t_ground(self):
902 return self._t_ground
904 @t_ground.setter
905 def t_ground(self, value):
907 if isinstance(value, float):
908 self._t_ground = value
909 elif value is None:
910 self._t_ground = value
911 else:
912 try:
913 value = float(value)
914 self._t_ground = value
915 except:
916 raise ValueError("Can't convert temperature to float")
918 @property
919 def t_ground_amplitude(self):
920 return self._t_ground_amplitude
922 @t_ground_amplitude.setter
923 def t_ground_amplitude(self, value):
925 if isinstance(value, float):
926 self._t_ground_amplitude = value
927 elif value is None:
928 self._t_ground_amplitude = value
929 else:
930 try:
931 value = float(value)
932 self._t_ground_amplitude = value
933 except:
934 raise ValueError("Can't convert temperature to float")