within Buildings.Examples.VAVReheat.BaseClasses;
partial model PartialHVAC
  "Partial model of variable air volume flow system with terminal reheat that serves five thermal zones"

  replaceable package MediumA = Buildings.Media.Air
    constrainedby Modelica.Media.Interfaces.PartialCondensingGases "Medium model for air";
  replaceable package MediumW = Buildings.Media.Water "Medium model for water";

  constant Integer numZon(min=2)=5 "Total number of served VAV boxes";

  parameter Modelica.Units.SI.Volume VRoo[numZon] "Room volume per zone";
  parameter Modelica.Units.SI.Area AFlo[numZon] "Floor area per zone";

  final parameter Modelica.Units.SI.Area ATot=sum(AFlo)
    "Total floor area for all zone";

  constant Real conv=1.2/3600 "Conversion factor for nominal mass flow rate";

  parameter Modelica.Units.SI.HeatFlowRate QHeaAHU_flow_nominal(min=0) = mHeaAir_flow_nominal * cpAir * (THeaAirSup_nominal-THeaAirMix_nominal)
    "Nominal heating heat flow rate of air handler unit coil";

  parameter Modelica.Units.SI.HeatFlowRate QCooAHU_flow_nominal(max=0) = 1.3 * mCooAir_flow_nominal * cpAir *(TCooAirSup_nominal-TCooAirMix_nominal)
    "Nominal total cooling heat flow rate of air handler unit coil (negative number)";

  parameter Modelica.Units.SI.MassFlowRate mCooVAV_flow_nominal[numZon]
    "Design mass flow rate per zone for cooling"

  parameter Modelica.Units.SI.MassFlowRate mHeaVAV_flow_nominal[numZon] = 0.3*mCooVAV_flow_nominal
    "Design mass flow rate per zone for heating"

  parameter Modelica.Units.SI.MassFlowRate mAir_flow_nominal=mCooAir_flow_nominal
    "Nominal mass flow rate for fan"
  parameter Modelica.Units.SI.MassFlowRate mCooAir_flow_nominal=0.7*sum(mCooVAV_flow_nominal)
    "Nominal mass flow rate for fan"
  parameter Modelica.Units.SI.MassFlowRate mHeaAir_flow_nominal = 0.7*sum(mHeaVAV_flow_nominal)
    "Nominal mass flow rate for fan"

  parameter Modelica.Units.SI.MassFlowRate mHeaWat_flow_nominal=
      QHeaAHU_flow_nominal/cpWat/10
    "Nominal water mass flow rate for heating coil in AHU"
  parameter Modelica.Units.SI.MassFlowRate mCooWat_flow_nominal=
      QCooAHU_flow_nominal/cpWat/(-6)
    "Nominal water mass flow rate for cooling coil"

  parameter Real ratOAFlo_A(final unit="m3/(s.m2)") = 0.3e-3
    "Outdoor airflow rate required per unit area";
  parameter Real ratOAFlo_P = 2.5e-3
    "Outdoor airflow rate required per person";
  parameter Real ratP_A = 5e-2
    "Occupant density";
  parameter Real effZ(final unit="1") = 0.8
    "Zone air distribution effectiveness (limiting value)";
  parameter Real divP(final unit="1") = 0.7
    "Occupant diversity ratio";

  parameter Modelica.Units.SI.VolumeFlowRate VZonOA_flow_nominal[numZon]=(
      ratOAFlo_P*ratP_A + ratOAFlo_A)*AFlo/effZ
    "Zone outdoor air flow rate of each VAV box";

  parameter Modelica.Units.SI.VolumeFlowRate Vou_flow_nominal=(divP*ratOAFlo_P*
      ratP_A + ratOAFlo_A)*sum(AFlo) "System uncorrected outdoor air flow rate";
  parameter Real effVen(final unit="1") = if divP < 0.6 then
    0.88 * divP + 0.22 else 0.75
    "System ventilation efficiency";
  parameter Modelica.Units.SI.VolumeFlowRate Vot_flow_nominal=Vou_flow_nominal/
      effVen "System design outdoor air flow rate";

  parameter Modelica.Units.SI.Temperature THeaOn=293.15
    "Heating setpoint during on"
  parameter Modelica.Units.SI.Temperature THeaOff=285.15
    "Heating setpoint during off"
  parameter Modelica.Units.SI.Temperature TCooOn=297.15
    "Cooling setpoint during on"
  parameter Modelica.Units.SI.Temperature TCooOff=303.15
    "Cooling setpoint during off"
  parameter Modelica.Units.SI.PressureDifference dpBuiStaSet(min=0) = 12
    "Building static pressure";
  parameter Real yFanMin = 0.1 "Minimum fan speed";

  parameter Modelica.Units.SI.Temperature TCooAirMix_nominal(displayUnit="degC")=303.15
    "Mixed air temperature during cooling nominal conditions (used to size cooling coil)"
  parameter Modelica.Units.SI.Temperature TCooAirSup_nominal(displayUnit="degC")=285.15
    "Supply air temperature during cooling nominal conditions (used to size cooling coil)"
  parameter Modelica.Units.SI.MassFraction wCooAirMix_nominal = 0.017
    "Humidity ratio of mixed air at a nominal conditions used to size cooling coil (in kg/kg dry total)"
   parameter Modelica.Units.SI.Temperature TCooWatInl_nominal(displayUnit="degC") = 279.15
    "Cooling coil nominal inlet water temperature"

  parameter Modelica.Units.SI.Temperature THeaAirMix_nominal(displayUnit="degC")=277.15
    "Mixed air temperature during heating nominal conditions (used to size heating coil)"
  parameter Modelica.Units.SI.Temperature THeaAirSup_nominal(displayUnit="degC")=285.15
    "Supply air temperature during heating nominal conditions (used to size heating coil)"
  parameter Modelica.Units.SI.Temperature THeaWatInl_nominal(displayUnit="degC")
    "Reheat coil nominal inlet water temperature"

  parameter Boolean allowFlowReversal=true
    "= false to simplify equations, assuming, but not enforcing, no flow reversal"

  Modelica.Fluid.Interfaces.FluidPort_a port_supAir[numZon](redeclare package
      Medium = MediumA)
    "Supply air to thermal zones"
  Modelica.Fluid.Interfaces.FluidPort_a port_retAir[numZon](redeclare package
      Medium = MediumA)
    "Return air from thermal zones"

  Modelica.Blocks.Interfaces.RealInput TRoo[numZon](
   each final unit="K",
   each displayUnit="degC")
   "Room temperatures"

  Buildings.Fluid.Sources.Outside amb(
    redeclare package Medium = MediumA,
      nPorts=2) "Ambient conditions"

  Buildings.Fluid.HeatExchangers.DryCoilEffectivenessNTU heaCoi(
    redeclare package Medium1 = MediumW,
    redeclare package Medium2 = MediumA,
    Q_flow_nominal=QHeaAHU_flow_nominal,
    m1_flow_nominal=mHeaWat_flow_nominal,
    m2_flow_nominal=mHeaAir_flow_nominal,
    show_T=true,
    configuration=Buildings.Fluid.Types.HeatExchangerConfiguration.CounterFlow,
    dp1_nominal=3000,
    dp2_nominal=0,
    allowFlowReversal1=false,
    allowFlowReversal2=allowFlowReversal,
    T_a1_nominal=THeaWatInl_nominal,
    T_a2_nominal=THeaAirMix_nominal)
    "Heating coil"

  Fluid.HeatExchangers.WetCoilEffectivenessNTU cooCoi(
    redeclare package Medium1 = MediumW,
    redeclare package Medium2 = MediumA,
    use_Q_flow_nominal=true,
    Q_flow_nominal=QCooAHU_flow_nominal,
    m1_flow_nominal=mCooWat_flow_nominal,
    m2_flow_nominal=mCooAir_flow_nominal,
    dp2_nominal=0,
    dp1_nominal=3000,
    T_a1_nominal=TCooWatInl_nominal,
    T_a2_nominal=TCooAirMix_nominal,
    w_a2_nominal=wCooAirMix_nominal,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    allowFlowReversal1=false,
    allowFlowReversal2=allowFlowReversal,
    show_T=true) "Cooling coil"
  Buildings.Fluid.FixedResistances.PressureDrop dpRetDuc(
    m_flow_nominal=mAir_flow_nominal,
    redeclare package Medium = MediumA,
    allowFlowReversal=allowFlowReversal,
    dp_nominal=40) "Pressure drop for return duct"
  Buildings.Fluid.Movers.Preconfigured.SpeedControlled_y fanSup(
    redeclare package Medium = MediumA,
    m_flow_nominal=mAir_flow_nominal,
    dp_nominal=780 + 10 + dpBuiStaSet,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Supply air fan"

  Buildings.Fluid.Sensors.VolumeFlowRate senSupFlo(redeclare package Medium =
        MediumA, m_flow_nominal=mAir_flow_nominal)
    "Sensor for supply fan flow rate"

  Buildings.Fluid.Sensors.VolumeFlowRate senRetFlo(redeclare package Medium =
        MediumA, m_flow_nominal=mAir_flow_nominal)
    "Sensor for return fan flow rate"

  Modelica.Blocks.Routing.RealPassThrough TOut(y(
      final quantity="ThermodynamicTemperature",
      final unit="K",
      displayUnit="degC",
      min=0))
  Buildings.Fluid.Sensors.TemperatureTwoPort TSup(
    redeclare package Medium = MediumA,
    m_flow_nominal=mAir_flow_nominal,
    allowFlowReversal=allowFlowReversal)
  Buildings.Fluid.Sensors.RelativePressure dpDisSupFan(redeclare package Medium =
        MediumA) "Supply fan static discharge pressure"
  Buildings.Controls.SetPoints.OccupancySchedule occSch(occupancy=3600*{6,19})
    "Occupancy schedule"
  Buildings.Fluid.Sensors.TemperatureTwoPort TRet(
    redeclare package Medium = MediumA,
    m_flow_nominal=mAir_flow_nominal,
    allowFlowReversal=allowFlowReversal) "Return air temperature sensor"
  Buildings.Fluid.Sensors.TemperatureTwoPort TMix(
    redeclare package Medium = MediumA,
    m_flow_nominal=mAir_flow_nominal,
    allowFlowReversal=allowFlowReversal,
    transferHeat=true) "Mixed air temperature sensor"
  Buildings.Fluid.Sensors.VolumeFlowRate VOut1(redeclare package Medium =
        MediumA, m_flow_nominal=mAir_flow_nominal)
    "Outside air volume flow rate"

  BoundaryConditions.WeatherData.Bus weaBus "Weather data bus"

  Results res(
    final A=ATot,
    PFan=fanSup.P + 0,
    PPum=pumHeaCoi.P + pumCooCoi.P,
    PHea=heaCoi.Q2_flow + sum(VAVBox.terHea.Q2_flow),
    PCooSen=cooCoi.QSen2_flow,
    PCooLat=cooCoi.QLat2_flow) "Results of the simulation";

  Fluid.Actuators.Dampers.Exponential damRet(
    redeclare package Medium = MediumA,
    m_flow_nominal=mAir_flow_nominal,
    from_dp=false,
    strokeTime=15,
    dpDamper_nominal=5,
    dpFixed_nominal=5) "Return air damper"
  Fluid.Actuators.Dampers.Exponential damOut(
    redeclare package Medium = MediumA,
    m_flow_nominal=mAir_flow_nominal,
    from_dp=false,
    strokeTime=15,
    dpDamper_nominal=5,
    dpFixed_nominal=5) "Outdoor air damper"
  Fluid.FixedResistances.Junction splCooSup(
    redeclare package Medium = MediumW,
    m_flow_nominal=mCooWat_flow_nominal*{1,1,1},
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    dp_nominal(each displayUnit="Pa") = {0,0,0},
    portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving) "Flow splitter"
  Fluid.Actuators.Valves.TwoWayEqualPercentage valCooCoi(
    redeclare package Medium = MediumW,
    m_flow_nominal=mCooWat_flow_nominal,
    dpValve_nominal=6000,
    dpFixed_nominal=0) "Valve for cooling coil"
  Fluid.FixedResistances.Junction splCooRet(
    redeclare package Medium = MediumW,
    m_flow_nominal=mCooWat_flow_nominal*{1,1,1},
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    dp_nominal(each displayUnit="Pa") = {0,0,0},
    portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving) "Flow splitter"
  Fluid.Movers.Preconfigured.SpeedControlled_y pumCooCoi(
    redeclare package Medium = MediumW,
    m_flow_nominal=mCooWat_flow_nominal,
    dp_nominal=3000,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Supply air fan"
  Fluid.Movers.Preconfigured.SpeedControlled_y pumHeaCoi(
    redeclare package Medium = MediumW,
    m_flow_nominal=mHeaWat_flow_nominal,
    dp_nominal=3000,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial)
    "Pump for heating coil"

  Fluid.Actuators.Valves.TwoWayEqualPercentage valHeaCoi(
    redeclare package Medium = MediumW,
    m_flow_nominal=mHeaWat_flow_nominal,
    dpValve_nominal=6000,
    dpFixed_nominal=0) "Valve for heating coil"
  Fluid.FixedResistances.Junction splHeaRet(
    redeclare package Medium = MediumW,
    m_flow_nominal=mHeaWat_flow_nominal*{1,1,1},
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    dp_nominal(each displayUnit="Pa") = {0,0,0},
    portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving) "Flow splitter"
  Fluid.FixedResistances.Junction splHeaSup(
    redeclare package Medium = MediumW,
    m_flow_nominal=mHeaWat_flow_nominal*{1,1,1},
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    dp_nominal(each displayUnit="Pa") = {0,0,0},
    portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving) "Flow splitter"

  Modelica.Fluid.Interfaces.FluidPort_a portHeaCoiSup(redeclare package Medium =
        MediumW) "Heating coil loop supply"
  Modelica.Fluid.Interfaces.FluidPort_b portHeaCoiRet(redeclare package Medium =
        MediumW) "Heating coil loop return"
  Modelica.Fluid.Interfaces.FluidPort_a portHeaTerSup(redeclare package Medium =
        MediumW) "Terminal heat loop supply"
  Modelica.Fluid.Interfaces.FluidPort_b portHeaTerRet(redeclare package Medium =
        MediumW) "Terminal heat loop return"
  Modelica.Fluid.Interfaces.FluidPort_a portCooCoiSup(redeclare package Medium =
        MediumW) "Cooling coil loop supply"
  Modelica.Fluid.Interfaces.FluidPort_b portCooCoiRet(redeclare package Medium =
        MediumW)
    "Coolin coil loop return"
  Buildings.Examples.VAVReheat.BaseClasses.VAVReheatBox VAVBox[numZon](
    redeclare each package MediumA = MediumA,
    redeclare each package MediumW = MediumW,
    mCooAir_flow_nominal=mCooVAV_flow_nominal,
    mHeaAir_flow_nominal=mHeaVAV_flow_nominal,
    VRoo=VRoo,
    each allowFlowReversal=allowFlowReversal,
    each THeaWatInl_nominal=THeaWatInl_nominal,
    each THeaWatOut_nominal=THeaWatInl_nominal - 10,
    each THeaAirInl_nominal=285.15,
    each THeaAirDis_nominal=301.15) "VAV boxes"
  Buildings.Fluid.FixedResistances.Junction splRetRoo[numZon - 1](
    redeclare each package Medium = MediumA,
    each from_dp=false,
    each linearized=true,
    m_flow_nominal={{sum(mCooVAV_flow_nominal[i:numZon]),sum(
        mCooVAV_flow_nominal[(i + 1):numZon]),mCooVAV_flow_nominal[i]} for i in
            1:(numZon - 1)},
    each energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    each dp_nominal(each displayUnit="Pa") = {0,0,0},
    each portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    each portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    each portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering)
    "Splitter for room return air"
  Buildings.Fluid.FixedResistances.Junction splSupRoo[numZon - 1](
    redeclare each package Medium = MediumA,
    each from_dp=true,
    each linearized=true,
    m_flow_nominal={{sum(mCooVAV_flow_nominal[i:numZon]),sum(
        mCooVAV_flow_nominal[(i + 1):numZon]),mCooVAV_flow_nominal[i]} for i in
            1:(numZon - 1)},
    each energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    each dp_nominal(each displayUnit="Pa") = {0,0,0},
    each portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    each portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    each portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving)
    "Splitter for room supply air"

  Fluid.FixedResistances.PressureDrop dpSupDuc(
    m_flow_nominal=mAir_flow_nominal,
    redeclare package Medium = MediumA,
    allowFlowReversal=allowFlowReversal,
    dp_nominal=200 + 200 + 100 + 40) "Pressure drop for supply duct"

  Fluid.FixedResistances.Junction splRetOut(
    redeclare package Medium = MediumA,
    tau=15,
    m_flow_nominal=mAir_flow_nominal*{1,1,1},
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    dp_nominal(each displayUnit="Pa") = {0,0,0},
    portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    linearized=true)
    "Flow splitter"
protected
  constant Modelica.Units.SI.SpecificHeatCapacity cpAir=Buildings.Utilities.Psychrometrics.Constants.cpAir
    "Air specific heat capacity";
  constant Modelica.Units.SI.SpecificHeatCapacity cpWat=Buildings.Utilities.Psychrometrics.Constants.cpWatLiq
    "Water specific heat capacity";
  model Results "Model to store the results of the simulation"
    parameter Modelica.Units.SI.Area A "Floor area";
    input Modelica.Units.SI.Power PFan "Fan energy";
    input Modelica.Units.SI.Power PPum "Pump energy";
    input Modelica.Units.SI.Power PHea "Heating energy";
    input Modelica.Units.SI.Power PCooSen "Sensible cooling energy";
    input Modelica.Units.SI.Power PCooLat "Latent cooling energy";

    Real EFan(
      unit="J/m2",
      start=0,
      nominal=1E5,
      fixed=true) "Fan energy";
    Real EPum(
      unit="J/m2",
      start=0,
      nominal=1E5,
      fixed=true) "Pump energy";
    Real EHea(
      unit="J/m2",
      start=0,
      nominal=1E5,
      fixed=true) "Heating energy";
    Real ECooSen(
      unit="J/m2",
      start=0,
      nominal=1E5,
      fixed=true) "Sensible cooling energy";
    Real ECooLat(
      unit="J/m2",
      start=0,
      nominal=1E5,
      fixed=true) "Latent cooling energy";
    Real ECoo(unit="J/m2") "Total cooling energy";
  equation

    A*der(EFan) = PFan;
    A*der(EPum) = PPum;
    A*der(EHea) = PHea;
    A*der(ECooSen) = PCooSen;
    A*der(ECooLat) = PCooLat;
    ECoo = ECooSen + ECooLat;

  end Results;
equation
  connect(fanSup.port_b, dpDisSupFan.port_a)
  connect(TSup.port_a, fanSup.port_b)
  connect(amb.ports[1], VOut1.port_a)
  connect(weaBus.TDryBul, TOut.u)
  connect(amb.weaBus, weaBus)

  connect(senRetFlo.port_a, dpRetDuc.port_b)
  connect(TSup.port_b, senSupFlo.port_a)
  connect(dpDisSupFan.port_b, amb.ports[2])
  connect(senRetFlo.port_b, TRet.port_a)
  connect(TMix.port_b, heaCoi.port_a2)
  connect(heaCoi.port_b2, cooCoi.port_a2)
  connect(VOut1.port_b, damOut.port_a)
  connect(damRet.port_a, TRet.port_b)
  connect(pumHeaCoi.port_b, heaCoi.port_a1)
  connect(cooCoi.port_b1,pumCooCoi. port_a)
  connect(splCooSup.port_2, cooCoi.port_a1)
  connect(splCooRet.port_3,splCooSup. port_3)
  connect(pumCooCoi.port_b, splCooRet.port_2)
  connect(splHeaSup.port_2, pumHeaCoi.port_a)
  connect(heaCoi.port_b1, splHeaRet.port_2)
  connect(splHeaRet.port_3, splHeaSup.port_3)
  connect(splHeaSup.port_1, valHeaCoi.port_b)
  connect(splCooSup.port_1, valCooCoi.port_b)
  connect(portHeaCoiSup, valHeaCoi.port_a)
  connect(portHeaCoiRet, splHeaRet.port_1)
  connect(portCooCoiSup, valCooCoi.port_a)
  connect(portCooCoiRet, splCooRet.port_1)

  connect(VAVBox.port_bAir, port_supAir)

  for i in 1:numZon loop
    connect(VAVBox[i].port_aHeaWat, portHeaTerSup)
    connect(VAVBox[i].port_bHeaWat, portHeaTerRet)
  end for;

  connect(splSupRoo[1].port_1, senSupFlo.port_b)
  connect(splRetRoo[1].port_1, dpRetDuc.port_a)
  connect(splSupRoo.port_3, VAVBox[1:(numZon-1)].port_aAir)
  connect(splRetRoo.port_3, port_retAir[1:(numZon-1)])

  for i in 1:(numZon - 2) loop
      connect(splSupRoo[i].port_2, splSupRoo[i+1].port_1)
      connect(splRetRoo[i].port_2, splRetRoo[i+1].port_1)
  end for;
  connect(splSupRoo[numZon-1].port_2, VAVBox[numZon].port_aAir);
  connect(splRetRoo[numZon-1].port_2, port_retAir[numZon]);

  connect(cooCoi.port_b2, dpSupDuc.port_a)
  connect(dpSupDuc.port_b, fanSup.port_a)
  connect(damOut.port_b, splRetOut.port_1)
  connect(splRetOut.port_2, TMix.port_a)
  connect(damRet.port_b, splRetOut.port_3)
end PartialHVAC;


Room leakage model

within Buildings.Examples.VAVReheat.BaseClasses;
model RoomLeakage "Room leakage model"
  extends Buildings.BaseClasses.BaseIcon;
  replaceable package Medium = Modelica.Media.Interfaces.PartialMedium
    "Medium in the component"
  parameter Modelica.Units.SI.Volume VRoo "Room volume";
  parameter Boolean use_windPressure=false
    "Set to true to enable wind pressure"
  Buildings.Fluid.FixedResistances.PressureDrop res(
    redeclare package Medium = Medium,
    dp_nominal=50,
    m_flow_nominal=VRoo*1.2/3600) "Resistance model"
  Modelica.Fluid.Interfaces.FluidPort_b port_b(redeclare package Medium =
        Medium)
  Buildings.Fluid.Sources.Outside_CpLowRise
                        amb(redeclare package Medium = Medium, nPorts=1,
    s=s,
    azi=azi,
    Cp0=if use_windPressure then 0.6 else 0)
  BoundaryConditions.WeatherData.Bus weaBus "Bus with weather data"
  Buildings.Fluid.Sensors.MassFlowRate senMasFlo1(redeclare package Medium = Medium,
      allowFlowReversal=true) "Sensor for mass flow rate"
  Modelica.Blocks.Math.Gain ACHInf(k=1/VRoo/1.2*3600, y(unit="1/h"))
    "Air change per hour due to infiltration"
  parameter Real s "Side ratio, s=length of this wall/length of adjacent wall";
  parameter Modelica.Units.SI.Angle azi "Surface azimuth (South:0, West:pi/2)";

equation
  connect(res.port_b, port_b)
  connect(amb.weaBus, weaBus)
  connect(amb.ports[1], senMasFlo1.port_a)
  connect(senMasFlo1.port_b, res.port_a)
  connect(senMasFlo1.m_flow, ACHInf.u)
end RoomLeakage;


within Buildings.Examples.VAVReheat.BaseClasses;
model VAVReheatBox "Supply box of a VAV system with a hot water reheat coil"
  extends Modelica.Blocks.Icons.Block;
  replaceable package MediumA = Modelica.Media.Interfaces.PartialMedium
    "Medium model for air"
  replaceable package MediumW = Modelica.Media.Interfaces.PartialMedium
    "Medium model for water"

  parameter Boolean allowFlowReversal=true
    "= false to simplify equations, assuming, but not enforcing, no flow reversal";
  parameter Modelica.Units.SI.MassFlowRate mCooAir_flow_nominal
    "Nominal air mass flow rate from cooling sizing calculations";
  parameter Modelica.Units.SI.MassFlowRate mHeaAir_flow_nominal
    "Nominal air mass flow rate from heating sizing calculations";
  final parameter Modelica.Units.SI.MassFlowRate mHeaWat_flow_nominal=
      QHea_flow_nominal/(cpWatLiq*(THeaWatInl_nominal - THeaWatOut_nominal))
    "Nominal mass flow rate of hot water to reheat coil";
  parameter Modelica.Units.SI.Volume VRoo "Room volume";
  parameter Modelica.Units.SI.Temperature THeaWatInl_nominal(start=55 + 273.15,
      displayUnit="degC") "Reheat coil nominal inlet water temperature";
  parameter Modelica.Units.SI.Temperature THeaWatOut_nominal(start=
        THeaWatInl_nominal - 10, displayUnit="degC")
    "Reheat coil nominal outlet water temperature";
  parameter Modelica.Units.SI.Temperature THeaAirInl_nominal(start=12 + 273.15,
      displayUnit="degC")
    "Inlet air nominal temperature into VAV box during heating";
  parameter Modelica.Units.SI.Temperature THeaAirDis_nominal(start=28 + 273.15,
      displayUnit="degC")
    "Discharge air temperature from VAV box during heating";
  parameter Modelica.Units.SI.HeatFlowRate QHea_flow_nominal=
      mHeaAir_flow_nominal * cpAir * (THeaAirDis_nominal-THeaAirInl_nominal)
    "Nominal heating heat flow rate";
  Modelica.Fluid.Interfaces.FluidPort_a port_aAir(
    redeclare package Medium=MediumA)
    "Fluid connector a1 (positive design flow direction is from port_a1 to port_b1)"
  Modelica.Fluid.Interfaces.FluidPort_a port_bAir(
    redeclare package Medium=MediumA)
    "Fluid connector b (positive design flow direction is from port_a1 to port_b1)"
  Modelica.Blocks.Interfaces.RealInput yVAV(final unit="1")
    "Signal for VAV damper"
  Modelica.Blocks.Interfaces.RealOutput y_actual(final unit="1")
  "Actual VAV damper position"
  Modelica.Blocks.Interfaces.RealOutput yVal_actual(final unit="1")
    "Actual valve position"
  Modelica.Fluid.Interfaces.FluidPort_a port_aHeaWat(redeclare package Medium =
      MediumW) "Hot water inlet port"
  Modelica.Fluid.Interfaces.FluidPort_b port_bHeaWat(redeclare package Medium =
      MediumW) "Hot water outlet port"
  Modelica.Blocks.Interfaces.RealOutput TSup(
     final unit = "K",
     displayUnit = "degC")
     "Supply Air Temperature"
  Modelica.Blocks.Interfaces.RealOutput VSup_flow(
    final unit="m3/s")
    "Supply Air Volumetric Flow Rate"
  Buildings.Fluid.Actuators.Dampers.Exponential vav(
    redeclare package Medium = MediumA,
    m_flow_nominal=mCooAir_flow_nominal,
    dpDamper_nominal=20,
    allowFlowReversal=allowFlowReversal,
    dpFixed_nominal=130) "VAV box for room"
  Buildings.Fluid.HeatExchangers.DryCoilEffectivenessNTU terHea(
    redeclare package Medium1 = MediumW,
    redeclare package Medium2 = MediumA,
    m1_flow_nominal=mHeaWat_flow_nominal,
    m2_flow_nominal=mHeaAir_flow_nominal,
    Q_flow_nominal=QHea_flow_nominal,
    configuration=Buildings.Fluid.Types.HeatExchangerConfiguration.CounterFlow,
    dp1_nominal=0,
    from_dp2=true,
    dp2_nominal=0,
    allowFlowReversal1=false,
    allowFlowReversal2=allowFlowReversal,
    T_a1_nominal=THeaWatInl_nominal,
    T_a2_nominal=THeaAirInl_nominal) "Reheat coil"

  Fluid.Sensors.TemperatureTwoPort senTem(
    redeclare package Medium = MediumA,
    initType=Modelica.Blocks.Types.Init.InitialState,
    m_flow_nominal=mCooAir_flow_nominal,
    allowFlowReversal=allowFlowReversal) "Supply air temperature sensor"
  Fluid.Sensors.VolumeFlowRate senVolFlo(
    redeclare package Medium = MediumA,
    initType=Modelica.Blocks.Types.Init.InitialState,
    m_flow_nominal=mCooAir_flow_nominal,
    allowFlowReversal=allowFlowReversal)
    "Supply air volumetric flow rate sensor"
  Fluid.Actuators.Valves.TwoWayEqualPercentage val(
    redeclare package Medium = MediumW,
    allowFlowReversal=false,
    m_flow_nominal=mHeaWat_flow_nominal,
    from_dp=true,
    dpValve_nominal=3000,
    use_strokeTime=false,
    dpFixed_nominal=3000) "Valve for terminal heater"
  Modelica.Blocks.Interfaces.RealInput yHea
    "Actuator position for heating valve (0: closed, 1: open)"
  Real ACH(unit="1/h") = VSup_flow/VRoo*3600 "Air changes per hour";
protected
  constant Modelica.Units.SI.SpecificHeatCapacity cpAir=Buildings.Utilities.Psychrometrics.Constants.cpAir
    "Air specific heat capacity";
  constant Modelica.Units.SI.SpecificHeatCapacity cpWatLiq=Buildings.Utilities.Psychrometrics.Constants.cpWatLiq
    "Water specific heat capacity";
equation
  connect(vav.y, yVAV)
  connect(vav.y_actual, y_actual)
  connect(port_aAir, terHea.port_a2)
  connect(vav.port_a, terHea.port_b2)
  connect(port_bHeaWat, terHea.port_b1)
  connect(vav.port_b, senTem.port_a)
  connect(senTem.port_b, senVolFlo.port_a)
  connect(senVolFlo.port_b, port_bAir)
  connect(senVolFlo.V_flow, VSup_flow)
  connect(senTem.T, TSup)
  connect(port_aHeaWat, val.port_a)
  connect(val.port_b, terHea.port_a1)
  connect(yHea, val.y)
  connect(val.y_actual, yVal_actual)
end VAVReheatBox;


Computes the duct static pressure setpoint.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
model DuctStaticPressureSetpoint "Computes the duct static pressure setpoint"
  extends Modelica.Blocks.Interfaces.MISO;
  parameter Modelica.Units.SI.AbsolutePressure pMin(displayUnit="Pa") = 100
    "Minimum duct static pressure setpoint";
  parameter Modelica.Units.SI.AbsolutePressure pMax(displayUnit="Pa") = 410
    "Maximum duct static pressure setpoint";
  parameter Real k=0.1 "Gain of controller";
  parameter Modelica.Units.SI.Time Ti=60 "Time constant of integrator block";
  parameter Modelica.Units.SI.Time Td=60 "Time constant of derivative block";
  parameter Modelica.Blocks.Types.SimpleController controllerType=Modelica.Blocks.Types.SimpleController.PI
    "Type of controller";
  Buildings.Controls.Continuous.LimPID limPID(
    controllerType=controllerType,
    k=k,
    Ti=Ti,
    Td=Td,
    initType=Modelica.Blocks.Types.Init.InitialState,
    reverseActing=false)
protected
  Buildings.Utilities.Math.Max max(final nin=nin)
  Modelica.Blocks.Sources.Constant ySet(k=0.9)
    "Setpoint for maximum damper position"
  Modelica.Blocks.Math.Add dp(final k2=-1) "Pressure difference"
  Modelica.Blocks.Sources.Constant pMaxSig(k=pMax)
  Modelica.Blocks.Sources.Constant pMinSig(k=pMin)
  Modelica.Blocks.Math.Add pSet "Pressure setpoint"
  Modelica.Blocks.Math.Product product
equation
  connect(max.u, u)
  connect(ySet.y, limPID.u_s)
  connect(max.y, limPID.u_m)
  connect(limPID.y, product.u1)
  connect(pMaxSig.y, dp.u1)
  connect(pMinSig.y, dp.u2)
  connect(dp.y, product.u2)
  connect(pMinSig.y, pSet.u2)
  connect(pSet.y, y)
  connect(product.y, pSet.u1)
end DuctStaticPressureSetpoint;


Controller for economizer. This is a controller for an economizer, that adjusts the mixed air dampers
to fulfill three control functions.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
block Economizer "Controller for economizer"
  import Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes;
  parameter Boolean have_reset = false
    "Set to true to reset the outdoor air damper controllers with the enable signal"
  parameter Boolean have_frePro = false
    "Set to true to enable freeze protection (mixed air low temperature control)";
  parameter Modelica.Units.SI.Temperature TFreSet=277.15
    "Lower limit of mixed air temperature for freeze protection"
  parameter Modelica.Units.SI.TemperatureDifference dTLock(final min=0.1) = 1
    "Temperature difference between return and outdoor air for economizer lockout";
  parameter Modelica.Units.SI.VolumeFlowRate VOut_flow_min(min=0)
    "Minimum outside air volume flow rate";
  parameter Modelica.Blocks.Types.SimpleController controllerType=Modelica.Blocks.Types.SimpleController.PI
    "Type of controller";
  parameter Real k = 0.05 "Gain of controller";
  parameter Modelica.Units.SI.Time Ti=120 "Time constant of integrator block";
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput uEna
    "Enable signal for economizer"
  ControlBus controlBus "Control bus"
  Modelica.Blocks.Interfaces.RealInput uOATSup
    "Control signal for outdoor air damper from supply temperature controller"
  Modelica.Blocks.Interfaces.RealInput TMix if have_frePro
    "Measured mixed air temperature"
  Modelica.Blocks.Interfaces.RealInput VOut_flow
    "Measured outside air flow rate"
  Modelica.Blocks.Interfaces.RealInput TRet "Return air temperature"
  Modelica.Blocks.Interfaces.RealOutput yRet
    "Control signal for return air damper"
  Modelica.Blocks.Interfaces.RealOutput yOA
    "Control signal for outside air damper"
  Modelica.Blocks.Math.Gain gain(k=1/VOut_flow_min) "Normalize mass flow rate"
  Buildings.Controls.OBC.CDL.Reals.PID conV_flow(
    controllerType=controllerType,
    k=k,
    Ti=Ti,
    yMax=1,
    yMin=0,
    Td=60)
    "Controller for outside air flow rate"
  Modelica.Blocks.Sources.Constant uni(k=1) "Unity signal"
  Modelica.Blocks.Sources.Constant closed(k=0) "Signal to close OA damper"
  Buildings.Controls.OBC.CDL.Reals.PID yOATFre(
    controllerType=controllerType,
    k=k,
    Ti=Ti,
    Td=60,
    yMax=1,
    yMin=0,
    reverseActing=false) if have_frePro
    "Controller of outdoor damper to track freeze temperature setpoint"
  Buildings.Controls.OBC.CDL.Reals.Min minFrePro
    "Takes lower signal (limits damper opening for freeze protection)"
  Modelica.Blocks.Sources.Constant TFre(k=TFreSet)
    "Setpoint for freeze protection"
  Buildings.Controls.OBC.CDL.Reals.Subtract invSig
    "Invert control signal for interlocked damper"
  Modelica.Blocks.Logical.Hysteresis hysLoc(final uLow=0, final uHigh=dTLock)
    "Hysteresis for economizer lockout"
  Modelica.Blocks.Math.Feedback feedback
  Buildings.Controls.OBC.CDL.Reals.Switch swiOA
    "Switch to close outdoor air damper"
  Modelica.Blocks.Sources.Constant one(k=1) if not have_frePro
    "Fill value in case freeze protection is disabled"
  Buildings.Controls.OBC.CDL.Reals.Switch swiModClo
    "Switch between modulating or closing outdoor air damper"
  Buildings.Controls.OBC.CDL.Reals.Max maxOutDam
    "Select larger of the outdoor damper signals"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant conOne(
    final k=1)
    "Constant 1"
equation
  connect(VOut_flow, gain.u)
  connect(gain.y, conV_flow.u_m)
  connect(uni.y, conV_flow.u_s)
  connect(yOATFre.y, minFrePro.u1)
  connect(yRet, invSig.y)
  connect(feedback.y, hysLoc.u)
  connect(TRet, feedback.u1)
  connect(controlBus.TOut, feedback.u2)
  connect(closed.y, swiOA.u3)
  connect(hysLoc.y, swiOA.u2)
  connect(uOATSup, swiOA.u1)
  connect(one.y, minFrePro.u1)
  connect(yOATFre.u_s, TFre.y)
  connect(TMix, yOATFre.u_m)
  connect(swiModClo.y, yOA)
  connect(uEna, swiModClo.u2)
  connect(closed.y, swiModClo.u3)
  connect(maxOutDam.u1, swiOA.y)
  connect(conV_flow.y, maxOutDam.u2)
  connect(minFrePro.y, swiModClo.u1)
  connect(maxOutDam.y, minFrePro.u2)
  connect(swiModClo.y, invSig.u2)
  connect(conOne.y, invSig.u1)
end Economizer;


Controller for fan revolution. PI controller for the fan speed. The controller outputs y = 0 if the fan control signal is off, e.g., if uFan = false.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
block FanVFD "Controller for fan revolution"
  extends Modelica.Blocks.Interfaces.SISO;
  import Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes;
  Buildings.Controls.OBC.CDL.Reals.PIDWithReset con(
    r=xSet_nominal,
    yMax=1,
    Td=60,
    yMin=r_N_min,
    k=k,
    Ti=Ti,
    controllerType=controllerType)
    "Controller"
  parameter Real xSet_nominal "Nominal setpoint (used for normalization)";
  Modelica.Blocks.Sources.Constant off(k=0) "Off signal"
  Modelica.Blocks.Interfaces.RealInput u_m
    "Connector of measurement input signal"
  parameter Real r_N_min=0.01 "Minimum normalized fan speed";
  parameter Modelica.Blocks.Types.Init initType=Modelica.Blocks.Types.Init.NoInit
    "Type of initialization (1: no init, 2: steady state, 3/4: initial output)";
  parameter Real y_start=0 "Initial or guess value of output (= state)";

  parameter Modelica.Blocks.Types.SimpleController
    controllerType=Modelica.Blocks.Types.SimpleController.PI
    "Type of controller"
  parameter Real k=0.5 "Gain of controller"
  parameter Modelica.Units.SI.Time Ti=15 "Time constant of integrator block"

  Buildings.Controls.OBC.CDL.Reals.Switch swi
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput uFan
    "Set to true to enable the fan on"
equation
  connect(con.y, swi.u1)
  connect(off.y, swi.u3)
  connect(swi.u2, uFan)
  connect(swi.y, y)
  connect(con.trigger, uFan)
  connect(con.u_s, u)
  connect(u_m, con.u_m)
end FanVFD;


Freeze thermostat with timed lockout. Freeze stat that outputs true if freeze protection should be engaged.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
model FreezeStat "Freeze thermostat with timed lockout"

  parameter Real lockoutTime(
    final quantity="Time",
    final unit="s",
    displayUnit="min",
    min=60) = 900
    "Delay time";

  parameter Real TSet(
    final quantity="ThermodynamicTemperature",
    final unit="K",
    displayUnit="degC") = 276.15 "Temperature below which the freeze protection starts";

  Buildings.Controls.OBC.CDL.Interfaces.RealInput u(
    final quantity="ThermodynamicTemperature",
    final unit="K",
    displayUnit="degC")
    "Connector of Real input signal used as measurement signal"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y
    "Connector of Real output signal used as actuator signal"

  Buildings.Controls.OBC.CDL.Logical.TrueDelay locOut(final delayTime=
        lockoutTime) "If freeze stat triggers, keep it on for specified time"
  Buildings.Controls.OBC.CDL.Logical.Not freStaSig "Signal for freeze stat"
  Buildings.Controls.OBC.CDL.Reals.GreaterThreshold greThr(
    final t=TSet,
    final h=0.5)
    "Greater comparison"
equation
  connect(locOut.y, freStaSig.u)
  connect(freStaSig.y, y)
  connect(greThr.y, locOut.u)
  connect(greThr.u, u)
end FreezeStat;


Finite State Machine for the operational modes.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
model ModeSelector "Finite State Machine for the operational modes"
  Modelica.StateGraph.InitialStepWithSignal initialStepWithSignal(nIn=0, nOut=1)
  Modelica.StateGraph.Transition start "Starts the system"
  State unOccOff(
    mode=Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.unoccupiedOff,
    nIn=3,
    nOut=4) "Unoccupied off mode, no coil protection"
  State unOccNigSetBac(
    nOut=2,
    mode=Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.unoccupiedNightSetBack,
    nIn=1) "Unoccupied night set back"
  Modelica.StateGraph.Transition t2(
    enableTimer=true,
    waitTime=60,
    condition=TRooMinErrHea.y > delTRooOnOff/2)
  parameter Modelica.Units.SI.TemperatureDifference delTRooOnOff(min=0.1) = 1
    "Deadband in room temperature between occupied on and occupied off";
  parameter Modelica.Units.SI.Temperature TRooSetHeaOcc=293.15
    "Set point for room air temperature during heating mode";
  parameter Modelica.Units.SI.Temperature TRooSetCooOcc=299.15
    "Set point for room air temperature during cooling mode";
  Modelica.StateGraph.Transition t1(condition=delTRooOnOff/2 < -TRooMinErrHea.y,
    enableTimer=true,
    waitTime=30*60)
  inner Modelica.StateGraph.StateGraphRoot stateGraphRoot
  ControlBus cb
  Modelica.Blocks.Routing.RealPassThrough TRooSetHea
    "Current heating setpoint temperature"
  State morWarUp(mode=Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.unoccupiedWarmUp,
                                                                            nIn=2,
    nOut=1) "Morning warm up"
  Modelica.StateGraph.TransitionWithSignal t6(enableTimer=true, waitTime=60)
  Modelica.Blocks.Logical.LessEqualThreshold occThrSho(threshold=1800)
    "Signal to allow transition into morning warmup"
  Modelica.StateGraph.TransitionWithSignal t5
  State occ(
    mode=Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.occupied,
    nIn=3,
    nOut=1)
    "Occupied mode"
  Modelica.Blocks.Routing.RealPassThrough TRooMin
  Modelica.Blocks.Math.Feedback TRooMinErrHea "Room control error for heating"
  Modelica.StateGraph.Transition t3(condition=TRooMin.y + delTRooOnOff/2 >
        TRooSetHeaOcc or occupied.y)
  Modelica.Blocks.Routing.BooleanPassThrough occupied
    "outputs true if building is occupied"
  Modelica.StateGraph.TransitionWithSignal t4(enableTimer=false)
  State morPreCoo(
    nIn=1,
    mode=Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.unoccupiedPreCool,
    nOut=1) "Pre-cooling mode"
  Modelica.StateGraph.Transition t7(condition=TRooMin.y - delTRooOnOff/2 <
        TRooSetCooOcc or occupied.y)
  Modelica.Blocks.Routing.RealPassThrough TRooAve "Average room temperature"
  Modelica.Blocks.Sources.BooleanExpression booleanExpression(
    y=occThrSho.y and (TRooAve.y < TRooSetHeaOcc))
    "Test that outputs true if room temperature is below occupied heating and system should be switched on soon"
  PreCoolingStarter preCooSta(TRooSetCooOcc=TRooSetCooOcc)
    "Model to start pre-cooling"
  Modelica.StateGraph.TransitionWithSignal t9
  Modelica.Blocks.Logical.Not not1
  Modelica.Blocks.Logical.And and2
  Modelica.Blocks.Logical.Not not2
  Modelica.StateGraph.TransitionWithSignal t8
    "changes to occupied in case precooling is deactivated"
  Modelica.Blocks.MathInteger.Sum sum(nu=6)
  Modelica.Blocks.Interfaces.BooleanOutput yFan
    "True if the fans are to be switched on"
  Modelica.Blocks.MathBoolean.Or or1(nu=4)
  Modelica.Blocks.Interfaces.BooleanOutput yEco
    "True if the economizer is enabled"
  Modelica.Blocks.MathBoolean.Or or2(nu=2) "Occupied or pre-cool mode"
  Buildings.Controls.OBC.CDL.Logical.And and3
    "(Occupied or pre-cool mode) and fan on"
  Modelica.Blocks.Math.BooleanToInteger modIni(integerTrue=Integer(Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.unoccupiedOff))
    "Initial operation mode"
equation
  connect(start.outPort, unOccOff.inPort[1])
  connect(initialStepWithSignal.outPort[1], start.inPort)
  connect(unOccOff.outPort[1], t2.inPort)
  connect(t2.outPort, unOccNigSetBac.inPort[1])
  connect(unOccNigSetBac.outPort[1], t1.inPort)
  connect(t1.outPort, unOccOff.inPort[2])
  connect(cb.dTNexOcc, occThrSho.u)
  connect(t6.outPort, morWarUp.inPort[1])
  connect(t5.outPort, morWarUp.inPort[2])
  connect(unOccNigSetBac.outPort[2], t5.inPort)
  connect(cb.TRooMin, TRooMin.u)
  connect(TRooSetHea.y, TRooMinErrHea.u1)
  connect(TRooMin.y, TRooMinErrHea.u2)
  connect(unOccOff.outPort[2], t6.inPort)
  connect(morWarUp.outPort[1], t3.inPort)
  connect(cb.occupied, occupied.u)
  connect(occ.outPort[1], t4.inPort)
  connect(t4.outPort, unOccOff.inPort[3])
  connect(cb.TRooAve, TRooAve.u)
  connect(preCooSta.y, t9.condition)
  connect(t9.outPort, morPreCoo.inPort[1])
  connect(unOccOff.outPort[3], t9.inPort)
  connect(cb, preCooSta.controlBus)
  connect(morPreCoo.outPort[1], t7.inPort)
  connect(t7.outPort, occ.inPort[2])
  connect(t3.outPort, occ.inPort[1])
  connect(occThrSho.y, not1.u)
  connect(not1.y, and2.u2)
  connect(and2.y, t4.condition)
  connect(occupied.y, not2.u)
  connect(not2.y, and2.u1)
  connect(cb.TRooSetHea, TRooSetHea.u)
  connect(t8.outPort, occ.inPort[3])
  connect(unOccOff.outPort[4], t8.inPort)
  connect(occupied.y, t8.condition)
  connect(morPreCoo.y, sum.u[1])
  connect(morWarUp.y, sum.u[2])
  connect(occ.y, sum.u[3])
  connect(unOccOff.y, sum.u[4])
  connect(unOccNigSetBac.y, sum.u[5])
  connect(yFan, or1.y)
  connect(unOccNigSetBac.active, or1.u[1])
  connect(occ.active, or1.u[2])
  connect(morWarUp.active, or1.u[3])
  connect(morPreCoo.active, or1.u[4])
  connect(yEco, and3.y)
  connect(or1.y, and3.u1)
  connect(or2.y, and3.u2)
  connect(occ.active, or2.u[1])
  connect(morPreCoo.active, or2.u[2])
  connect(initialStepWithSignal.active, modIni.u)
  connect(sum.y, cb.controlMode)
  connect(modIni.y, sum.u[6])
  connect(t6.condition, booleanExpression.y)
  connect(t5.condition, booleanExpression.y)
end ModeSelector;


Package with controller models

within Buildings.Examples.VAVReheat.BaseClasses;
package Controls "Package with controller models"
    extends Modelica.Icons.VariantsPackage;
end Controls;


Outputs true when precooling should start.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
block PreCoolingStarter "Outputs true when precooling should start"
  extends Modelica.Blocks.Interfaces.BooleanSignalSource;
  parameter Modelica.Units.SI.Temperature TOutLim=286.15
    "Limit for activating precooling";
  parameter Modelica.Units.SI.Temperature TRooSetCooOcc
    "Set point for room air temperature during cooling mode";
  BaseClasses.Controls.ControlBus controlBus
  Modelica.Blocks.Logical.GreaterThreshold greater(threshold=TRooSetCooOcc)
  Modelica.Blocks.Logical.LessThreshold greater2(threshold=1800)
  Modelica.Blocks.Logical.LessThreshold greater1(threshold=TOutLim)
  Modelica.Blocks.MathBoolean.And and3(nu=3)
equation
  connect(controlBus.dTNexOcc, greater2.u)
  connect(controlBus.TRooAve, greater.u)
  connect(controlBus.TOut, greater1.u)
  connect(and3.y, y)
  connect(greater.y, and3.u[1])
  connect(greater1.y, and3.u[2])
  connect(greater2.y, and3.u[3])
end PreCoolingStarter;


Set point scheduler for room temperature.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
block RoomTemperatureSetpoint "Set point scheduler for room temperature"
  extends Modelica.Blocks.Icons.Block;
  import Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes;
  parameter Modelica.Units.SI.Temperature THeaOn=293.15
    "Heating setpoint during on";
  parameter Modelica.Units.SI.Temperature THeaOff=285.15
    "Heating setpoint during off";
  parameter Modelica.Units.SI.Temperature TCooOn=297.15
    "Cooling setpoint during on";
  parameter Modelica.Units.SI.Temperature TCooOff=303.15
    "Cooling setpoint during off";
  BaseClasses.Controls.ControlBus controlBus
  Modelica.Blocks.Routing.IntegerPassThrough mode
  Modelica.Blocks.Sources.RealExpression setPoiHea(
     y(final unit="K", displayUnit="degC")=if (mode.y == Integer(OperationModes.occupied) or mode.y == Integer(OperationModes.unoccupiedWarmUp)
         or mode.y == Integer(OperationModes.safety)) then THeaOn else THeaOff)
  Modelica.Blocks.Sources.RealExpression setPoiCoo(
    y(final unit="K", displayUnit="degC")=if (mode.y == Integer(OperationModes.occupied) or
          mode.y == Integer(OperationModes.unoccupiedPreCool) or
          mode.y == Integer(OperationModes.safety)) then TCooOn else TCooOff)
    "Cooling setpoint"
equation
  connect(controlBus.controlMode,mode. u)
  connect(setPoiHea.y, controlBus.TRooSetHea)
  connect(setPoiCoo.y, controlBus.TRooSetCoo)
end RoomTemperatureSetpoint;


Controller for room VAV box. Controller for terminal VAV box with hot water reheat.
It is based on the control logic \"dual maximum with constant volume heating\" as
described in the Advanced VAV System Design Guide (EDR 2007).
The controller could be used for units with the pressure independent damper and the
units with the exponential damper, by setting the flag have_preIndDam.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
block RoomVAV "Controller for room VAV box"
  extends Modelica.Blocks.Icons.Block;

  parameter Boolean have_preIndDam = true
    "True: the terminal box has pressure independent damper";
  parameter Real ratVFloMin(final unit="1") = 0.3
    "Minimum airflow set point (ratio to nominal)";
  parameter Real ratVFloHea(final unit="1") = ratVFloMin
    "Heating airflow set point (ratio to nominal)";
  parameter Modelica.Units.SI.VolumeFlowRate V_flow_nominal(min=0)=0.1
    "Norminal air volume flow rate"
  parameter Buildings.Controls.OBC.CDL.Types.SimpleController cooController=
    Buildings.Controls.OBC.CDL.Types.SimpleController.PI "Type of controller"
  parameter Real kCoo=0.1 "Gain of controller"
  parameter Modelica.Units.SI.Time TiCoo=120
    "Time constant of integrator block"
  parameter Modelica.Units.SI.Time TdCoo=60 "Time constant of derivative block"
  parameter Buildings.Controls.OBC.CDL.Types.SimpleController heaController=
      Buildings.Controls.OBC.CDL.Types.SimpleController.PI "Type of controller"
  parameter Real kHea=0.1 "Gain of controller"
  parameter Modelica.Units.SI.Time TiHea=120
    "Time constant of integrator block"
  parameter Modelica.Units.SI.Time TdHea=60 "Time constant of derivative block"
  parameter Buildings.Controls.OBC.CDL.Types.SimpleController damController=
      Buildings.Controls.OBC.CDL.Types.SimpleController.PI "Type of damper position controller"
  parameter Real kDam=0.1 "Gain of controller"
  parameter Modelica.Units.SI.Time TiDam=120
    "Time constant of integrator block"
  parameter Modelica.Units.SI.Time TdDam=60 "Time constant of derivative block"

  Buildings.Controls.OBC.CDL.Interfaces.RealInput TRooHeaSet(
    final quantity="ThermodynamicTemperature",
    final unit = "K",
    displayUnit = "degC")
    "Setpoint temperature for room for heating"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TRooCooSet(
    final quantity="ThermodynamicTemperature",
    final unit = "K",
    displayUnit = "degC")
    "Setpoint temperature for room for cooling"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TRoo(
    final quantity="ThermodynamicTemperature",
    final unit = "K",
    displayUnit = "degC")
    "Measured room temperature"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput VDis_flow(
    final min=0,
    final unit="m3/s",
    final quantity="VolumeFlowRate") if not have_preIndDam
    "Measured primary discharge airflow rate"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yDam "Signal for VAV damper"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yVal "Signal for heating coil valve"

  Buildings.Controls.OBC.CDL.Reals.PID conHea(
    yMax=yMax,
    Td=TdHea,
    yMin=yMin,
    k=kHea,
    Ti=TiHea,
    controllerType=heaController,
    Ni=10) "Controller for heating"
  Buildings.Controls.OBC.CDL.Reals.PID conCoo(
    yMax=yMax,
    Td=TdCoo,
    k=kCoo,
    Ti=TiCoo,
    controllerType=cooController,
    yMin=yMin,
    reverseActing=false) "Controller for cooling (acts on damper)"
  Buildings.Controls.OBC.CDL.Reals.Line reqFlo "Required flow rate"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant cooMax(k=1)
    "Cooling maximum flow"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant minFloCoo(
    final k=ratVFloMin) "Minimum air flow set point"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant conOne(k=1)
    "Constant 1"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant conZer(k=0)
    "Constant 0"

  Buildings.Controls.OBC.CDL.Reals.Hysteresis hysWitHol(
    final uLow=-dTHys,
    final uHigh=0)
    "Output true if room temperature below heating set point"
  Buildings.Controls.OBC.CDL.Reals.Subtract dTHea
    "Heating loop control error"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant minFloHea(
    final k=ratVFloHea) "Minimum air flow set point in heating mode"
  Buildings.Controls.OBC.CDL.Reals.Switch swi
    "Switch between heating and deadband air flow rate"
  Buildings.Controls.OBC.CDL.Utilities.Assert assMes(message=
    "The difference between cooling and heating set points must be greater than dTHys")
    "Assert message"
  Buildings.Controls.OBC.CDL.Reals.Subtract dTSet
    "Difference between cooling and heating set points"
  Buildings.Controls.OBC.CDL.Reals.GreaterThreshold greThr(t=dTHys)
    "Test for overlap of heating and cooling set points "
  Buildings.Controls.OBC.CDL.Reals.PID conDam(
    final yMax=yMax,
    final Td=TdDam,
    final yMin=yMin,
    final k=kDam,
    final Ti=TiDam,
    final controllerType=damController,
    Ni=10) if not have_preIndDam
           "Damper position setpoint"
  Buildings.Controls.OBC.CDL.Reals.Divide VDis_flowNor
    if not have_preIndDam
    "Normalized discharge volume flow rate"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant norFlo(
    final k=V_flow_nominal) if not have_preIndDam
    "Norminal airflow rate"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter gai(
    final k=1)
    if have_preIndDam "Dummy block to enable or disable connection"

protected
  parameter Real yMax=1 "Upper limit of PID control output";
  parameter Real yMin=0 "Lower limit of PID control output";
  parameter Modelica.Units.SI.TemperatureDifference dTHys(final min=0) = 0.5
    "Hysteresis width for switching minimum air flow rate";
equation
  connect(TRooCooSet, conCoo.u_s)
  connect(TRoo, conHea.u_m)
  connect(TRooHeaSet, conHea.u_s)
  connect(conHea.y, yVal)
  connect(conZer.y, reqFlo.x1)
  connect(cooMax.y, reqFlo.f2)
  connect(conOne.y, reqFlo.x2)
  connect(conCoo.y, reqFlo.u)
  connect(TRoo, conCoo.u_m)

  connect(TRooHeaSet, dTHea.u1)
  connect(dTHea.y, hysWitHol.u)
  connect(TRoo, dTHea.u2)
  connect(minFloCoo.y, swi.u3)
  connect(minFloHea.y, swi.u1)
  connect(hysWitHol.y, swi.u2)
  connect(swi.y, reqFlo.f1)
  connect(TRooCooSet, dTSet.u1)
  connect(TRooHeaSet, dTSet.u2)
  connect(dTSet.y, greThr.u)
  connect(greThr.y, assMes.u)
  connect(reqFlo.y, conDam.u_s)
  connect(VDis_flow, VDis_flowNor.u1)
  connect(norFlo.y, VDis_flowNor.u2)
  connect(VDis_flowNor.y, conDam.u_m)
  connect(conDam.y, yDam)
  connect(reqFlo.y, gai.u)
  connect(gai.y, yDam)
end RoomVAV;


Block that outputs the mode if the state is active, or zero otherwise.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
model State
  "Block that outputs the mode if the state is active, or zero otherwise"
  extends Modelica.StateGraph.StepWithSignal(nIn=1, nOut=1);
 parameter OperationModes mode "Enter enumeration of mode";
  Modelica.Blocks.Interfaces.IntegerOutput y "Mode signal (=0 if not active)"
equation
   y = if localActive then Integer(mode) else 0;
end State;


Control block for tracking the supply air temperature set point. This block implements the control logic for the supply air temperature,
as described in the control sequence VAV 2A2-21232 of the
Sequences of Operation for Common HVAC Systems (ASHRAE, 2006).

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
block SupplyAirTemperature
  "Control block for tracking the supply air temperature set point"
  extends Modelica.Blocks.Icons.Block;
  parameter Boolean have_heating = true
    "Set to true for heating and cooling functions (false for cooling only)"
  parameter Buildings.Controls.OBC.CDL.Types.SimpleController controllerType=
         Buildings.Controls.OBC.CDL.Types.SimpleController.PI "Type of controller";
  parameter Real k(min=0) = 0.01 "Gain of controller";
  parameter Modelica.Units.SI.Time Ti(min=Buildings.Controls.OBC.CDL.Constants.small)=
       120 "Time constant of integrator block"
  parameter Modelica.Units.SI.Time Td(min=0) = 0.1
    "Time constant of derivative block"

  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput uEna
    "Signal enabling set point tracking"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TSup(
    final unit="K",
    displayUnit="degC")
    "Supply air temperature measurement"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TSupSet(
    final unit="K",
    displayUnit="degC")
    "Supply air temperature set point"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yHea(
    final unit="1") if have_heating
    "Control signal for heating coil valve"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yOA(final unit="1")
    "Control signal for outdoor air damper"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yCoo(final unit="1")
    "Control signal for cooling coil valve"
  Buildings.Controls.OBC.CDL.Reals.PIDWithReset con(
    final controllerType=controllerType,
    final k=k,
    final Ti=Ti,
    final Td=Td,
    final yMax=1,
    final yMin=if have_heating then -1 else 0,
    y_reset=if have_heating then limSupHea.k else limInfOA.k,
    u_s(
    final unit="K",
    displayUnit="degC"),
    u_m(
    final unit="K",
    displayUnit="degC"))
    "Supply temperature controller"
  Buildings.Controls.OBC.CDL.Reals.Line mapHea if have_heating
    "Mapping function for actuating the heating coil valve "
  Buildings.Controls.OBC.CDL.Reals.Line mapOA
    "Mapping function for actuating the outdoor air damper"
  Buildings.Controls.OBC.CDL.Reals.Line mapCoo
    "Mapping function for actuating the cooling coil valve"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant limInfHea(k=-1)
    "Inferior limit of the control signal for heating coil control "
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant limSupHea(k=-0.5)
    "Superior limit of the control signal for heating coil control "
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant limInfOA(k=0)
    "Inferior limit of the control signal for outdoor air damper control "
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant limSupOA(k=0.5)
    "Superior limit of the control signal for outdoor air damper control "
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant limSupCoo(k=1)
    "Superior limit of the control signal for cooling coil control "
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant zero(k=0) "Zero"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant one(k=1) "one"
  Buildings.Controls.OBC.CDL.Reals.Switch swiHea if have_heating
    "Switch to close heating coil valve"
  Buildings.Controls.OBC.CDL.Reals.Switch swiCoo
    "Switch to close cooling coil valve"
  Buildings.Controls.OBC.CDL.Reals.Switch swiOA
    "Switch to close outdoor air damper"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant limInfCoo(
    k=limSupOA.k)
    "Inferior limit of the control signal for cooling coil control "
equation
  connect(TSup, con.u_s)
  connect(TSupSet, con.u_m)
  connect(con.y, mapOA.u)
  connect(con.y, mapCoo.u)
  connect(con.y, mapHea.u)
  connect(limInfHea.y, mapHea.x1)
  connect(limSupHea.y, mapHea.x2)
  connect(limInfOA.y, mapOA.x1)
  connect(limSupOA.y, mapOA.x2)
  connect(limSupCoo.y, mapCoo.x2)
  connect(one.y, mapCoo.f2)
  connect(zero.y, mapHea.f2)
  connect(one.y, mapOA.f2)
  connect(zero.y, mapOA.f1)
  connect(one.y, mapHea.f1)
  connect(zero.y, mapCoo.f1)
  connect(swiHea.y, yHea)
  connect(mapHea.y, swiHea.u1)
  connect(mapCoo.y, swiCoo.u1)
  connect(zero.y, swiHea.u3)
  connect(zero.y, swiCoo.u3)
  connect(swiOA.y, yOA)
  connect(mapOA.y, swiOA.u1)
  connect(zero.y, swiOA.u3)
  connect(uEna, swiCoo.u2)
  connect(uEna, swiOA.u2)
  connect(uEna, swiHea.u2)
  connect(swiCoo.y, yCoo)
  connect(limInfCoo.y, mapCoo.x1)
  connect(uEna, con.trigger)
end SupplyAirTemperature;


Block computing the supply air temperature set point based on the operation mode. This block computes the supply air temperature set point
based on the actual operating mode.
The default set point values are taken from the control sequence
VAV 2A2-21232 of the Sequences of Operation for
Common HVAC Systems (ASHRAE, 2006).

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
block SupplyAirTemperatureSetpoint
  "Block computing the supply air temperature set point based on the operation mode"
  extends Modelica.Blocks.Icons.Block;
  BaseClasses.Controls.ControlBus controlBus
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput TSet(
    final unit="K",
    displayUnit="degC")
    "Supply air temperature set point"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TSetVal[6](k={12,12,35,
        35,12,7} .+ 273.15)          "Set point values for each operating mode"
  Modelica.Blocks.Routing.IntegerPassThrough mode
  Modelica.Blocks.Sources.RealExpression ext(y=TSetVal[mode.y].y)
    "Extract set point value corresponding to actual mode"
equation
  connect(TSet, controlBus.TAirSupSet)
  connect(controlBus.controlMode, mode.u)
  connect(ext.y, TSet)
end SupplyAirTemperatureSetpoint;


Block that applies hysteresis and a minimum on timer to a control signal. Block that ensure that the system runs for a minimum time once it is switched on.

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
model SystemHysteresis
  "Block that applies hysteresis and a minimum on timer to a control signal"

  Buildings.Controls.OBC.CDL.Interfaces.RealInput u "Control signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput y "Real output signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yPum
    "Control signal for pump"

  Buildings.Controls.OBC.CDL.Reals.GreaterThreshold greThr(
    t=0.1,
    h=0.09)
    "Threshold to switch on system"
  Buildings.Controls.OBC.CDL.Reals.Switch swi "Switch for control signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant con(final k=0)
    "Zero output signal"

  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea
    "Switch for pump control signal"
  Buildings.Controls.OBC.CDL.Logical.TrueFalseHold truFalHol1(
    trueHoldDuration(displayUnit="h") = 14400,
    final falseHoldDuration=0)
        "Keep pump running at least for trueHoldDuration"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput sysOn
    "System on signal, set for example to true if fan is on"
  Buildings.Controls.OBC.CDL.Reals.Switch swiSysOff
    "Switch to overide if system is off"
  Buildings.Controls.OBC.CDL.Reals.Switch swiSysOff1
    "Switch to overide if system is off"
  Buildings.Controls.OBC.CDL.Logical.TrueDelay truDel(final delayTime=30)
    "Short delay to ignore spike inputs that trigger pump to operate"
equation
  connect(greThr.u, u)
  connect(swi.u1, u)
  connect(con.y, swi.u3)
  connect(truFalHol1.y, swi.u2)
  connect(truFalHol1.y, booToRea.u)
  connect(sysOn, swiSysOff.u2)
  connect(swiSysOff.y, y)
  connect(swi.y, swiSysOff.u1)
  connect(con.y, swiSysOff.u3)
  connect(swiSysOff1.y, yPum)
  connect(booToRea.y, swiSysOff1.u1)
  connect(swiSysOff1.u3, con.y)
  connect(sysOn, swiSysOff1.u2)
  connect(truFalHol1.u, truDel.y)
  connect(greThr.y, truDel.u)
end SystemHysteresis;


Test model for operation modes. This model tests the transition between the different modes of operation of the HVAC system.

within Buildings.Examples.VAVReheat.BaseClasses.Controls.Examples;
model OperationModes "Test model for operation modes"
    extends Modelica.Icons.Example;

  Buildings.Examples.VAVReheat.BaseClasses.Controls.ModeSelector operationModes
  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow preHeaFlo
  Modelica.Thermal.HeatTransfer.Sources.FixedTemperature fixTem(T=273.15)
  Modelica.Thermal.HeatTransfer.Components.HeatCapacitor cap(C=20000, T(fixed=
          true))
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor con(G=1)
  Modelica.Blocks.Logical.Switch switch1
  Modelica.Blocks.Sources.Constant on(k=200)
  Modelica.Blocks.Sources.Constant off(k=0)
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor temperatureSensor
  Modelica.Blocks.Sources.RealExpression TRooSetHea(
    y=if mode.y == Integer(Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.occupied)
      then 293.15 else 287.15)
  Modelica.Blocks.Sources.Constant TCoiHea(k=283.15)
    "Temperature after heating coil"
  Buildings.Examples.VAVReheat.BaseClasses.Controls.ControlBus controlBus
  Modelica.Blocks.Routing.IntegerPassThrough mode "Outputs the control mode"
  Modelica.Blocks.Sources.BooleanExpression modSel(
    y=mode.y == Integer(Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.unoccupiedNightSetBack) or
      mode.y == Integer(Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.unoccupiedWarmUp))
  Modelica.Blocks.Sources.Constant TOut(k=283.15) "Outside temperature"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor temperatureSensor1
  Modelica.Blocks.Sources.BooleanExpression modSel1(
    y=mode.y == Integer(Buildings.Examples.VAVReheat.BaseClasses.Controls.OperationModes.occupied))
  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow preHeaFlo1
  Buildings.Controls.Continuous.LimPID PID(initType=Modelica.Blocks.Types.Init.InitialState)
  Modelica.Blocks.Logical.Switch switch2
  Modelica.Blocks.Math.Gain gain(k=200)
  Buildings.Controls.SetPoints.OccupancySchedule occSch "Occupancy schedule"
equation
  connect(fixTem.port, con.port_a)
  connect(preHeaFlo.port, cap.port)
  connect(con.port_b, cap.port)
  connect(switch1.y, preHeaFlo.Q_flow)
  connect(on.y, switch1.u1)
  connect(off.y, switch1.u3)
  connect(cap.port, temperatureSensor.port)
  connect(controlBus, operationModes.cb)
  connect(temperatureSensor.T, controlBus.TRooMin)
  connect(TCoiHea.y, controlBus.TCoiHeaOut)
  connect(controlBus.controlMode, mode.u)
  connect(modSel.y, switch1.u2)
  connect(TOut.y, controlBus.TOut)
  connect(cap.port, temperatureSensor1.port)
  connect(temperatureSensor1.T, controlBus.TRooAve)
  connect(TRooSetHea.y, PID.u_s)
  connect(temperatureSensor.T, PID.u_m)
  connect(modSel1.y, switch2.u2)
  connect(off.y, switch2.u3)
  connect(preHeaFlo1.port, cap.port)
  connect(PID.y, switch2.u1)
  connect(gain.y, preHeaFlo1.Q_flow)
  connect(switch2.y, gain.u)
  connect(occSch.tNexOcc, controlBus.dTNexOcc)
  connect(TRooSetHea.y, controlBus.TRooSetHea)
  connect(occSch.occupied, controlBus.occupied)
end OperationModes;


Example models to test the components

within Buildings.Examples.VAVReheat.BaseClasses.Controls;
package Examples "Example models to test the components"
    extends Modelica.Icons.ExamplesPackage;
end Examples;


Test model for the room VAV controller. This model tests the VAV box contoller of transition from heating control to cooling
control.

within Buildings.Examples.VAVReheat.BaseClasses.Controls.Examples;
model RoomVAV "Test model for the room VAV controller"
  extends Modelica.Icons.Example;

  Buildings.Examples.VAVReheat.BaseClasses.Controls.RoomVAV boxWitExpDam(
    have_preIndDam=false,
    ratVFloMin=0.15,
    ratVFloHea=0.3,
    V_flow_nominal=1.5)
    "VAV terminal unit single maximum controller, for units with the exponential damper"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant heaSet(k=273.15 + 21)
    "Heating setpoint"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant cooSet(k=273.15 + 22)
    "Cooling setpoint"
  Buildings.Controls.OBC.CDL.Reals.Sources.Ramp ram(
    height=4,
    duration=3600,
    offset=-4) "Ramp source"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin sin(
    amplitude=1,
    freqHz=1/3600,
    offset=273.15 + 23.5) "Sine source"
  Buildings.Controls.OBC.CDL.Reals.Add rooTem "Room temperature"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin disFlo(
    amplitude=0.1,
    freqHz=1/3600,
    offset=0.2) "Discharge airflow rate"
  Buildings.Examples.VAVReheat.BaseClasses.Controls.RoomVAV boxWitPreIndDam(
    have_preIndDam=true,
    ratVFloMin=0.15,
    ratVFloHea=0.3,
    V_flow_nominal=1.5)
    "VAV terminal unit single maximum controller, for units with the pressure independent damper"
equation
  connect(rooTem.y, boxWitExpDam.TRoo)
  connect(cooSet.y, boxWitExpDam.TRooCooSet)
  connect(heaSet.y, boxWitExpDam.TRooHeaSet)
  connect(sin.y, rooTem.u2)
  connect(ram.y, rooTem.u1)

  connect(disFlo.y, boxWitExpDam.VDis_flow)
  connect(heaSet.y, boxWitPreIndDam.TRooHeaSet)
  connect(cooSet.y, boxWitPreIndDam.TRooCooSet)
  connect(rooTem.y, boxWitPreIndDam.TRoo)
end RoomVAV;


Validation of the supply air temperature control. This model validates the supply air temperature control, as implemented in

Buildings.Examples.VAVReheat.BaseClasses.Controls.SupplyAirTemperature

within Buildings.Examples.VAVReheat.BaseClasses.Controls.Examples;
model SupplyAirTemperature
  "Validation of the supply air temperature control"
  extends Modelica.Icons.Example;
  Buildings.Examples.VAVReheat.BaseClasses.Controls.SupplyAirTemperature conTSup(k=0.1, Ti=
        60)
    "Controller"
  Buildings.Controls.OBC.CDL.Logical.Sources.Pulse uEna(period=2800)
    "Enable signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TSupSet(
    y(final unit="K", displayUnit="degC"),
    k=13 + 273.15)
    "Set point"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TSup(
    phase=3.1415926535898,
    y(final unit="K", displayUnit="degC"),
    amplitude=5,
    freqHz=2/3600,
    offset=13 + 273.15) "Measurement"
equation
  connect(TSupSet.y, conTSup.TSupSet)
  connect(uEna.y, conTSup.uEna)
  connect(TSup.y, conTSup.TSup)
end SupplyAirTemperature;


Test model for the system hysteresis. This model tests

Buildings.Examples.VAVReheat.BaseClasses.Controls.SystemHysteresis.

within Buildings.Examples.VAVReheat.BaseClasses.Controls.Examples;
model SystemHysteresis "Test model for the system hysteresis"
  extends Modelica.Icons.Example;
  Buildings.Examples.VAVReheat.BaseClasses.Controls.SystemHysteresis sysHys
  Buildings.Examples.VAVReheat.BaseClasses.Controls.SystemHysteresis sysHys1
  Buildings.Controls.OBC.CDL.Reals.Sources.Pulse pul(width=1/60, period=3600)
    "Pulse source"
  Buildings.Controls.OBC.CDL.Reals.Sources.Pulse pul1(width=1/10, period(
        displayUnit="min") = 60)  "Pulse source"
protected
  Buildings.Controls.OBC.CDL.Logical.Sources.Pulse
                        booPul(
    final width=0.5,
    final period(displayUnit="h") = 10800,
    final shift=0)
    "Boolean pulse"
equation
  connect(pul.y, sysHys.u)
  connect(pul1.y, sysHys1.u)
  connect(booPul.y, sysHys.sysOn)
  connect(booPul.y, sysHys1.sysOn)
end SystemHysteresis;


Validation of detailed model that is at steady state with constant weather data. This model validates that the detailed model of multiple rooms and an HVAC system
starts at and remains at exactly <i>20</i>°C room air temperature
if there is no solar radiation, constant outdoor conditions, no internal gains and
no HVAC operation.

within Buildings.Examples.VAVReheat.Validation;
model Guideline36SteadyState
  "Validation of detailed model that is at steady state with constant weather data"
  extends Buildings.Examples.VAVReheat.Guideline36(
    flo(
      gai(K=0*[0.4; 0.4; 0.2]),
      use_windPressure=false,
      sampleModel=false),
    hvac(
      occSch(occupancy=3600*24*365*{1,2}, period=2*3600*24*365)),
    weaDat(
      pAtmSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      ceiHeiSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      totSkyCovSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      opaSkyCovSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      TDryBulSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      TDewPoiSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      TBlaSkySou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      TBlaSky=293.15,
      relHumSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      winSpeSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      winDirSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      HInfHorSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
      HSou=Buildings.BoundaryConditions.Types.RadiationDataSource.Input_HGloHor_HDifHor));
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant solRad(k=0) "Solar radiation"
equation
  connect(weaDat.HDifHor_in, solRad.y)
  connect(weaDat.HGloHor_in, solRad.y)
end Guideline36SteadyState;


Collection of validation models

within Buildings.Examples.VAVReheat;
package Validation "Collection of validation models"
  extends Modelica.Icons.ExamplesPackage;
end Validation;


This validates the ability to simulate trace substances in the air. This model validates that the detailed model of multiple rooms and an HVAC system
with a trace substance simulates properly.  CO2 is used as the trace substance.
A concentration of 400ppm is assumed for the ambient air.  For assumptions
about zone CO2 sources, see the model

Buildings.Examples.VAVReheat.Validation.BaseClasses.Floor.

within Buildings.Examples.VAVReheat.Validation;
model TraceSubstance
  "This validates the ability to simulate trace substances in the air"
  extends ASHRAE2006(
    MediumA(extraPropertiesNames={"CO2"}),
    hvac(
      amb(
        C=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                    /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, MediumA.nC))),
    redeclare BaseClasses.Floor flo);
