Skip to content

Commit

Permalink
Added fan coil units
Browse files Browse the repository at this point in the history
  • Loading branch information
peteryefi committed Oct 23, 2024
1 parent 43f41be commit 3ad47fc
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 12 deletions.
2 changes: 2 additions & 0 deletions metamenth/enumerations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@
from metamenth.enumerations.terrain_type import TerrainType
from metamenth.enumerations.solar_distribution_type import SolarDistributionType
from metamenth.enumerations.relationship_name import RelationshipName
from metamenth.enumerations.fcu_type import FCUType
from metamenth.enumerations.fcu_pipe_system import FCUPipeSystem
14 changes: 14 additions & 0 deletions metamenth/enumerations/fcu_pipe_system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from metamenth.enumerations.abstract_enum import AbstractEnum


class FCUPipeSystem(AbstractEnum):
"""
Piping system for FCUs
Author: Peter Yefi
Email: [email protected]
"""
TWO_PIPE = "TwoPipe"
FOUR_PIPE = "FourPipe"


14 changes: 14 additions & 0 deletions metamenth/enumerations/fcu_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from metamenth.enumerations.abstract_enum import AbstractEnum


class FCUType(AbstractEnum):
"""
Types of fan coil units
Author: Peter Yefi
Email: [email protected]
"""
CEILING_MOUNTED = "CeilingMounted"
STANDALONE = "Standalone"


4 changes: 3 additions & 1 deletion metamenth/misc/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,14 @@ def is_hvac_component_allowed_in_space(hvac_component, disallowed_entities: List
from metamenth.subsystem.radiant_slab import RadiantSlab
from metamenth.subsystem.baseboard_heater import BaseboardHeater
from metamenth.subsystem.hvac_components.duct import Duct
from metamenth.subsystem.hvac_components.fan_coil_unit import FanCoilUnit

if any(isinstance(hvac_component, cls) for cls in disallowed_entities):
raise ValueError(f'{hvac_component.name} cannot be added to a space entity')
elif isinstance(space_entity, Room):
if (space_entity.room_type is not RoomType.MECHANICAL and
not any(isinstance(hvac_component, cls) for cls in [AirVolumeBox, BaseboardHeater, RadiantSlab, Duct])):
not any(isinstance(hvac_component, cls) for cls in [AirVolumeBox, BaseboardHeater, RadiantSlab, Duct])
and not (isinstance(hvac_component, FanCoilUnit) and not hvac_component.is_ducted)):
raise ValueError('You can only add HVAC components to mechanical rooms')
elif (isinstance(space_entity, OpenSpace) and
not any(isinstance(hvac_component, cls) for cls in [AirVolumeBox, BaseboardHeater, RadiantSlab, Duct])):
Expand Down
28 changes: 17 additions & 11 deletions metamenth/subsystem/hvac_components/duct_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from metamenth.subsystem.hvac_components.boiler import Boiler
from metamenth.utils import StructureEntitySearch
from typing import Dict
from metamenth.subsystem.hvac_components.fan_coil_unit import FanCoilUnit


class DuctConnection:
Expand All @@ -28,18 +29,23 @@ def add_entity(self, entity_type: DuctConnectionEntityType, duct_entity):
:return:
"""
from metamenth.subsystem.hvac_components.duct import Duct
allowed_entity_types = [HeatExchanger, AbstractSpace, Duct, Boiler, Chiller, CirculationPump,
Compressor, Pump, HeatPump, Condenser, AirVolumeBox, CoolingTower]
allowed_entity_types = [
HeatExchanger, AbstractSpace, Duct, Boiler, Chiller, CirculationPump,
Compressor, Pump, HeatPump, Condenser, AirVolumeBox, CoolingTower, FanCoilUnit
]

if any(isinstance(duct_entity, cls) for cls in allowed_entity_types):
if entity_type == DuctConnectionEntityType.SOURCE:
if duct_entity not in self._destination_entities:
self._source_entities.append(duct_entity)
elif entity_type == DuctConnectionEntityType.DESTINATION:
if duct_entity not in self._source_entities:
self._destination_entities.append(duct_entity)
else:
raise ValueError(f'{duct_entity} cannot be connected to a duct')
if not any(isinstance(duct_entity, cls) for cls in allowed_entity_types):
raise ValueError(f'{duct_entity.name} cannot be connected to a duct')

if isinstance(duct_entity, FanCoilUnit) and not duct_entity.is_ducted:
raise ValueError(f'{duct_entity.name} cannot be connected to a duct')

if entity_type == DuctConnectionEntityType.SOURCE:
if duct_entity not in self._destination_entities:
self._source_entities.append(duct_entity)
elif entity_type == DuctConnectionEntityType.DESTINATION:
if duct_entity not in self._source_entities:
self._destination_entities.append(duct_entity)

def remove_entity(self, entity_type: DuctConnectionEntityType, duct_entity):
"""
Expand Down
96 changes: 96 additions & 0 deletions metamenth/subsystem/hvac_components/fan_coil_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from metamenth.subsystem.hvac_components.interfaces.abstract_duct_connected_component import (
AbstractDuctConnectedComponent)
from metamenth.subsystem.hvac_components.heat_exchanger import HeatExchanger
from metamenth.subsystem.hvac_components.fan import Fan
from metamenth.enumerations import FCUType
from metamenth.enumerations import FCUPipeSystem