end TraceSubstance;


Extends Buildings.Examples.VAVReheat.BaseClasses.Floor with CO2 generation from people and CO2 from outside air.. This model is used in the model

Buildings.Examples.VAVReheat.Validation.TraceSubstance.  It extends

Buildings.Examples.VAVReheat.BaseClasses.Floor with CO2 generation
from people and CO2 from outside air infiltration.  Calculation of the
generation from people uses the internal gain schedule and uses 0.05 people per m2
along with the zone area to calculate the number of people.  Then, it assumes
CO2 generation is 0.0048 l/s per person (Table 5, Persily and De Jonge 2017)
and density of CO2 to be 1.8 kg/m3, making CO2 generation equal to
8.64e-6 kg/s per person. Outside air CO2 concentration is assumed 400 ppm.

within Buildings.Examples.VAVReheat.Validation.BaseClasses;
model Floor
  "Extends Buildings.Examples.VAVReheat.BaseClasses.Floor with CO2 generation from people and CO2 from outside air."
  extends Buildings.Examples.VAVReheat.BaseClasses.Floor(
    nor(use_C_flow=true,
        C_start=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC)),
    eas(use_C_flow=true,
        C_start=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC)),
    cor(use_C_flow=true,
        C_start=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC)),
    wes(use_C_flow=true,
        C_start=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC)),
    sou(use_C_flow=true,
        C_start=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC)),
    leaSou(amb(C=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC))),
    leaEas(amb(C=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC))),
    leaNor(amb(C=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC))),
    leaWes(amb(C=fill(400e-6*Modelica.Media.IdealGases.Common.SingleGasesData.CO2.MM
                      /Modelica.Media.IdealGases.Common.SingleGasesData.Air.MM, Medium.nC))));

  Modelica.Blocks.Sources.RealExpression CO2GenWes(y=intGaiFra.y[1]*AFloWes*0.05
        *8.64e-6)
               "CO2 generated by people in the west zone"
  Modelica.Blocks.Sources.RealExpression CO2GenCor(y=intGaiFra.y[1]*AFloCor*0.05
        *8.64e-6)
               "CO2 generated by people in the corridor zone"
  Modelica.Blocks.Sources.RealExpression CO2GenEas(y=intGaiFra.y[1]*AFloEas*0.05
        *8.64e-6)
               "CO2 generated by people in the east zone"
  Modelica.Blocks.Sources.RealExpression CO2GenNor(y=intGaiFra.y[1]*AFloNor*0.05
        *8.64e-6)
               "CO2 generated by people in the north zone"
  Modelica.Blocks.Sources.RealExpression CO2GenSou(y=intGaiFra.y[1]*AFloSou*0.05
        *8.64e-6)
               "CO2 generated by people in the south zone"

equation
  connect(CO2GenNor.y, nor.C_flow[1])
  connect(CO2GenWes.y, wes.C_flow[1])
  connect(CO2GenCor.y, cor.C_flow[1])
  connect(CO2GenSou.y, sou.C_flow[1])
  connect(CO2GenEas.y, eas.C_flow[1])
end Floor;


within Buildings.Examples.VAVReheat.Validation;
package BaseClasses "This package contains base classes for Validation examples"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


within Buildings;
package Fluid "Package with models for fluid flow systems"
  extends Modelica.Icons.Package;

package UsersGuide "User's Guide"
  extends Modelica.Icons.Information;

end UsersGuide;
end Fluid;


Package with type definitions

within Buildings.Fluid;
package Types "Package with type definitions"
  extends Modelica.Icons.TypesPackage;

  type EfficiencyCurves = enumeration(
      Constant "constant",
      Polynomial "Polynomial",
      QuadraticLinear "quadratic in x1, linear in x2")
    "Enumeration to define the efficiency curves";
  type CvTypes = enumeration(
      OpPoint "flow coefficient defined by m_flow_nominal/sqrt(dp_nominal)",
      Kv "Kv (metric) flow coefficient",
      Cv "Cv (US) flow coefficient",
      Av "Av (metric) flow coefficient")
    "Enumeration to define the choice of valve flow coefficient"
  type HeatExchangerConfiguration = enumeration(
      ParallelFlow "Parallel flow",
      CounterFlow "Counter flow",
      CrossFlowUnmixed "Cross flow, both streams unmixed",
      CrossFlowStream1MixedStream2Unmixed
        "Cross flow, stream 1 mixed, stream 2 unmixed",
      CrossFlowStream1UnmixedStream2Mixed
        "Cross flow, stream 1 unmixed, stream 2 mixed",
      ConstantTemperaturePhaseChange "Constant temperature phase change in one stream")
    "Enumeration for heat exchanger construction"

  type HeatExchangerFlowRegime = enumeration(
      ParallelFlow "Parallel flow",
      CounterFlow "Counter flow",
      CrossFlowUnmixed "Cross flow, both streams unmixed",
      CrossFlowCMinMixedCMaxUnmixed "Cross flow, CMin mixed,   CMax unmixed",
      CrossFlowCMinUnmixedCMaxMixed "Cross flow, CMin unmixed, CMax mixed",
      ConstantTemperaturePhaseChange "Constant temperature phase change in one stream")
    "Enumeration for heat exchanger flow configuration"
  type InputType = enumeration(
      Constant "Use parameter to set stage",
      Stages "Use integer input to select stage",
      Continuous "Use continuous, real input") "Input options for movers"