class FanCoilUnit(AbstractDuctConnectedComponent):
def __init__(self, name: str, heat_exchanger: HeatExchanger,
fan: Fan, fcu_type: FCUType, fcu_pipe_system: FCUPipeSystem, is_ducted: bool = True):
"""
Models a fan coil unit (FCU) in a built environment
:param name: the unique name of the heat exchanger
:param heat_exchanger: the heat exchanger which is part of the FCU
:param fan: the fan which is part of the FCU
:param fcu_type: the type of FCU
:param fcu_pipe_system: the piping system of the FCU
:param is_ducted: indicates if the FCU is connected to ventilation ducts or not
"""
super().__init__(name)
self._fan = None
self._heat_exchanger = None
self._fcu_type = None
self._fcu_pipe_system = None
self._is_ducted = is_ducted

self.heat_exchanger = heat_exchanger
self.fan = fan
self.fcu_type = fcu_type
self.fcu_pipe_system = fcu_pipe_system

@property
def heat_exchanger(self) -> HeatExchanger:
return self._heat_exchanger

@heat_exchanger.setter
def heat_exchanger(self, value: HeatExchanger):
if value is not None:
self._heat_exchanger = value
else:
raise ValueError("heat_exchanger must be of type HeatExchanger")

@property
def fan(self) -> Fan:
return self._fan

@fan.setter
def fan(self, value: Fan):
if value is not None:
self._fan = value
else:
raise ValueError("fan must be of type Fan")

@property
def fcu_type(self) -> FCUType:
return self._fcu_type

@fcu_type.setter
def fcu_type(self, value: FCUType):
if value is not None:
self._fcu_type = value
else:
raise ValueError("fcu_type must be of type FCUType")

@property
def fcu_pipe_system(self) -> FCUPipeSystem:
return self._fcu_pipe_system

@fcu_pipe_system.setter
def fcu_pipe_system(self, value: FCUPipeSystem):
if value is not None:
self._fcu_pipe_system = value
else:
raise ValueError("fcu_pipe_system must be of type FCUPipeSystem")

@property
def is_ducted(self) -> bool:
return self._is_ducted

@is_ducted.setter
def is_ducted(self, value: bool):
if value is not None:
self._is_ducted = value
else:
raise ValueError("is_ducted must be of type bool")

def __str__(self):
return (
f"FanCoilUnit ({super().__str__()}"
f"FCU Type: {self.fcu_type}, "
f"FCU Pipe System: {self.fcu_pipe_system}, "
f"Is Ducted: {self.is_ducted}, "
f"Heat Exchanger: {self.heat_exchanger}"
f"Fan : {self.fan})"
)
56 changes: 56 additions & 0 deletions tests/subsystem/test_building_control_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
from metamenth.enumerations import PumpType
from metamenth.subsystem.hvac_components.pump import Pump
from metamenth.enumerations import RoomType
from metamenth.enumerations import FCUType
from metamenth.enumerations import FCUPipeSystem
from metamenth.subsystem.hvac_components.fan_coil_unit import FanCoilUnit