end Types;


within Buildings.Fluid;
package Actuators "Package with actuator models such as valves and dampers"
  extends Modelica.Icons.VariantsPackage;

package UsersGuide "User's Guide"
  extends Modelica.Icons.Information;
  res(dp_nominal=10000,                m_flow_nominal=0.1);

Instead of this arrangement, the model res can be deleted
and the valve configured as

  val(dpValve_nominal=6000, dpFixed=10000, m_flow_nominal=0.1);

This yields the same simulation results, but a nonlinear equation can be avoided
in some cases. Although lumping the pressure drop of other components into the
valve model violates the intent that in component-based modeling, each component
should only model its own behavior, having the option of eliminating
a nonlinear equation can be worthwhile.

For three way valves, similar parameters exist for the controlled ports of the valve. For example,
consider the configuration below.

Suppose the parameters are

  val(dpValve_nominal=6000, dpFixed={0, 0}, m_flow_nominal=0.1);
  res1(dp_nominal=10000,                    m_flow_nominal=0.1);
  res3(dp_nominal=100,                      m_flow_nominal=0.1);

An equivalent model could be created by deleting the two resistance models
res1 and res3, and configuring the valve as

  val(dpValve_nominal=6000, dpFixed={10000, 100}, m_flow_nominal=0.1);

Leakage flow rate

Valves and air dampers should for numerical reasons have a small leakage flow rate.
This leakage l is a non-dimensional number, defined as
l=Kv(y=0) ⁄ =Kv(y=1).
A typical default value is l=0.0001.

If l=0, models will issue an error message as
this can in some situations lead to numerical problems if
a flow leg becomes decoupled from a reference pressure source.

Transients of actuators

This section describes how valves and dampers can be configured
to model the travel time of an actuator.
Modeling the travel time of the actuator can lead to faster simulation because
discrete or fast changes in controllers are damped before they
influence the flow network.

Change in actuator position that is linear in time

The valves and dampers in the package

Buildings.Fluid.Actuators
all have a parameter use_strokeTime.
This parameter is used as follows:

If use_strokeTime=false, then the actual valve
or damper position is equal to the input signal y.

If use_strokeTime=true, then the actual valve or damper position
changes linear in time until it reaches the control input.
The parameter strokeTime, which by default is set to
120 seconds, determines how fast the speed changes.
For example, if strokeTime=120 seconds and the current actuator position is 0, then
a step change in the actuator input signal from 0 to 1 will
cause the actuator position to change linearly from 0 to 1 within
30 seconds. Similarly, if the actuator position is then reduced by changing
the input signal from 1 to 0.5, it will take 15 seconds
to achieve the new set point.