class TestBuildingControlSystem(BaseTest):
Expand Down Expand Up @@ -192,6 +195,59 @@ def test_add_heat_exchanger_to_non_mechanical_room(self):
except ValueError as err:
self.assertEqual(err.__str__(), "You can only add HVAC components to mechanical rooms")

def test_non_ducted_fcu_to_mechanical_room(self):
vfd = VariableFrequencyDrive('PR.VNT.VRD.01')
fan = Fan("PR.VNT.FN.01", PowerState.ON, vfd)
heat_exchanger = HeatExchanger("PR.VNT.HE.01", HeatExchangerType.FIN_TUBE, HeatExchangerFlowType.PARALLEL)
fcu = FanCoilUnit('FCU.01', heat_exchanger, fan, FCUType.STANDALONE, FCUPipeSystem.FOUR_PIPE, False)
self.room.room_type = RoomType.MECHANICAL
self.room.add_hvac_component(fcu)
self.assertEqual(self.room.get_hvac_components({'name': 'FCU.01'}), [fcu])

def test_ducted_fcu_to_mechanical_room(self):
vfd = VariableFrequencyDrive('PR.VNT.VRD.01')
fan = Fan("PR.VNT.FN.01", PowerState.ON, vfd)
heat_exchanger = HeatExchanger("PR.VNT.HE.01", HeatExchangerType.FIN_TUBE, HeatExchangerFlowType.PARALLEL)
fcu = FanCoilUnit('FCU.01', heat_exchanger, fan, FCUType.STANDALONE, FCUPipeSystem.FOUR_PIPE)
self.room.room_type = RoomType.MECHANICAL
self.room.add_hvac_component(fcu)
self.assertEqual(self.room.get_hvac_components({'name': 'FCU.01'}), [fcu])

def test_ducted_fcu_to_non_mechanical_room(self):
try:
vfd = VariableFrequencyDrive('PR.VNT.VRD.01')
fan = Fan("PR.VNT.FN.01", PowerState.ON, vfd)
heat_exchanger = HeatExchanger("PR.VNT.HE.01", HeatExchangerType.FIN_TUBE, HeatExchangerFlowType.PARALLEL)
fcu = FanCoilUnit('FCU.01', heat_exchanger, fan, FCUType.STANDALONE, FCUPipeSystem.FOUR_PIPE)
self.room.add_hvac_component(fcu)
except ValueError as err:
self.assertEqual(err.__str__(), 'You can only add HVAC components to mechanical rooms')

def test_add_non_ducted_fcu_to_duct(self):
try:
vfd = VariableFrequencyDrive('PR.VNT.VRD.01')
fan = Fan("PR.VNT.FN.01", PowerState.ON, vfd)
heat_exchanger = HeatExchanger("PR.VNT.HE.01", HeatExchangerType.FIN_TUBE, HeatExchangerFlowType.PARALLEL)
fcu = FanCoilUnit('FCU.01', heat_exchanger, fan, FCUType.STANDALONE, FCUPipeSystem.FOUR_PIPE, False)

duct = Duct("VNT", DuctType.AIR)
conn = DuctConnection()
conn.add_entity(DuctConnectionEntityType.SOURCE, fcu)
duct.connections = conn
except ValueError as err:
self.assertEqual(err.__str__(), "FCU.01 cannot be connected to a duct")

def test_add_ducted_fcu_to_duct(self):
vfd = VariableFrequencyDrive('PR.VNT.VRD.01')
fan = Fan("PR.VNT.FN.01", PowerState.ON, vfd)
heat_exchanger = HeatExchanger("PR.VNT.HE.01", HeatExchangerType.FIN_TUBE, HeatExchangerFlowType.PARALLEL)
fcu = FanCoilUnit('FCU.01', heat_exchanger, fan, FCUType.STANDALONE, FCUPipeSystem.FOUR_PIPE)
duct = Duct("VNT", DuctType.AIR)
conn = DuctConnection()
conn.add_entity(DuctConnectionEntityType.SOURCE, fcu)
duct.connections = conn
self.assertEqual(duct.connections.get_source_entities(), [fcu])

def test_add_boiler_to_mechanical_room(self):
boiler = Boiler('PR.VNT.BL.01', BoilerCategory.NATURAL_GAS, PowerState.ON)
self.room.room_type = RoomType.MECHANICAL
Expand Down

0 comments on commit 3ad47fc

Please sign in to comment.