Using a linear change in actuator position often leads to a more robust simulation,
because a step change
in the input signal is \"smoothened\" by the filter, and
hence the flow network is only exposed to a continuously differentiable change
in the input signal.
However, if the filter is part of a closed loop control, then the transient
response gets changed. Therefore, if the parameter strokeTime
is changed, control gains may need to be retuned.

For example, suppose there is a closed loop control with a PI-controller

Buildings.Controls.Continuous.LimPID
and a valve, configured with use_strokeTime=true and strokeTime=120 seconds.
Assume that the transient response of the other dynamic elements in the control loop is fast
compared to the rise time of the filter.
Then, a proportional gain of k=0.1 and an integrator time constant of
Ti=120 seconds often yields satisfactory closed loop control performance.
These values may need to be changed for different applications as they are also a function
of the loop gain.
If the control loop shows oscillatory behavior, then reduce k and/or increase Ti.
If the control loop reacts too slow, do the opposite.

We will now show how the parameter strokeTime affects the actual position of a
control valve.
The figure below shows a model with two control valves.
The valve val1 is configured with use_strokeTime=true
and a rise time strokeTime=120 seconds.
The grey motor symbol above the control valve val1
indicates that use_strokeTime=true.

If these valves both have a step input signal at 10 seconds, then the
actual opening of the valves are as follows:

Thus, in the valve val1, the mass flow rate will slowly increase,
whereas in val2, the mass flow rate changes instantaneously.

If use_strokeTime=true, then the parameter
y_start can be used to set the initial position of the
actuator, and the parameter
init can be used to configure how the position
should be initialized.

For most applications, the default values are appropriate.
Although setting use_strokeTime increases the number of equations,
it can reduce computing time because the equations are
easier to solve when a controller switches.

Approximation using a motor model with hysteresis

The model

Buildings.Fluid.Actuators.Motors.IdealMotor
models a motor with hysteresis.
It is more detailed than the above approximation.
However, it can significantly increase computing time
because it generates a state event whenever the valve position changes.

"));

end UsersGuide;
end Actuators;


Partial model that implements the filtered opening for valves and dampers. This model implements the filter that is used to approximate the travel
time of the actuator.
Models that extend this model use the signal
y_actual to obtain the
current position of the actuator.

within Buildings.Fluid.Actuators.BaseClasses;
model ActuatorSignal
  "Partial model that implements the filtered opening for valves and dampers"

  parameter Boolean use_strokeTime=true
    "Set to true to continuously open and close valve using strokeTime"

  parameter Modelica.Units.SI.Time strokeTime=120
    "Time needed to fully open or close actuator"
  parameter Modelica.Blocks.Types.Init init=Modelica.Blocks.Types.Init.InitialOutput
    "Type of initialization (no init/steady state/initial state/initial output)"
  parameter Real y_start=1 "Initial position of actuator"

  Modelica.Blocks.Interfaces.RealInput y(min=0, max=1)
    "Actuator position (0: closed, 1: open)"

  Modelica.Blocks.Interfaces.RealOutput y_actual
    "Actual actuator position"

  // Classes used to implement the filtered opening

protected
  parameter Boolean casePreInd = false
    "In case of PressureIndependent the model I/O is modified"
  Modelica.Blocks.Interfaces.RealOutput y_internal(unit="1")
    "Output connector for internal use (= y_actual if not casePreInd)";
  Modelica.Blocks.Interfaces.RealOutput y_filtered if use_strokeTime
    "Filtered valve position in the range 0..1"

  Modelica.Blocks.Nonlinear.SlewRateLimiter actPos(
    Rising=1/strokeTime,
    Falling=-1/strokeTime,
    Td=0.001*strokeTime,
    initType=init,
    y_start=y_start,
    strict=true) if use_strokeTime "Actuator position"
equation
  connect(actPos.y, y_filtered)

  if use_strokeTime then
  connect(actPos.u, y)

    connect(y_filtered, y_internal);
  else
    connect(y, y_internal)
  end if;
  if not casePreInd then
    connect(y_internal, y_actual);
  end if;
end ActuatorSignal;


within Buildings.Fluid.Actuators;
package BaseClasses "Package with base classes for Buildings.Fluid.Actuators"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


Partial model for air dampers with exponential opening characteristics. Partial model for air dampers with exponential opening characteristics.
This is the base model for air dampers.
The model implements the functions that relate the opening signal and the
flow coefficient.
The model also defines parameters that are used by different air damper
models.

within Buildings.Fluid.Actuators.BaseClasses;
partial model PartialDamperExponential
  "Partial model for air dampers with exponential opening characteristics"
  extends Buildings.Fluid.BaseClasses.PartialResistance(
    final dp_nominal=dpDamper_nominal+dpFixed_nominal,
    final m_flow_turbulent=if use_deltaM then deltaM * m_flow_nominal else
      eta_default*ReC*sqrt(A)*facRouDuc);
  extends Buildings.Fluid.Actuators.BaseClasses.ActuatorSignal;
  parameter Modelica.Units.SI.PressureDifference dpDamper_nominal(displayUnit=
        "Pa") "Pressure drop of fully open damper at nominal mass flow rate"
  parameter Modelica.Units.SI.PressureDifference dpFixed_nominal(displayUnit=
        "Pa") = 0
    "Pressure drop of duct and resistances other than the damper in series, at nominal mass flow rate"
  parameter Boolean use_deltaM = true
    "Set to true to use deltaM for turbulent transition, else ReC is used";
  parameter Real deltaM = 0.3
    "Fraction of nominal mass flow rate where transition to turbulent occurs"
  final parameter Modelica.Units.SI.Velocity v_nominal=(2/rho_default/k1*
      dpDamper_nominal)^0.5 "Nominal face velocity";
  final parameter Modelica.Units.SI.Area A=m_flow_nominal/rho_default/v_nominal
    "Face area";
  parameter Boolean roundDuct = false
    "Set to true for round duct, false for square cross section"
  parameter Real ReC=4000 "Reynolds number where transition to turbulence starts"
  parameter Real a(unit="1")=-1.51 "Coefficient a for damper characteristics"
  parameter Real b(unit="1")=0.105*90 "Coefficient b for damper characteristics"
  parameter Real yL(unit="1") = 15/90 "Lower value for damper curve"
  parameter Real yU(unit="1") = 55/90 "Upper value for damper curve"
  final parameter Real k0(min=0, unit="1") = 2 * rho_default * (A / kDamMin)^2
    "Loss coefficient for y=0 (pressure drop divided by dynamic pressure)"
  parameter Real k1(min=0, unit="1") = 0.45
    "Loss coefficient for y=1 (pressure drop divided by dynamic pressure)"
  parameter Real l(min=1e-10, max=1) = 0.0001
    "Damper leakage, ratio of flow coefficients k(y=0)/k(y=1)"
  parameter Boolean use_constant_density=true
    "Set to true to use constant density for flow friction"
  Medium.Density rho "Medium density";
  final parameter Real kFixed = if dpFixed_nominal > Modelica.Constants.eps then
    m_flow_nominal / sqrt(dpFixed_nominal) else Modelica.Constants.inf
    "Flow coefficient of fixed resistance that may be in series with damper, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2).";
  Real kDam
    "Flow coefficient of damper, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2)";
  Real k
    "Flow coefficient of damper plus fixed resistance, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2)";
protected
  parameter Medium.Density rho_default=Medium.density(sta_default)
    "Density, used to compute fluid volume";
  parameter Real facRouDuc= if roundDuct then sqrt(Modelica.Constants.pi)/2 else 1
    "Shape factor used to compute the hydraulic diameter for round ducts";
  parameter Real kL = Buildings.Fluid.Actuators.BaseClasses.exponentialDamper(
    y=yL, a=a, b=b, cL=cL, cU=cU, yL=yL, yU=yU)^2
    "Loss coefficient at the lower limit of the exponential characteristics";
  parameter Real kU = Buildings.Fluid.Actuators.BaseClasses.exponentialDamper(
    y=yU, a=a, b=b, cL=cL, cU=cU, yL=yL, yU=yU)^2
    "Loss coefficient at the upper limit of the exponential characteristics";
  parameter Real[3] cL={
    (Modelica.Math.log(k0) - b - a)/yL^2,
    (-b*yL - 2*Modelica.Math.log(k0) + 2*b + 2*a)/yL,
    Modelica.Math.log(k0)} "Polynomial coefficients for curve fit for y < yl";
  parameter Real[3] cU={
    (Modelica.Math.log(k1) - a)/(yU^2 - 2*yU + 1),
    (-b*yU^2 - 2*Modelica.Math.log(k1)*yU - (-2*b - 2*a)*yU - b)/(yU^2 - 2*yU + 1),
    (Modelica.Math.log(k1)*yU^2 + b*yU^2 + (-2*b - 2*a)*yU + b + a)/(yU^2 - 2*yU + 1)}
    "Polynomial coefficients for curve fit for y > yu";
  parameter Real kDamMax =  (2 * rho_default / k1)^0.5 * A
    "Flow coefficient of damper fully open, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2)";
  parameter Real kTotMax = if dpFixed_nominal > Modelica.Constants.eps then
    sqrt(1 / (1 / kFixed^2 + 1 / kDamMax^2)) else kDamMax
    "Flow coefficient of damper fully open plus fixed resistance, with unit=(kg.m)^(1/2)";
  parameter Real kDamMin = l * kDamMax
    "Flow coefficient of damper fully closed, with unit=(kg.m)^(1/2)";
  parameter Real kTotMin = if dpFixed_nominal > Modelica.Constants.eps then
    sqrt(1 / (1 / kFixed^2 + 1 / kDamMin^2)) else kDamMin
    "Flow coefficient of damper fully closed + fixed resistance, with unit=(kg.m)^(1/2)";
initial equation
  assert(dpDamper_nominal > Modelica.Constants.eps,
    "In " + getInstanceName() + ": dpDamper_nominal must be strictly greater than zero.");
  assert(dpFixed_nominal >= 0,
    "In " + getInstanceName() + ": dpFixed_nominal must be greater than zero.");
  assert(yL < yU,
    "In " + getInstanceName() + ": yL must be strictly lower than yU.");
  assert(m_flow_turbulent > 0,
    "In " + getInstanceName() + ": m_flow_turbulent must be strictly greater than zero.");
  assert(k1 >= 0.2,
    "In " + getInstanceName() + ": k1 must be greater than 0.2. k1=" + String(k1));
  assert(k1 < kU,
    "In " + getInstanceName() + ": k1 must be strictly lower than exp(a + b * (1 - yU)). k1=" +
    String(k1) + ", exp(...) = " + String(kU));
  assert(k0 <= 1e10,
    "In " + getInstanceName() + ": k0 must be lower than 1e10. k0=" + String(k0));
  assert(k0 > kL,
    "In " + getInstanceName() + ": k0 must be strictly higher than exp(a + b * (1 - yL)). k0=" +
    String(k0) + ", exp(...) = " + String(kL));
equation
  rho = if use_constant_density then
      rho_default
    else
      Medium.density(Medium.setState_phX(
        port_a.p, inStream(port_a.h_outflow), inStream(port_a.Xi_outflow)));
  // flow coefficient, k = m_flow/sqrt(dp)
  kDam=sqrt(2*rho)*A/Buildings.Fluid.Actuators.BaseClasses.exponentialDamper(
    y=y_actual,
    a=a,
    b=b,
    cL=cL,
    cU=cU,
    yL=yL,
    yU=yU);
  k = if dpFixed_nominal > Modelica.Constants.eps then sqrt(1/(1/kFixed^2 + 1/kDam^2)) else kDam;
end PartialDamperExponential;


Partial three way valve

within Buildings.Fluid.Actuators.BaseClasses;
partial model PartialThreeWayValve "Partial three way valve"
  extends Buildings.Fluid.BaseClasses.PartialThreeWayResistance(
    m_flow_small=m_flow_nominal*1e-4,
    final mDyn_flow_nominal=m_flow_nominal,
    redeclare replaceable Buildings.Fluid.Actuators.BaseClasses.PartialTwoWayValve res1
      constrainedby Buildings.Fluid.Actuators.BaseClasses.PartialTwoWayValve(
        deltaM=deltaM,
        from_dp=from_dp,
        final linearized=linearized[1],
        final homotopyInitialization=homotopyInitialization,
        final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
        final m_flow_nominal=m_flow_nominal,
        final dpValve_nominal=dpValve_nominal,
        final dpFixed_nominal=dpFixed_nominal[1],
        final use_strokeTime=false,
        final strokeTime=strokeTime),
    redeclare FixedResistances.LosslessPipe res2(m_flow_nominal=m_flow_nominal),
    redeclare replaceable Buildings.Fluid.Actuators.BaseClasses.PartialTwoWayValve res3
      constrainedby Buildings.Fluid.Actuators.BaseClasses.PartialTwoWayValve(
        deltaM=deltaM,
        from_dp=from_dp,
        final linearized=linearized[2],
        final homotopyInitialization=homotopyInitialization,
        final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
        final m_flow_nominal=m_flow_nominal,
        final dpValve_nominal=dpValve_nominal/fraK^2,
        final dpFixed_nominal=dpFixed_nominal[2],
        final use_strokeTime=false,
        final strokeTime=strokeTime));
    extends Buildings.Fluid.Actuators.BaseClasses.ActuatorSignal;
    extends Buildings.Fluid.Actuators.BaseClasses.ValveParameters(
      rhoStd=Medium.density_pTX(101325, 273.15+4, Medium.X_default));

  constant Boolean homotopyInitialization = true "= true, use homotopy method"

  parameter Modelica.Units.SI.PressureDifference dpFixed_nominal[2](
    each displayUnit="Pa",
    each min=0) = {0,0}
    "Nominal pressure drop of pipes and other equipment in flow legs at port_1 and port_3"

  parameter Real fraK(min=0, max=1) = 0.7
    "Fraction Kv(port_3→port_2)/Kv(port_1→port_2)";
  parameter Real[2] l(each min=0, each max=1) = {0.0001, 0.0001}
    "Valve leakage, l=Kv(y=0)/Kv(y=1)";
  parameter Real deltaM = 0.02
    "Fraction of nominal flow rate where linearization starts, if y=1"

  parameter Boolean[2] linearized = {false, false}
    "= true, use linear relation between m_flow and dp for any flow rate"

protected
  Modelica.Blocks.Math.Feedback inv "Inversion of control signal"
  Modelica.Blocks.Sources.Constant uni(final k=1)
    "Outputs one for bypass valve"

initial equation
  assert(homotopyInitialization, "In " + getInstanceName() +
    ": The constant homotopyInitialization has been modified from its default value. This constant will be removed in future releases.",
    level = AssertionLevel.warning);

equation
  connect(uni.y, inv.u1)
end PartialThreeWayValve;


Partial model for a two way valve. Partial model for a two way valve. This is the base model for valves
with different opening characteristics, such as linear, equal percentage,
quick opening or pressure-independent.

within Buildings.Fluid.Actuators.BaseClasses;
partial model PartialTwoWayValve "Partial model for a two way valve"

  extends Buildings.Fluid.BaseClasses.PartialResistance(
       final dp_nominal=dpValve_nominal + dpFixed_nominal,
       dp(nominal=6000),
       final m_flow_turbulent = deltaM * abs(m_flow_nominal));

  extends Buildings.Fluid.Actuators.BaseClasses.ValveParameters(
      rhoStd=Medium.density_pTX(101325, 273.15+4, Medium.X_default));

  extends Buildings.Fluid.Actuators.BaseClasses.ActuatorSignal;
  parameter Modelica.Units.SI.PressureDifference dpFixed_nominal(
    displayUnit="Pa",
    min=0) = 0 "Pressure drop of pipe and other resistances that are in series"

  parameter Real l(min=1e-10, max=1) = 0.0001
    "Valve leakage, l=Kv(y=0)/Kv(y=1)";
  input Real phi
    "Ratio actual to nominal mass flow rate of valve, phi=Kv(y)/Kv(y=1)";
  parameter Real kFixed(unit="", min=0) = if dpFixed_nominal > Modelica.Constants.eps
    then m_flow_nominal / sqrt(dpFixed_nominal) else 0
    "Flow coefficient of fixed resistance that may be in series with valve, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2).";
  Real kVal(unit="", min=Modelica.Constants.small)
    "Flow coefficient of valve, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2).";
  Real k(unit="", min=Modelica.Constants.small)
    "Flow coefficient of valve and pipe in series, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2).";
initial equation
  assert(dpFixed_nominal > -Modelica.Constants.eps, "In " + getInstanceName() +
  ": Model requires dpFixed_nominal >= 0 but received dpFixed_nominal = "
        + String(dpFixed_nominal) + " Pa.");
end PartialTwoWayValve;


Partial model for a two way valve using a Kv characteristic. Partial model for valves with different opening characteristics,
such as linear, equal percentage or quick opening. This partial extends from

Buildings.Fluid.Actuators.BaseClasses.PartialTwoWayValve
and also contains the governing equations for these three two way valve models.

within Buildings.Fluid.Actuators.BaseClasses;
partial model PartialTwoWayValveKv
  "Partial model for a two way valve using a Kv characteristic"
  extends Buildings.Fluid.Actuators.BaseClasses.PartialTwoWayValve;

equation
 kVal = phi*Kv_SI;
 if (dpFixed_nominal > Modelica.Constants.eps) then
   k = sqrt(1/(1/kFixed^2 + 1/kVal^2));
 else
   k = kVal;
 end if;

 if linearized then
   // This implementation yields m_flow_nominal = phi*kv_SI * sqrt(dp_nominal)
   // if m_flow = m_flow_nominal and dp = dp_nominal
   m_flow*m_flow_nominal_pos = k^2 * dp;
 else
   if homotopyInitialization then
     if from_dp then
         m_flow=homotopy(actual=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(dp=dp, k=k,
                                m_flow_turbulent=m_flow_turbulent),
                                simplified=m_flow_nominal_pos*dp/dp_nominal_pos);
      else
         dp=homotopy(actual=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(m_flow=m_flow, k=k,
                                m_flow_turbulent=m_flow_turbulent),
                                simplified=dp_nominal_pos*m_flow/m_flow_nominal_pos);
     end if;
   else // do not use homotopy
     if from_dp then
       m_flow=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(dp=dp, k=k,
                                m_flow_turbulent=m_flow_turbulent);
      else
        dp=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(m_flow=m_flow, k=k,
                                m_flow_turbulent=m_flow_turbulent);
      end if;
    end if; // homotopyInitialization
 end if; // linearized
end PartialTwoWayValveKv;