Equipment availability for heating and cooling applications. If a heat pump is commanded enabled in either heating or cooling mode,
it is removed from the staging order of the opposite mode until it has
been off for dtOff.

within Buildings.Templates.Plants.Controls.StagingRotation;
block EquipmentAvailability
  "Equipment availability for heating and cooling applications"
  parameter Boolean have_heaWat
    "Set to true for plants that provide HW"
  parameter Boolean have_chiWat
    "Set to true for plants that provide CHW"
  parameter Real dtOff(
    final min=0,
    final unit="s")=900
    "Off time required before equipment is deemed available again";
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1
    "Equipment enable command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1Hea
    if have_heaWat
    "Equipment available for heating"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Hea
    if have_heaWat and have_chiWat
    "Equipment operating mode command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1Coo
    if have_chiWat
    "Equipment available for cooling"
  Buildings.Controls.OBC.CDL.Logical.And onAndHea
    "Return true if equipment on and in heating mode"
  Buildings.Controls.OBC.CDL.Logical.And onAndCoo
    "Return true if equipment on and in cooling mode"
  Buildings.Controls.OBC.CDL.Logical.Not coo
    "Return true if equipment in cooling mode"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Ava
    "Equipment available signal"
  Utilities.PlaceholderLogical phHea(final have_inp=have_heaWat and have_chiWat,
      final u_internal=have_heaWat or not have_chiWat)
    "Placeholder value if signal is not available"
  Buildings.Controls.OBC.CDL.Logical.Not off
    "Return true if equipment is off"
  inner Modelica.StateGraph.StateGraphRoot stateGraphRoot
  Modelica.StateGraph.StepWithSignal onHea(
    nOut=2,
    nIn=1)
  Modelica.StateGraph.InitialStepWithSignal avaMod(
    nOut=3,
    nIn=2)
    "Initial state – Equipment available for all modes"
  Modelica.StateGraph.TransitionWithSignal trnToOff
    "Transition to off state"
  Modelica.StateGraph.StepWithSignal onCoo(
    nOut=2,
    nIn=1)
  Modelica.StateGraph.TransitionWithSignal trnToCoo
    "Transition to cooling mode"
  Modelica.StateGraph.TransitionWithSignal trnToHea
    "Transition to heating mode"
  Modelica.StateGraph.TransitionWithSignal trnToOff1
    "Transition to off state"
  Buildings.Controls.OBC.CDL.Logical.Or avaAllHea
    "Return true if equipment available for all modes or in heating mode"
  Buildings.Controls.OBC.CDL.Logical.Or avaAllCoo
    "Return true if equipment available for all modes or in cooling mode"
  Modelica.StateGraph.Step offSta(
    nOut=1,
    nIn=2)
    "Off state"
  Modelica.StateGraph.Transition trnToAvaTim(
    enableTimer=true,
    final waitTime=dtOff)
    "Transition back to available state after off time elapsed"
  Modelica.StateGraph.Step unaSta(
    nOut=1,
    nIn=3)
    "Unavailable state"
  Modelica.StateGraph.TransitionWithSignal trnToUna
    "Transition to unavailable state"
  Buildings.Controls.OBC.CDL.Logical.Not una
    "Return true if equipment is unavailable"
  Modelica.StateGraph.TransitionWithSignal trnToAva
    "Transition back to available state"
  Modelica.StateGraph.TransitionWithSignal trnToUna2
    "Transition to unavailable state"
  Modelica.StateGraph.TransitionWithSignal trnToUna3
    "Transition to unavailable state"
equation
  connect(coo.y, onAndCoo.u2)
  connect(u1, onAndHea.u1)
  connect(u1, onAndCoo.u1)
  connect(u1Hea, phHea.u)
  connect(phHea.y, onAndHea.u2)
  connect(phHea.y, coo.u)
  connect(u1, off.u)
  connect(avaMod.outPort[1], trnToCoo.inPort)
  connect(onAndCoo.y, trnToCoo.condition)
  connect(onHea.outPort[1], trnToOff.inPort)
  connect(off.y, trnToOff.condition)
  connect(onCoo.outPort[1], trnToOff1.inPort)
  connect(trnToCoo.outPort, onCoo.inPort[1])
  connect(onAndHea.y, trnToHea.condition)
  connect(avaMod.outPort[2], trnToHea.inPort)
  connect(avaMod.active, avaAllHea.u1)
  connect(onHea.active, avaAllHea.u2)
  connect(avaAllHea.y, y1Hea)
  connect(avaAllCoo.y, y1Coo)
  connect(off.y, trnToOff1.condition)
  connect(trnToHea.outPort, onHea.inPort[1])
  connect(offSta.outPort[1], trnToAvaTim.inPort)
  connect(trnToOff.outPort, offSta.inPort[1])
  connect(trnToOff1.outPort, offSta.inPort[2])
  connect(trnToAvaTim.outPort, avaMod.inPort[1])
  connect(u1Ava, una.u)
  connect(avaMod.outPort[3], trnToUna.inPort)
  connect(una.y, trnToUna.condition)
  connect(trnToUna.outPort, unaSta.inPort[1])
  connect(unaSta.outPort[1], trnToAva.inPort)
  connect(u1Ava, trnToAva.condition)
  connect(trnToAva.outPort, avaMod.inPort[2])
  connect(avaMod.active, avaAllCoo.u1)
  connect(onCoo.active, avaAllCoo.u2)
  connect(onHea.outPort[2], trnToUna2.inPort)
  connect(trnToUna2.outPort, unaSta.inPort[2])
  connect(una.y, trnToUna2.condition)
  connect(onCoo.outPort[2], trnToUna3.inPort)
  connect(trnToUna3.outPort, unaSta.inPort[3])
  connect(una.y, trnToUna3.condition)
end EquipmentAvailability;


Return array of equipment to be enabled at given stage. This block generates the equipment enable commands based on the
active stage index uSta, the equipment available
signal u1Ava and the indices of lead/lag alternate
equipment, sorted by increasing staging runtime.

within Buildings.Templates.Plants.Controls.StagingRotation;
block EquipmentEnable
  "Return array of equipment to be enabled at given stage"
  parameter Real staEqu[:,:](
    each unit="1",
    each min=0,
    each max=1)
    "Staging matrix – Equipment required for each stage"
  parameter Integer nEquAlt=if nEqu==1 then 1 else
    max({sum({(if staEqu[i, j] > 0 and staEqu[i, j] < 1 then 1 else 0) for j in 1:nEqu}) for i in 1:nSta})
    "Number of lead/lag alternate equipment"
  final parameter Integer nSta=size(staEqu, 1)
    "Number of stages"
  final parameter Integer nEqu=size(staEqu, 2)
    "Number of equipment"
  final parameter Real traStaEqu[nEqu, nSta]={{staEqu[i, j] for i in 1:nSta} for j in 1:nEqu}
    "Transpose of staging matrix";
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput uIdxAltSor[nEquAlt]
    "Indices of lead/lag alternate equipment sorted by increasing runtime"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput uSta
    "Stage index"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Ava[nEqu]
    "Equipment available signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant traMatStaEqu[nEqu, nSta](
    final k=traStaEqu)
    "Transpose of staging matrix"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1[nEqu]
    "Equipment enable command"
  Buildings.Controls.OBC.CDL.Routing.RealExtractor reqEquSta[nEqu](
    each final nin=nSta)
    "Extract equipment required at given stage"
  Buildings.Controls.OBC.CDL.Routing.IntegerScalarReplicator intScaRep(
    final nout=nEqu)
    "Replicate signal"
  Buildings.Controls.OBC.CDL.Reals.MultiSum nEquStaRea(
    nin=nEqu)
    "Return the number of equipment required"
  Buildings.Controls.OBC.CDL.Reals.GreaterThreshold isReq[nEqu](
    each final t=0.99)
    "Return true if equipment required without lead/lag alternate"
  Buildings.Controls.OBC.CDL.Logical.And isReqAva[nEqu]
    "Return true if equipment required without lead/lag alternate and available"
  Buildings.Controls.OBC.CDL.Reals.GreaterThreshold isReqPosAlt[nEqu](
      each final t=1E-4)
    "Return true if equipment required (with or without lead/lag alternate)"
  Buildings.Controls.OBC.CDL.Reals.LessThreshold isNotReqNoAlt[nEqu](
      each final t=0.9999)
    "Return true if equipment not required or required with lead/lag alternate"
  Buildings.Controls.OBC.CDL.Logical.MultiAnd isReqAltAva[nEqu](
    each final nin=3)
    "Return true if lead/lag alternate equipment required and available"
  Buildings.Controls.OBC.CDL.Logical.Or ena[nEqu]
    "Enable equipment required without lead/lag alternate and available or lead/lag alternate equipment to meet stage requirement"
  Buildings.Controls.OBC.CDL.Conversions.RealToInteger nEquSta
    "Number of equipment required"
  Buildings.Controls.OBC.CDL.Integers.Subtract nAltReq
    "Number of lead/lag alternate equipment to run to meet stage requirement"
  Buildings.Controls.OBC.CDL.Logical.And isReqAltAvaNee[nEqu]
    "Return true if equipment required with lead/lag alternate and available and needed to meet stage requirement"
  Buildings.Controls.OBC.CDL.Integers.Change cha
    "Detect stage index change"
  Buildings.Controls.OBC.CDL.Logical.Pre y1Pre[nEqu]
    "Left limit of signal in discrete time"
  Buildings.Controls.OBC.CDL.Logical.Switch logSwi[nEqu]
    "Switch to newly computed value at stage change"
  Buildings.Controls.OBC.CDL.Routing.BooleanScalarReplicator booScaRep(
    final nout=nEqu)
    "Replicate signal"
  Utilities.CountTrue nReq(
    nin=nEqu)
    "Count the number of required equipment without lead/lag alternate, not necessarily available"
  Utilities.CountTrue nEnaAvaPre(
    nin=nEqu)
    "Count the number of previously enabled equipment that are available"
  Buildings.Controls.OBC.CDL.Integers.Less intLes
    "Compare to required number of equipment"
  Buildings.Controls.OBC.CDL.Logical.Or swiEna
    "Evaluate condition to switch to newly computed enable signal"
  Buildings.Controls.OBC.CDL.Logical.And isEnaPreAva[nEqu]
    "Return true if equipment previously enabled and available"
  Utilities.TrueArrayConditional truArrCon(
    final nout=nEqu,
    final nin=nEquAlt)
    "Generate array of size nEqu with nAltReq true elements at uIdxAltSor indices "
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant one(
    final k=1)
    "Constant"
  Buildings.Controls.OBC.CDL.Integers.Max maxInt
    "Maximum between stage index and 1"
  Buildings.Controls.OBC.CDL.Integers.GreaterThreshold greZer(
    final t=0)
    "Check if stage index is greater than zero"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea(
    final realTrue=1,
    final realFalse=0)
    "Cast to real"
  Buildings.Controls.OBC.CDL.Routing.RealScalarReplicator reaScaRep(
    final nout=nEqu)
    "Replicate signal"
  Buildings.Controls.OBC.CDL.Reals.Multiply voiStaZer[nEqu]
    "Void if stage is equal to zero"
equation
  connect(intScaRep.y, reqEquSta.index)
  connect(traMatStaEqu.y, reqEquSta.u)
  connect(isReq.y, isReqAva.u1)
  connect(u1Ava, isReqAva.u2)
  connect(isReqPosAlt.y, isReqAltAva.u[1])
  connect(isNotReqNoAlt.y, isReqAltAva.u[2])
  connect(u1Ava, isReqAltAva.u[3])
  connect(isReqAva.y, ena.u2)
  connect(nEquStaRea.y, nEquSta.u)
  connect(nEquSta.y, nAltReq.u1)
  connect(isReqAltAva.y, isReqAltAvaNee.u2)
  connect(isReqAltAvaNee.y, ena.u1)
  connect(uSta, cha.u)
  connect(logSwi.y, y1)
  connect(y1, y1Pre.u)
  connect(y1Pre.y, logSwi.u3)
  connect(ena.y, logSwi.u1)
  connect(booScaRep.y, logSwi.u2)
  connect(nReq.y, nAltReq.u2)
  connect(isReq.y, nReq.u1)
  connect(nEnaAvaPre.y, intLes.u1)
  connect(nEquSta.y, intLes.u2)
  connect(swiEna.y, booScaRep.u)
  connect(cha.y, swiEna.u1)
  connect(intLes.y, swiEna.u2)
  connect(isEnaPreAva.y, nEnaAvaPre.u1)
  connect(y1Pre.y, isEnaPreAva.u2)
  connect(u1Ava, isEnaPreAva.u1)
  connect(nAltReq.y, truArrCon.u)
  connect(uIdxAltSor, truArrCon.uIdx)
  connect(truArrCon.y1, isReqAltAvaNee.u1)
  connect(one.y, maxInt.u1)
  connect(uSta, maxInt.u2)
  connect(maxInt.y, intScaRep.u)
  connect(uSta, greZer.u)
  connect(greZer.y, booToRea.u)
  connect(booToRea.y, reaScaRep.u)
  connect(reqEquSta.y, voiStaZer.u1)
  connect(reaScaRep.y, voiStaZer.u2)
  connect(voiStaZer.y, nEquStaRea.u)
  connect(voiStaZer.y, isReqPosAlt.u)
  connect(voiStaZer.y, isNotReqNoAlt.u)
  connect(voiStaZer.y, isReq.u)
end EquipmentEnable;


Staging event sequencing. If a heat pump is commanded on in a desired heating or cooling mode:

within Buildings.Templates.Plants.Controls.StagingRotation;
block EventSequencing "Staging event sequencing"
  parameter Boolean have_heaWat
    "Set to true for plants that provide HW"
  parameter Boolean have_chiWat
    "Set to true for plants that provide CHW"
  parameter Boolean have_valInlIso
    "Set to true if the system as inlet isolation valves"
  parameter Boolean have_valOutIso
    "Set to true if the system as outlet isolation valves"
  parameter Boolean have_pumHeaWatPri(start=false)
    "Set to true for plants with primary HW pumps"
  parameter Boolean have_pumChiWatPri(
    start=false)
    "Set to true for plants with separate primary CHW pumps"
  parameter Boolean have_pumHeaWatSec(start=false)
    "Set to true for plants with secondary HW pumps"
  parameter Boolean have_pumChiWatSec(start=false)
    "Set to true for plants with secondary CHW pumps"
  parameter Real dtVal(
    min=0,
    start=90,
    unit="s")=90
    "Nominal valve timing"
  parameter Real dtOff(
    min=0,
    unit="s") = 180
    "Heat pump internal shutdown cycle timing";
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Hea
    if have_heaWat
    "Enable command from heating mode sequence"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1PumHeaWatPri_actual
    if have_heaWat and have_pumHeaWatPri
    "Primary HW pump status (dedicated or lead headered pump)"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1PumChiWatPri_actual
    if have_chiWat and have_pumChiWatPri
    "Primary CHW pump status – Dedicated or lead headered pump"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1PumHeaWatSec_actual
    if have_heaWat and have_pumHeaWatSec
    "Lead headered secondary HW pump status"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1PumChiWatSec_actual
    if have_chiWat and have_pumChiWatSec
    "Lead headered secondary CHW pump status"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1ValHeaWatInlIso
    if have_heaWat and have_valInlIso
    "Inlet HW inlet isolation valve command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1ValHeaWatOutIso
    if have_heaWat and have_valOutIso
    "Outlet HW isolation valve command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1ValChiWatInlIso
    if have_chiWat and have_valInlIso
    "Inlet CHW isolation valve command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1ValChiWatOutIso
    if have_chiWat and have_valOutIso
    "Outlet CHW isolation valve command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1PumHeaWatPri
    if have_heaWat and have_pumHeaWatPri
    "Primary HW pump start command – Dedicated or lead headered pump"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1PumChiWatPri
    if have_chiWat and have_pumChiWatPri
    "Primary CHW pump start command – Dedicated or lead headered pump"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1
    "Equipment enable command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1Hea
    if have_heaWat and have_chiWat
    "Heating/cooling mode command: true=heating, false=cooling"
  Buildings.Controls.OBC.CDL.Logical.Timer timVal(
    final t=dtVal)
    "Return true when nominal valve timing elapsed"
  Buildings.Controls.OBC.CDL.Logical.MultiAnd heaValPum(
    nin=3)
    "Return true if heating AND valve timing elapsed AND lead HW pumps on"
  Buildings.Controls.OBC.CDL.Logical.Or ena
    "Return true if enabled"
  Buildings.Controls.OBC.CDL.Logical.MultiAnd cooValPum(
    nin=4)
    "Return true if cooling AND valve timing elapsed AND lead CHW pumps on"
  Utilities.PlaceholderLogical u1PumChiWatSec_internal(
    final have_inp=have_chiWat and have_pumChiWatSec,
    final have_inpPh=false,
    final u_internal=true)
    "Replace with placeholder value if input signal is not available"
  Utilities.PlaceholderLogical timVal_internal(
    final have_inp=have_valInlIso or have_valOutIso,
    final have_inpPh=true,
    final u_internal=true)
    "Replace with placeholder value if input signal is not available"
  Utilities.PlaceholderLogical u1PumHeaWatSec_internal(
    final have_inp=have_heaWat and have_pumHeaWatSec,
    final have_inpPh=false,
    final u_internal=true)
    "Replace with placeholder value if input signal is not available"
  Utilities.PlaceholderLogical u1PumChiWatPri_internal(
    final have_inp=have_chiWat and have_pumChiWatPri,
    final have_inpPh=false,
    final u_internal=true)
    "Replace with placeholder value if input signal is not available"
  Utilities.PlaceholderLogical u1PumHeaWatPri_internal(
    final have_inp=have_heaWat and have_pumHeaWatPri,
    final have_inpPh=false,
    final u_internal=true)
    "Replace with placeholder value if input signal is not available"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Coo
    if have_chiWat
    "Enable command from cooling mode sequence"
  Utilities.PlaceholderLogical u1Coo_internal(
    final have_inp=have_chiWat,
    final have_inpPh=false,
    final u_internal=false)
    "Replace with placeholder value if input signal is not available"
  Buildings.Controls.OBC.CDL.Logical.Or u1HeaOrCoo
    "Return true if enabled from heating or cooling mode sequence"
  Utilities.PlaceholderLogical u1Hea_internal(
    final have_inp=have_heaWat,
    final have_inpPh=false,
    final u_internal=false)
    "Replace with placeholder value if input signal is not available"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1AndHea
    if have_heaWat
    "Equipment commanded on in heating mode"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1AndCoo
    if have_chiWat
    "Equipment commanded on in cooling mode"
  Buildings.Controls.OBC.CDL.Logical.And enaAndHea
    "Return true if enabled in heating mode"
  Buildings.Controls.OBC.CDL.Logical.And enaAndCoo
    "Return true if enabled in cooling mode"
  Buildings.Controls.OBC.CDL.Routing.BooleanScalarReplicator rou(
    final nout=1)
    if have_pumChiWatPri
    "Signal routing for plants with dedicated primary CHW pumps"
  Buildings.Controls.OBC.CDL.Routing.BooleanScalarReplicator rou1(
    final nout=1)
    if not have_pumChiWatPri
    "Signal routing for plants without dedicated primary CHW pumps"
  Buildings.Controls.OBC.CDL.Logical.Nor off
    "Return true if disabled from heating and cooling mode sequence"
  Buildings.Controls.OBC.CDL.Logical.Timer timHp(final t=dtOff)
    "Return true when heat pump internal shutdown cycle times out"
  Buildings.Controls.OBC.CDL.Logical.Latch latValHeaWatIso
    if have_heaWat
    "Keep valve open until heat pump internal shutdown cycle times out"
  Buildings.Controls.OBC.CDL.Logical.Latch latValChiWatIso
    if have_chiWat
    "Keep valve open until heat pump internal shutdown cycle times out"
  Buildings.Controls.OBC.CDL.Logical.Latch latPumHeaWatPri
    if have_heaWat
    "Keep pump running until heat pump internal shutdown cycle times out"
  Buildings.Controls.OBC.CDL.Logical.Latch latPumChiWatPri
    if have_chiWat and have_pumChiWatPri
    "Keep pump running until heat pump internal shutdown cycle times out"
equation
  connect(heaValPum.y, ena.u1)
  connect(cooValPum.y, ena.u2)
  connect(timVal.passed, timVal_internal.u)
  connect(timVal_internal.y, heaValPum.u[1])
  connect(u1PumChiWatSec_actual, u1PumChiWatSec_internal.u)
  connect(u1PumHeaWatPri_actual, u1PumHeaWatPri_internal.u)
  connect(u1PumChiWatPri_actual, u1PumChiWatPri_internal.u)
  connect(u1PumHeaWatSec_actual, u1PumHeaWatSec_internal.u)
  connect(u1PumHeaWatPri_internal.y, heaValPum.u[2])
  connect(u1PumHeaWatSec_internal.y, heaValPum.u[3])
  connect(timVal_internal.y, cooValPum.u[1])
  connect(u1PumChiWatPri_internal.y, cooValPum.u[2])
  connect(u1PumChiWatSec_internal.y, cooValPum.u[3])
  connect(ena.y, y1)
  connect(u1Hea, u1Hea_internal.u)
  connect(u1Coo_internal.y, u1HeaOrCoo.u2)
  connect(u1Hea_internal.y, u1HeaOrCoo.u1)
  connect(u1HeaOrCoo.y, timVal.u)
  connect(u1HeaOrCoo.y, timVal_internal.uPh)
  connect(u1Coo, u1Coo_internal.u)
  connect(u1Coo_internal.y, cooValPum.u[4])
  connect(u1Hea_internal.y, y1Hea)
  connect(ena.y, enaAndHea.u1)
  connect(u1Hea_internal.y, enaAndHea.u2)
  connect(enaAndHea.y, y1AndHea)
  connect(ena.y, enaAndCoo.u1)
  connect(u1Coo_internal.y, enaAndCoo.u2)
  connect(enaAndCoo.y, y1AndCoo)
  connect(u1Hea_internal.y, rou.u)
  connect(u1HeaOrCoo.y, rou1.u)
  connect(u1Hea_internal.y, off.u1)
  connect(u1Coo_internal.y, off.u2)
  connect(off.y, timHp.u)
  connect(timHp.passed, latValHeaWatIso.clr)
  connect(latValHeaWatIso.y, y1ValHeaWatInlIso)
  connect(u1Hea_internal.y, latValHeaWatIso.u)
  connect(latValChiWatIso.y, y1ValChiWatInlIso)
  connect(timHp.passed, latValChiWatIso.clr)
  connect(latValHeaWatIso.y, y1ValHeaWatOutIso)
  connect(latValChiWatIso.y, y1ValChiWatOutIso)
  connect(rou1.y[1], latPumHeaWatPri.u)
  connect(rou.y[1], latPumHeaWatPri.u)
  connect(timHp.passed, latPumHeaWatPri.clr)
  connect(latPumHeaWatPri.y, y1PumHeaWatPri)
  connect(latPumChiWatPri.y, y1PumChiWatPri)
  connect(timHp.passed, latPumChiWatPri.clr)
  connect(u1Coo_internal.y, latPumChiWatPri.u)
  connect(u1Coo_internal.y, latValChiWatIso.u)
end EventSequencing;


Evaluate failsafe stage up condition. For heating applications with primary-secondary distribution:
The failsafe stage up condition evaluates to true if either of the following is true.

within Buildings.Templates.Plants.Controls.StagingRotation;
block FailsafeCondition
  "Evaluate failsafe stage up condition"
  parameter Buildings.Templates.Plants.Controls.Types.Application typ
    "Type of application"
  parameter Boolean have_pumSec
    "Set to true for primary-secondary distribution, false for primary-only"
  parameter Real dT(
    final min=0,
    final unit="K")
    "Delta-T triggering stage up command (>0)";
  parameter Real dtPri(
    final min=0,
    final unit="s")=900
    "Runtime with high primary-setpoint Delta-T before staging up";
  parameter Real dtSec(
    final min=0,
    final unit="s",
    start=600)=600
    "Runtime with high secondary-primary and secondary-setpoint Delta-T before staging up"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TSecSup(
    final unit="K",
    displayUnit="degC")
    if have_pumSec
    "Secondary supply temperature"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TPriSup(
    final unit="K",
    displayUnit="degC")
    "Primary supply temperature"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TSupSet(
    final unit="K",
    displayUnit="degC")
    "Supply temperature setpoint"
  Buildings.Controls.OBC.CDL.Reals.Subtract dTSecPri
    if have_pumSec
    "Secondary-primary ∆T"
  Buildings.Controls.OBC.CDL.Reals.Subtract dTPriSet
    "Primary-setpoint ∆T"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter polHeaCoo(
    final k=if typ == Buildings.Templates.Plants.Controls.Types.Application.Cooling
      then - 1 else 1)
    "Polarity depending on heating or cooling application"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter polHeaCoo1(
    final k=if typ == Buildings.Templates.Plants.Controls.Types.Application.Cooling
      then - 1 else 1)
    if have_pumSec
    "Polarity depending on heating or cooling application"
  Buildings.Controls.OBC.CDL.Reals.LessThreshold lesPriSet(
    final t=- dT,
    final h=0.1 * dT)
    "True if Delta-T less than threshold"
  Buildings.Controls.OBC.CDL.Reals.LessThreshold lesSecPri(
    final t=- dT,
    final h=0.1 * dT)
    if have_pumSec
    "True if Delta-T less than threshold"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput reset
    "Reset timers to zero at completion of stage change"
  Utilities.TimerWithReset timPriSet(
    final t=dtPri)
    "Timer"
  Utilities.TimerWithReset timSecPri(
    final t=dtSec)
    if have_pumSec
    "Timer"
  Buildings.Controls.OBC.CDL.Logical.Or any
    "Any criterion met"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1
    "Failsafe stage up condition met"
  Utilities.PlaceholderLogical ph(
    final have_inp=have_pumSec,
    final have_inpPh=false,
    final u_internal=false)
    "Placeholder signal"
  Buildings.Controls.OBC.CDL.Reals.Subtract dTSecSet
    if have_pumSec
    "Secondary-setpoint ∆T"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter polHeaCoo2(
    final k=if typ == Buildings.Templates.Plants.Controls.Types.Application.Cooling
      then - 1 else 1)
    if have_pumSec
    "Polarity depending on heating or cooling application"
  Buildings.Controls.OBC.CDL.Reals.LessThreshold lesSecSet(
    final t=- dT,
    final h=0.1 * dT)
    if have_pumSec
    "True if Delta-T less than threshold"
  Utilities.TimerWithReset timSecSet(
    final t=dtSec)
    if have_pumSec
    "Timer"
  Buildings.Controls.OBC.CDL.Logical.And allSec
    if have_pumSec
    "All criteria on secondary temperature met"
equation
  connect(TPriSup, dTPriSet.u1)
  connect(TSupSet, dTPriSet.u2)
  connect(TSecSup, dTSecPri.u1)
  connect(TPriSup, dTSecPri.u2)
  connect(dTPriSet.y, polHeaCoo.u)
  connect(dTSecPri.y, polHeaCoo1.u)
  connect(polHeaCoo1.y, lesSecPri.u)
  connect(polHeaCoo.y, lesPriSet.u)
  connect(lesPriSet.y, timPriSet.u)
  connect(reset, timPriSet.reset)
  connect(lesSecPri.y, timSecPri.u)
  connect(reset, timSecPri.reset)
  connect(any.y, y1)
  connect(dTSecSet.y, polHeaCoo2.u)
  connect(polHeaCoo2.y, lesSecSet.u)
  connect(lesSecSet.y, timSecSet.u)
  connect(reset, timSecSet.reset)
  connect(reset, timSecPri.reset)
  connect(TSecSup, dTSecSet.u1)
  connect(TSupSet, dTSecSet.u2)
  connect(timSecSet.passed, allSec.u1)
  connect(timSecPri.passed, allSec.u2)
  connect(allSec.y, ph.u)
  connect(timPriSet.passed, any.u1)
  connect(ph.y, any.u2)
end FailsafeCondition;


Compute the rolling average of the load on a system. This block computes the load on a system based on the return temperature,
active supply temperature setpoint and measured flow through the associated
circuit flow meter, using the following equation.

within Buildings.Templates.Plants.Controls.StagingRotation;
block LoadAverage
  "Compute the rolling average of the load on a system"
  parameter Buildings.Templates.Plants.Controls.Types.Application typ
    "Type of application"
  parameter Real cp_default(
    final min=0,
    final unit="J/(kg.K)")
    "Default specific heat capacity used to compute required capacity";
  parameter Real rho_default(
    final min=0,
    final unit="kg/m3")
    "Default fluid density used to compute required capacity";
  parameter Real dtMea(
    final min=0,
    final unit="s")=300
    "Duration used to compute the moving average of required capacity";
  Buildings.Controls.OBC.CDL.Reals.Subtract delT(
    y(
      final unit="K"))
    "Compute ∆T"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter capFlo(
    y(
      final unit="W/K"),
    final k=rho_default * cp_default)
    "Compute capacity flow rate"
  Buildings.Controls.OBC.CDL.Reals.Multiply capReq
    "Compute required capacity"
  Buildings.Controls.OBC.CDL.Reals.MovingAverage movAve(
    delta=dtMea)
    "Compute moving average"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TRet(
    final unit="K",
    displayUnit="degC")
    "Return temperature"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TSupSet(
    final unit="K",
    displayUnit="degC")
    "Active supply temperature setpoint"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput V_flow(
    final unit="m3/s")
    "Volume flow rate"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput QReq_flow(
    final unit="W")
    "Load"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter polHeaCoo(
    final k=if typ == Buildings.Templates.Plants.Controls.Types.Application.Cooling
      then - 1 else 1)
    "Polarity depending on heating or cooling application"
equation
  connect(capFlo.y, capReq.u2)
  connect(TSupSet, delT.u1)
  connect(TRet, delT.u2)
  connect(V_flow, capFlo.u)
  connect(movAve.y, QReq_flow)
  connect(delT.y, capReq.u1)
  connect(capReq.y, polHeaCoo.u)
  connect(polHeaCoo.y, movAve.u)
end LoadAverage;


within Buildings.Templates.Plants.Controls;
package StagingRotation "Package of sequences for equipment staging and rotation"
end StagingRotation;


Sort equipment by increasing staging runtime. This block implements the rotation logic for identical parallel
staged equipment that are lead/lag alternated.

within Buildings.Templates.Plants.Controls.StagingRotation;
block SortRuntime
  "Sort equipment by increasing staging runtime"
  parameter Integer nin=0
    "Size of input array"
  parameter Integer idxEquAlt[:]={i for i in 1:nin}
    "Indices of lead/lag alternate equipment"
  final parameter Integer nEquAlt=size(idxEquAlt, 1)
    "Number of lead/lag alternate equipment"
  parameter Real runTim_start[nEquAlt]={60 + i for i in 1:nEquAlt}
    "Staging runtime initial values";
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Run[nin]
    "Boolean signal used to assess equipment runtime"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Ava[nin]
    "Equipment available signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yRunTimLif[nEquAlt]
    "Lifetime runtime"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yRunTimSta[nEquAlt]
    "Staging runtime"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerOutput yIdx[nEquAlt](
    start={i for i in 1:nEquAlt})
    "Indices of equipment sorted by increasing staging runtime"
  Buildings.Controls.OBC.CDL.Logical.TimerAccumulating timRun[nEquAlt]
    "Compute staging runtime"
  Buildings.Controls.OBC.CDL.Logical.Not off[nEquAlt]
    "Return true if equipment off"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant u1Res[nEquAlt](
    each k=false) "Signal for staging runtime reset"
  Buildings.Controls.OBC.CDL.Reals.Sort sor(
    final ascending=true,
    nin=nEquAlt)
    "Sort equipment by increasing weighted runtime"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal weiOffAva[nEquAlt](
    each final realTrue=1E10,
    each final realFalse=1)
    "Weight to be applied to runtime of equipment off and available"
  Buildings.Controls.OBC.CDL.Reals.Multiply appWeiOffAva[nEquAlt]
    "Apply weights to runtime of equipment off and available"
  Buildings.Controls.OBC.CDL.Reals.Multiply voiRunUna[nEquAlt]
    "Void runtime of unavailable equipment"
  Buildings.Controls.OBC.CDL.Logical.And offAva[nEquAlt]
    "Return true if equipment off and available"
  Buildings.Controls.OBC.CDL.Logical.Not una[nEquAlt]
    "Return true if equipment unavailable"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal zerUna[nEquAlt](
    each final realTrue=0,
    each final realFalse=1)
    "Assign zero to unavailable equipment"
  Buildings.Controls.OBC.CDL.Logical.Timer timUna[nEquAlt]
    "Compute time elapsed since equipment is unavailable"
  Buildings.Controls.OBC.CDL.Reals.AddParameter addWei[nEquAlt](
    each final p=1E20)
    "Add weight"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter opp[nEquAlt](
    each final k=- 1)
    "Take opposite value"
  Buildings.Controls.OBC.CDL.Reals.Add addWeiUna[nEquAlt]
    "Add weight to unavailable equipment"
  Buildings.Controls.OBC.CDL.Reals.Multiply voiWeiAva[nEquAlt]
    "Void weight of available equipment"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal zerAva[nEquAlt](
    each final realTrue=0,
    each final realFalse=1)
    "Assign zero to available equipment"
  Buildings.Controls.OBC.CDL.Logical.TimerAccumulating timRunLif[nEquAlt]
    "Compute lifetime runtime"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant fal[nEquAlt](
    each final k=false)
    "Constant"
  Buildings.Controls.OBC.CDL.Routing.BooleanExtractSignal u1RunEquAlt(
    final nin=nin,
    final nout=nEquAlt,
    final extract=idxEquAlt)
    "Extract signal for lead/lag alternate equipment only"
  Buildings.Controls.OBC.CDL.Routing.BooleanExtractSignal u1AvaEquAlt(
    final nin=nin,
    final nout=nEquAlt,
    final extract=idxEquAlt)
    "Extract signal for lead/lag alternate equipment only"
  Buildings.Controls.OBC.CDL.Routing.IntegerExtractor resIdxInp[nEquAlt](
    each final nin=nEquAlt)
    "Restore indices consistent with input vectors"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant idxEquAltMat[nEquAlt, nEquAlt](
    final k={idxEquAlt for i in 1:nEquAlt})
    "Indices of lead/lag alternate equipment repeated nEquAlt times"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant runTimSta[nEquAlt](
    final k=runTim_start)
    "Staging runtime initial values"
  Buildings.Controls.OBC.CDL.Reals.Max iniRunTim[nEquAlt]
    "Fix runtime until it exceeds the initial value"
equation
  connect(u1Res.y, timRun.reset)
  connect(weiOffAva.y, appWeiOffAva.u1)
  connect(off.y, offAva.u1)
  connect(offAva.y, weiOffAva.u)
  connect(appWeiOffAva.y, voiRunUna.u1)
  connect(zerUna.y, voiRunUna.u2)
  connect(una.y, timUna.u)
  connect(una.y, zerUna.u)
  connect(timUna.y, opp.u)
  connect(opp.y, addWei.u)
  connect(addWeiUna.y, sor.u)
  connect(voiRunUna.y, addWeiUna.u1)
  connect(voiWeiAva.y, addWeiUna.u2)
  connect(addWei.y, voiWeiAva.u1)
  connect(zerAva.y, voiWeiAva.u2)
  connect(timRunLif.y, yRunTimLif)
  connect(fal.y, timRunLif.reset)
  connect(u1Run, u1RunEquAlt.u)
  connect(u1RunEquAlt.y, off.u)
  connect(u1RunEquAlt.y, timRunLif.u)
  connect(u1RunEquAlt.y, timRun.u)
  connect(u1Ava, u1AvaEquAlt.u)
  connect(u1AvaEquAlt.y, una.u)
  connect(u1AvaEquAlt.y, zerAva.u)
  connect(u1AvaEquAlt.y, offAva.u2)
  connect(idxEquAltMat.y, resIdxInp.u)
  connect(sor.yIdx, resIdxInp.index)
  connect(resIdxInp.y, yIdx)
  connect(runTimSta.y, iniRunTim.u1)
  connect(timRun.y, iniRunTim.u2)
  connect(iniRunTim.y, appWeiOffAva.u2)
  connect(timRun.y, yRunTimSta)
end SortRuntime;


Compute stage availability. A stage is deemed available if both the following are true:

within Buildings.Templates.Plants.Controls.StagingRotation;
block StageAvailability
  "Compute stage availability"
  parameter Real staEqu[:,:](
    each unit="1",
    each min=0,
    each max=1)
    "Staging matrix – Equipment required for each stage";
  final parameter Integer nSta=size(staEqu, 1)
    "Number of stages"
  final parameter Integer nEqu=size(staEqu, 2)
    "Number of equipment"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Ava[nEqu]
    "Equipment available signal"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1[nSta]
    "Stage available signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant matStaEqu[nSta, nEqu](
    final k=staEqu)
    "Staging matrix"
  Buildings.Controls.OBC.CDL.Reals.GreaterThreshold isReq[nSta, nEqu](
    each final t=0.99)
    "Return true if equipment required without lead/lag alternate"
  Buildings.Controls.OBC.CDL.Reals.GreaterThreshold isReqPosAlt[nSta, nEqu](
    each final t=0)
    "Return true if equipment required (with or without lead/lag alternate)"
  Buildings.Controls.OBC.CDL.Reals.LessThreshold isNotReqNoAlt[nSta, nEqu](
    each final t=1)
    "Return true if equipment not required or required with lead/lag alternate"
  Buildings.Controls.OBC.CDL.Routing.BooleanVectorReplicator booVecRep(
    final nin=nEqu,
    final nout=nSta)
    "Replicate equipment available signal"
  Buildings.Controls.OBC.CDL.Logical.And isReqAva[nSta, nEqu]
    "Return true if equipment required without lead/lag alternate and available"
  Buildings.Controls.OBC.CDL.Logical.And isReqAltAva[nSta, nEqu]
    "Return true if equipment required (with or without lead/lag alternate) and available"
  Buildings.Controls.OBC.CDL.Logical.Or isReqAvaOrNotReq[nSta, nEqu]
    "Return true if equipment required without lead/lag alternate and available, or if not required"
  Buildings.Controls.OBC.CDL.Logical.MultiAnd all[nSta](
    each final nin=nEqu)
    "Return true if previous block condition valid for all elements"
  Buildings.Controls.OBC.CDL.Logical.And isAva[nSta]
    "Return true if stage available"
  Buildings.Controls.OBC.CDL.Reals.MultiSum nEquSta[nSta](
    each final nin=nEqu)
    "Return the number of equipment required at each stage"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToInteger booToInt[nSta, nEqu]
    "Convert to integer"
  Buildings.Controls.OBC.CDL.Integers.GreaterEqual isReqAltAvaGreReq[nSta]
    "Return true if number of required available equipment higher than number of required equipment"
  Buildings.Controls.OBC.CDL.Integers.MultiSum nReqAltAva[nSta](
    each final nin=nEqu)
    "Number of equipment required (with or without lead/lag alternate) and available"
  Buildings.Controls.OBC.CDL.Conversions.RealToInteger nEquStaInt[nSta]
    "Integer cast of number of equipment required at each stage"
equation
  connect(matStaEqu.y, isReq.u)
  connect(matStaEqu.y, isReqPosAlt.u)
  connect(matStaEqu.y, isNotReqNoAlt.u)
  connect(isReq.y, isReqAva.u1)
  connect(booVecRep.y, isReqAva.u2)
  connect(booVecRep.y, isReqAltAva.u2)
  connect(isReqAva.y, isReqAvaOrNotReq.u2)
  connect(isNotReqNoAlt.y, isReqAvaOrNotReq.u1)
  connect(isAva.y, y1)
  connect(isReqAvaOrNotReq.y, all.u)
  connect(matStaEqu.y, nEquSta.u)
  connect(nReqAltAva.y, isReqAltAvaGreReq.u1)
  connect(nReqAltAva.u, booToInt.y)
  connect(nEquSta.y, nEquStaInt.u)
  connect(nEquStaInt.y, isReqAltAvaGreReq.u2)
  connect(u1Ava, booVecRep.u)
  connect(isReqPosAlt.y, isReqAltAva.u1)
  connect(isReqAltAva.y, booToInt.u)
  connect(isReqAltAvaGreReq.y, isAva.u1)
  connect(all.y, isAva.u2)
end StageAvailability;


Generate stage change command. The plant equipment is staged in part based on required capacity, Qrequired,
relative to nominal capacity of a given stage, Qstage.
This ratio is the operative part load ratio, OPLR.

within Buildings.Templates.Plants.Controls.StagingRotation;
block StageChangeCommand
  "Generate stage change command"
  parameter Buildings.Templates.Plants.Controls.Types.Application typ
    "Type of application"
  parameter Boolean have_pumSec
    "Set to true for primary-secondary distribution, false for primary-only"
  parameter Boolean have_inpPlrSta=false
    "Set to true to use an input signal for SPLR, false to use a parameter"
  parameter Real plrSta(
    max=1,
    min=0,
    start=0.9,
    unit="1")=0.9
    "Staging part load ratio"
  final parameter Real traStaEqu[nEqu, nSta]={{staEqu[i, j] for i in 1:nSta} for j in 1:nEqu}
    "Transpose of staging matrix";
  parameter Real staEqu[:,:](
    each final max=1,
    each final min=0,
    each final unit="1")
    "Staging matrix – Equipment required for each stage";
  final parameter Integer nSta=size(staEqu, 1)
    "Number of stages"
  final parameter Integer nEqu=size(staEqu, 2)
    "Number of equipment"
  parameter Real capEqu[nEqu](
    each final min=0,
    each final unit="W")
    "Design capacity of each equipment";
  parameter Real dtRun(
    min=0,
    unit="s")=900
    "Runtime with exceeded staging part load ratio before staging event is triggered";
  parameter Real dtMea(
    min=0,
    unit="s")=300
    "Duration used to compute the moving average of required capacity";
  parameter Real cp_default(min=0, unit="J/(kg.K)")
    "Default specific heat capacity used to compute required capacity";
  parameter Real rho_default(min=0, unit="kg/m3")
    "Default density used to compute required capacity";
  parameter Real dT(min=0, unit="K")
    "Delta-T triggering stage up command (>0)";
  parameter Real dtPri(
    min=0,
    unit="s")=900
    "Runtime with high primary-setpoint Delta-T before staging up";
  parameter Real dtSec(
    min=0,
    start=600,
    unit="s")=600
    "Runtime with high secondary-primary and secondary-setpoint Delta-T before staging up"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1AvaSta[nSta]
    "Stage available signal"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1StaPro
    "Staging process in progress"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput uPlrSta(
    final unit="1",
    final min=0,
    final max=1)
    if have_inpPlrSta
    "Input signal for staging part load ratio"
  // We allow the stage index to be zero, e.g., when the plant is disabled.
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput uSta(
    final min=0,
    final max=nSta)
    "Stage index"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TRet(
    final unit="K",
    displayUnit="degC") "Return temperature used to compute required capacity"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TSupSet(
    final unit="K",
    displayUnit="degC")
    "Active supply temperature setpoint used to compute required capacity"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput V_flow(
    final unit="m3/s") "Volume flow rate used to compute required capacity"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1Up
    "Stage up command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1Dow
    "Stage down command"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant traMatStaEqu[nEqu, nSta](
    final k=traStaEqu)
    "Transpose of staging matrix"
  Buildings.Controls.OBC.CDL.Routing.RealExtractor reqEquSta[nEqu](
    each final nin=nSta)
    "Extract equipment required at given stage"
  Buildings.Controls.OBC.CDL.Routing.IntegerScalarReplicator intScaRep(
    final nout=nEqu)
    "Replicate signal"
  Buildings.Controls.OBC.CDL.Reals.Multiply capEquSta[nEqu]
    "Capacity of each equipment required at given stage"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant capEquPar[nEqu](
    final k=capEqu)
    "Capacity of each equipment"
  Buildings.Controls.OBC.CDL.Reals.MultiSum capSta(
    nin=nEqu)
    "Compute nominal capacity of active stage"
  Buildings.Controls.OBC.CDL.Reals.Greater gre(h=1E-4*min(capEqu))
    "Compare OPLR to SPLR (hysteresis is to avoid chattering with some simulators)"
  Buildings.Templates.Plants.Controls.Utilities.TimerWithReset timUp(
    final t=dtRun)
    "Timer"
  Buildings.Controls.OBC.CDL.Reals.Less les(h=1E-4*min(capEqu))
    "Compare OPLR to SPLR (hysteresis is to avoid chattering with some simulators)"
  Buildings.Templates.Plants.Controls.Utilities.TimerWithReset timDow(
    final t=dtRun)
    "Timer"
  Utilities.HoldReal hol(final dtHol=dtRun)
    "Hold value of required capacity at stage change"
  Buildings.Controls.OBC.CDL.Integers.Max maxInt
    "Maximum between stage index and 1"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant one(
    final k=1)
    "Constant"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant idxSta[nSta](
    final k={i for i in 1:nSta})
    "Stage index"
  Buildings.Controls.OBC.CDL.Integers.Less idxStaLesAct[nSta]
    "Return true if stage index lower than active stage index"
  Buildings.Controls.OBC.CDL.Logical.And idxStaLesActAva[nSta]
    "Return true if stage index lower than active stage index and stage available"
  Buildings.Controls.OBC.CDL.Routing.IntegerScalarReplicator intScaRep1(
    final nout=nSta)
    "Replicate signal"
  Utilities.LastTrueIndex idxLasTru(
    nin=nSta)
    "Index of next available lower stage"
  Buildings.Controls.OBC.CDL.Integers.Max maxInt1
    "Maximum between stage index and 1"
  Buildings.Controls.OBC.CDL.Routing.IntegerScalarReplicator intScaRep2(
    final nout=nEqu)
    "Replicate signal"
  Buildings.Controls.OBC.CDL.Routing.RealExtractor reqEquStaLow[nEqu](
    each final nin=nSta)
    "Extract equipment required at next available lower stage"
  Buildings.Controls.OBC.CDL.Reals.Multiply capEquStaLow[nEqu]
    "Capacity of each equipment required at next available lower stage"
  Buildings.Controls.OBC.CDL.Reals.MultiSum capStaLow(
    nin=nEqu)
    "Compute nominal capacity of next available lower stage"
  Buildings.Controls.OBC.CDL.Integers.Min minInt
    "Minimum between stage index and 1"
  Buildings.Controls.OBC.CDL.Conversions.IntegerToReal intToRea
    "Convert to real value"
  Buildings.Controls.OBC.CDL.Reals.Multiply setZer
    "Set nominal capacity to zero if no lower available stage"
  Buildings.Controls.OBC.CDL.Reals.Multiply splTimCapSta
    "SPLR times capacity of active stage"
  Buildings.Controls.OBC.CDL.Reals.Multiply splTimCapStaLow
    "SPLR times capacity of next available lower stage"
  Utilities.PlaceholderReal parPlrSta(
    final have_inp=have_inpPlrSta,
    final have_inpPh=false,
    final u_internal=plrSta) "Parameter value for SPLR"
  Buildings.Controls.OBC.CDL.Logical.FallingEdge endStaPro
    "True when staging process terminates"
  LoadAverage capReq(
    final typ=typ,
    final cp_default=cp_default,
    final rho_default=rho_default,
    final dtMea=dtMea) "Compute required capacity"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TPriSup(final unit="K",
      displayUnit="degC") "Primary supply temperature"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput TSecSup(final unit="K",
      displayUnit="degC") if have_pumSec
                          "Secondary supply temperature"
  FailsafeCondition faiSaf(
    final typ=typ,
    final have_pumSec=have_pumSec,
    final dT=dT,
    final dtPri=dtPri,
    final dtSec=dtSec) "Failsafe stage up condition "
  Buildings.Controls.OBC.CDL.Logical.Or  effOrFaiSaf
    "Efficiency OR failsafe condition met"
  Buildings.Controls.OBC.CDL.Logical.Not notFaiSaf
    "Failsafe stage up condition is not true"
  Buildings.Controls.OBC.CDL.Logical.And effAndNotFaiSaf
    "Efficiency condition met AND failsafe stage up condition is not true"
equation
  connect(intScaRep.y, reqEquSta.index)
  connect(traMatStaEqu.y, reqEquSta.u)
  connect(reqEquSta.y, capEquSta.u1)
  connect(capEquPar.y, capEquSta.u2)
  connect(capEquSta.y, capSta.u)
  connect(intScaRep.u, maxInt.y)
  connect(idxSta.y, idxStaLesAct.u1)
  connect(uSta, intScaRep1.u)
  connect(intScaRep1.y, idxStaLesAct.u2)
  connect(idxStaLesAct.y, idxStaLesActAva.u1)
  connect(u1AvaSta, idxStaLesActAva.u2)
  connect(idxStaLesActAva.y, idxLasTru.u1)
  connect(idxLasTru.y, maxInt1.u2)
  connect(one.y, maxInt1.u1)
  connect(maxInt1.y, intScaRep2.u)
  connect(uSta, maxInt.u1)
  connect(one.y, maxInt.u2)
  connect(intScaRep2.y, reqEquStaLow.index)
  connect(traMatStaEqu.y, reqEquStaLow.u)
  connect(reqEquStaLow.y, capEquStaLow.u2)
  connect(capEquPar.y, capEquStaLow.u1)
  connect(capEquStaLow.y, capStaLow.u)
  connect(idxLasTru.y, minInt.u2)
  connect(one.y, minInt.u1)
  connect(minInt.y, intToRea.u)
  connect(capStaLow.y, setZer.u1)
  connect(intToRea.y, setZer.u2)
  connect(hol.y, gre.u1)
  connect(splTimCapSta.y, gre.u2)
  connect(capSta.y, splTimCapSta.u2)
  connect(setZer.y, splTimCapStaLow.u2)
  connect(splTimCapStaLow.y, les.u2)
  connect(hol.y, les.u1)
  connect(gre.y, timUp.u)
  connect(les.y, timDow.u)
  connect(uPlrSta, parPlrSta.u)
  connect(parPlrSta.y, splTimCapSta.u1)
  connect(parPlrSta.y, splTimCapStaLow.u1)
  connect(u1StaPro, hol.u1)
  connect(endStaPro.y, timUp.reset)
  connect(endStaPro.y, timDow.reset)
  connect(TSupSet, capReq.TSupSet)
  connect(TRet, capReq.TRet)
  connect(V_flow, capReq.V_flow)
  connect(capReq.QReq_flow, hol.u)
  connect(u1StaPro, endStaPro.u)
  connect(TPriSup, faiSaf.TPriSup)
  connect(endStaPro.y, faiSaf.reset)
  connect(TSecSup, faiSaf.TSecSup)
  connect(TSupSet, faiSaf.TSupSet)
  connect(faiSaf.y1, effOrFaiSaf.u1)
  connect(timUp.passed, effOrFaiSaf.u2)
  connect(effOrFaiSaf.y, y1Up)
  connect(faiSaf.y1, notFaiSaf.u)
  connect(timDow.passed, effAndNotFaiSaf.u2)
  connect(notFaiSaf.y, effAndNotFaiSaf.u1)
  connect(effAndNotFaiSaf.y, y1Dow)
end StageChangeCommand;


Checks successful completion of stage change. Block that detects a stage change and evaluates whether the stage
transition is completed.

within Buildings.Templates.Plants.Controls.StagingRotation;
block StageCompletion
  "Checks successful completion of stage change"
  parameter Integer nin=0
    "Size of input array"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1[nin]
    "Equipment enable command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1_actual[nin]
    "Equipment status"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1End
    "Successful completion of stage change"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1
    "Stage change in progress"
  Buildings.Controls.OBC.CDL.Logical.And enaAndOn[nin]
    "True if equipment enabled and on status returned"
  Buildings.Controls.OBC.CDL.Logical.Nor disAndOff[nin]
    "True if equipment disabled and off status returned"
  Buildings.Controls.OBC.CDL.Logical.MultiAnd allTru(
    nin=nin)
    "True if all inputs true"
  Buildings.Controls.OBC.CDL.Logical.Or onOrOff[nin]
    "True if on or off condition met"
  Buildings.Controls.OBC.CDL.Logical.FallingEdge endStaPro
    "True when staging process terminates"
  Buildings.Controls.OBC.CDL.Logical.Latch lckChaSta
    "Lock stage change signal until conditions on equipment command and status met"
  Buildings.Controls.OBC.CDL.Logical.Change cha[nin]
    "True if enable signal changes"
  Buildings.Controls.OBC.CDL.Logical.Latch lckAnyCha
    "Lock equipment command change signal until next stage change"
  Buildings.Controls.OBC.CDL.Logical.MultiOr anyCha(
    nin=nin)
    "True if any enable signal changes"
  Buildings.Controls.OBC.CDL.Logical.And chaAndMat
    "True if enable command changed and equipment status matches command"
  Buildings.Controls.OBC.CDL.Logical.Pre           preAnyCha
    "Left-limit of signal to guard against concomitant stage change and command change"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput uSta
    "Stage index"
  Buildings.Controls.OBC.CDL.Integers.Change chaSta
    "Return true when stage change is initiated"
equation
  connect(u1_actual, disAndOff.u2)
  connect(u1_actual, enaAndOn.u2)
  connect(endStaPro.y, y1End)
  connect(lckChaSta.y, y1)
  connect(cha.y, anyCha.u)
  connect(u1, cha.u)
  connect(u1, enaAndOn.u1)
  connect(lckAnyCha.y, chaAndMat.u1)
  connect(chaAndMat.y, lckChaSta.clr)
  connect(lckChaSta.y, endStaPro.u)
  connect(u1, disAndOff.u1)
  connect(enaAndOn.y, onOrOff.u1)
  connect(disAndOff.y, onOrOff.u2)
  connect(onOrOff.y, allTru.u)
  connect(allTru.y, chaAndMat.u2)
  connect(anyCha.y,preAnyCha. u)
  connect(preAnyCha.y, lckAnyCha.u)
  connect(chaSta.y, lckChaSta.u)
  connect(chaSta.y, lckAnyCha.clr)
  connect(uSta, chaSta.u)
end StageCompletion;


Validation model for the evaluation of equipment availability. This model validates

Buildings.Templates.Plants.Controls.StagingRotation.EquipmentAvailability
for heating-only applications (component avaHeaCoo) and heating and cooling
applications (component avaHea).

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model EquipmentAvailability "Validation model for the evaluation of equipment availability"
  Buildings.Templates.Plants.Controls.StagingRotation.EquipmentAvailability
    avaHeaCoo(have_heaWat=true, have_chiWat=true)
    "Evaluate equipment availability – Heating and cooling"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable u1Ava(
    table=[
      0, 1;
      8, 0;
      9, 1],
    timeScale=1000,
    period=10000)
    "Equipment available signal"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable u1_actual(
    table=[
      0, 1;
      1, 0;
      2.5, 1;
      4.2, 0;
      5, 1;
      7, 1;
      8, 0;
      9, 1],
    timeScale=1000,
    period=10000)
    "Equipment status"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable u1Hea_actual(
    table=[
      0, 1;
      1, 1;
      2, 1;
      5, 0;
      8.5, 1;
      9.7, 0],
    timeScale=1000,
    period=10000)
    "Equipment operating mode"
  Buildings.Templates.Plants.Controls.StagingRotation.EquipmentAvailability
    avaHea(have_heaWat=true, have_chiWat=false)
    "Evaluate equipment availability – Heating only"
equation
  connect(u1Hea_actual.y[1], avaHeaCoo.u1Hea)
  connect(u1_actual.y[1], avaHeaCoo.u1)
  connect(u1_actual.y[1], avaHea.u1)
  connect(u1Ava.y[1], avaHeaCoo.u1Ava)
  connect(u1Ava.y[1], avaHea.u1Ava)
end EquipmentAvailability;


Validation model for equipment enable logic. This model validates

Buildings.Templates.Plants.Controls.StagingRotation.EquipmentEnable
in a configuration with three equally sized units (component equEnaEqu)
and in a configuration with one small unit and two large equally sized
units (component equEnaOneTwo).
Only the units of the same size are lead/lag alternated.

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model EquipmentEnable "Validation model for equipment enable logic"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable u1AvaEqu(
    table=[
      0, 1, 1, 1;
      6, 0, 0, 1;
      8, 0, 1, 1;
      10, 1, 0, 1;
      15, 1, 1, 0;
      18, 0, 1, 1;
      22, 1, 1, 1],
    timeScale=1,
    period=25)
    "Equipment available signal"
  Buildings.Templates.Plants.Controls.StagingRotation.EquipmentEnable equEnaOneTwo(
    staEqu=[
      1, 0, 0;
      0, 1 / 2, 1 / 2;
      1, 1 / 2, 1 / 2;
      0, 1, 1;
      1, 1, 1])
    "Compute array of enabled equipment – One small equipment, two large equally sized equipment"
  Buildings.Controls.OBC.CDL.Integers.Sources.TimeTable uSta(
    table=[
      0, 1;
      5, 2;
      10, 3;
      15, 4;
      20, 5],
    timeScale=1,
    period=25)
    "Stage index"
  Buildings.Templates.Plants.Controls.StagingRotation.EquipmentEnable equEnaEqu(
    staEqu=[
      1 / 3, 1 / 3, 1 / 3;
      2 / 3, 2 / 3, 2 / 3;
      1, 1, 1])
    "Compute array of enabled equipment – Equally sized units"
  Buildings.Controls.OBC.CDL.Integers.Sources.TimeTable uSta1(
    table=[
      0, 1;
      10, 2;
      20, 3],
    timeScale=1,
    period=25)
    "Stage index"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea[3]
    "Cast to real"
  Buildings.Controls.OBC.CDL.Reals.Sort sort(
    ascending=false,
    nin=3)
    "Sort lead/lag alternate equipment with available equipment first"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea1[2]
    "Cast to real"
  Buildings.Controls.OBC.CDL.Reals.Sort sort1(
    ascending=false,
    nin=2)
    "Sort lead/lag alternate equipment with available equipment first"
  Buildings.Controls.OBC.CDL.Integers.AddParameter addPar[2](
    each final p=1)
    "Restore indices with respect to original vector u1AvaEqu"
equation
  connect(u1AvaEqu.y, equEnaOneTwo.u1Ava)
  connect(uSta.y[1], equEnaOneTwo.uSta)
  connect(uSta1.y[1], equEnaEqu.uSta)
  connect(u1AvaEqu.y, equEnaEqu.u1Ava)
  connect(u1AvaEqu.y, booToRea.u)
  connect(booToRea.y, sort.u)
  connect(sort.yIdx, equEnaEqu.uIdxAltSor)
  connect(u1AvaEqu.y[2:3], booToRea1.u)
  connect(booToRea1.y, sort1.u)
  connect(sort1.yIdx, addPar.u)
  connect(addPar.y, equEnaOneTwo.uIdxAltSor)
end EquipmentEnable;


Validation model for event sequencing logic. This model validates

Buildings.Templates.Plants.Controls.StagingRotation.EventSequencing
for the following configurations.

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model EventSequencing "Validation model for event sequencing logic"
  Buildings.Templates.Plants.Controls.StagingRotation.EventSequencing seqEveHea(
    have_heaWat=true,
    have_chiWat=false,
    have_valInlIso=true,
    have_valOutIso=false,
    have_pumHeaWatPri=true,
    have_pumHeaWatSec=true)
    "Event sequencing – Heating-only system with primary-secondary distribution"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable u1(
    table=[0,0,0; 1,1,0; 2,0,0; 3,0,1; 4,0,0; 5,0,0],
    timeScale=900,
    period=4500)
    "Command signal – Index 1 for heating command, 2 for cooling command"
  Buildings.Templates.Plants.Controls.StagingRotation.EventSequencing seqEveHeaCoo(
    have_heaWat=true,
    have_chiWat=true,
    have_valInlIso=true,
    have_valOutIso=true,
    have_pumHeaWatPri=true,
    have_pumChiWatPri=false,
    have_pumHeaWatSec=false,
    have_pumChiWatSec=false)
    "Event sequencing – Heating and cooling system with primary-only distribution"
  Components.Controls.StatusEmulator y1Pum_actual "Pump Status"
  Components.Controls.StatusEmulator y1Pum_actual1 "Pump Status"
equation
  connect(u1.y[1], seqEveHea.u1Hea)
  connect(u1.y[1], seqEveHea.u1PumHeaWatSec_actual)
  connect(u1.y[1], seqEveHeaCoo.u1Hea)
  connect(u1.y[2], seqEveHeaCoo.u1Coo)
  connect(seqEveHea.y1PumHeaWatPri, y1Pum_actual.y1)
  connect(y1Pum_actual.y1_actual, seqEveHea.u1PumHeaWatPri_actual)
  connect(seqEveHeaCoo.y1PumHeaWatPri, y1Pum_actual1.y1)
  connect(y1Pum_actual1.y1_actual, seqEveHeaCoo.u1PumHeaWatPri_actual)
end EventSequencing;


Validation model for failsafe staging logic. This model validates

Buildings.Templates.Plants.Controls.StagingRotation.FailsafeCondition
for the following configurations.

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model FailsafeCondition
  "Validation model for failsafe staging logic"
  Buildings.Templates.Plants.Controls.StagingRotation.FailsafeCondition faiSafHea(
    typ=Buildings.Templates.Plants.Controls.Types.Application.Heating,
    have_pumSec=true,
    dT=2.5)
    "Failsafe stage up condition for heating applications"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant THeaWatSupSet(
    k=60 + 273.15)
    "HWST setpoint"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin THeaWatPriSup(
    amplitude=5,
    freqHz=1 / 3000,
    offset=THeaWatSupSet.k)
    "PHWST"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin THeaWatSecSup(
    amplitude=5,
    freqHz=1 / 3000,
    phase=1.7453292519943,
    offset=THeaWatSupSet.k)
    "SHWST"
  Buildings.Controls.OBC.CDL.Logical.Sources.Pulse booPul(
    width=1 / 2000,
    period=2000,
    shift=100)
    "Reset"
  Buildings.Templates.Plants.Controls.StagingRotation.FailsafeCondition faiSafCooPri(
    typ=Buildings.Templates.Plants.Controls.Types.Application.Cooling,
    have_pumSec=false,
    dT=1)
    "Failsafe stage up condition – Cooling application, primary-only"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TChiWatSupSet(
    k=7 + 273.15)
    "CHWST setpoint"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TChiWatPriSup(
    amplitude=3,
    freqHz=1 / 3000,
    offset=TChiWatSupSet.k)
    "PCHWST"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TChiWatSecSup(
    amplitude=3,
    freqHz=1 / 3000,
    phase=1.7453292519943,
    offset=TChiWatSupSet.k)
    "SCHWST"
  Buildings.Templates.Plants.Controls.StagingRotation.FailsafeCondition faiSafCoo(
    typ=Buildings.Templates.Plants.Controls.Types.Application.Cooling,
    have_pumSec=true,
    dT=1)
    "Failsafe stage up condition – Cooling application"
equation
  connect(THeaWatSupSet.y, faiSafHea.TSupSet)
  connect(THeaWatPriSup.y, faiSafHea.TPriSup)
  connect(THeaWatSecSup.y, faiSafHea.TSecSup)
  connect(booPul.y, faiSafHea.reset)
  connect(TChiWatSupSet.y, faiSafCooPri.TSupSet)
  connect(TChiWatPriSup.y, faiSafCooPri.TPriSup)
  connect(booPul.y, faiSafCooPri.reset)
  connect(TChiWatSupSet.y, faiSafCoo.TSupSet)
  connect(booPul.y, faiSafCoo.reset)
  connect(TChiWatPriSup.y, faiSafCoo.TPriSup)
  connect(TChiWatSecSup.y, faiSafCoo.TSecSup)
end FailsafeCondition;


Validation model for heating and cooling load calculation. This model validates

Buildings.Templates.Plants.Controls.StagingRotation.LoadAverage
for heating and cooling applications.

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model LoadAverage
  "Validation model for heating and cooling load calculation"
  Buildings.Templates.Plants.Controls.StagingRotation.LoadAverage loaHea(
    typ=Buildings.Templates.Plants.Controls.Types.Application.Heating,
    cp_default=4186,
    rho_default=1000)
    "Calculate heating load"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant THeaWatSupSet(
    k=60 + 273.15)
    "HWST setpoint"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin THeaWatRet(
    amplitude=10,
    freqHz=2 / 3000,
    phase=3.1415926535898,
    offset=THeaWatSupSet.k)
    "HWRT"
  Buildings.Controls.OBC.CDL.Reals.Sources.Ramp V_flow(
    height=- 0.1,
    duration=100,
    offset=0.1,
    startTime=1200)
    "Volume flow rate"
  Buildings.Templates.Plants.Controls.StagingRotation.LoadAverage loaCoo(
    typ=Buildings.Templates.Plants.Controls.Types.Application.Cooling,
    cp_default=4186,
    rho_default=1000)
    "Calculate cooling load"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TChiWatSupSet(
    k=7 + 273.15)
    "CHWST setpoint"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TChiWatRet(
    amplitude=5,
    freqHz=2 / 3000,
    phase=3.1415926535898,
    offset=TChiWatSupSet.k)
    "CHWRT"
equation
  connect(THeaWatSupSet.y, loaHea.TSupSet)
  connect(TChiWatSupSet.y, loaCoo.TSupSet)
  connect(THeaWatRet.y, loaHea.TRet)
  connect(V_flow.y, loaHea.V_flow)
  connect(TChiWatRet.y, loaCoo.TRet)
  connect(V_flow.y, loaCoo.V_flow)
end LoadAverage;


Collection of validation models

within Buildings.Templates.Plants.Controls.StagingRotation;
package Validation "Collection of validation models"
end Validation;


Validation model for equipment runtime sorting logic. The simulation shows that even wear is achieved among available equipment.
When it becomes unavailable, equipment #1 is sent to last position
(sorRunTim.yIdx[3]=1),
and automatically moves up in the staging order only if another
equipment (#3) becomes unavailable
(sorRunTim.yIdx[3]=3 and sorRunTim.yIdx[2]=1).

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model SortRuntime "Validation model for equipment runtime sorting logic"
  Buildings.Templates.Plants.Controls.StagingRotation.SortRuntime sorRunTim(nin=3)
    "Sort runtime"
  Utilities.TrueArrayConditional u1Ena(
    nin=3)
    "Equipment enable signal"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable u1AvaEqu(
    table=[0,1,1,1; 1500,0,1,1; 2500,0,1,0; 3000,1,1,1],
    timeScale=1,
    period=5000)
    "Equipment available signal"
  Utilities.StageIndex idxSta(
    nSta=3,
    dtRun=60)
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant u1Lea(
    k=true)
    "Lead equipment enable signal"
  Buildings.Controls.OBC.CDL.Logical.Sources.SampleTrigger upPul(
    period=60)
    "Stage up command pulse"
  Buildings.Controls.OBC.CDL.Logical.Sources.SampleTrigger dowPul(
    period=60)
    "Stage down command pulse"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable booTimTab(
    table=[
      0, 0, 0;
      60, 1, 0;
      300, 0, 1],
    period=500)
    "Signal to inhibit up and down commands"
  Buildings.Controls.OBC.CDL.Logical.And up
    "Stage up command"
  Buildings.Controls.OBC.CDL.Logical.And dow
    "Stage up command"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant u1AvaSta[3](
    each k=true)
    "Stage available signal"
  Buildings.Controls.OBC.CDL.Logical.And run[3]
    "Returns true if equipment is enabled and available"
  Components.Controls.StatusEmulator y1_actual[3] "Equipment status"
equation
  connect(sorRunTim.yIdx, u1Ena.uIdx)
  connect(u1AvaEqu.y[1:3], sorRunTim.u1Ava[1:3])
  connect(u1Lea.y, idxSta.u1Lea)
  connect(upPul.y, up.u2)
  connect(booTimTab.y[2], dow.u1)
  connect(booTimTab.y[1], up.u1)
  connect(dowPul.y, dow.u2)
  connect(up.y, idxSta.u1Up)
  connect(dow.y, idxSta.u1Dow)
  connect(u1AvaSta.y, idxSta.u1AvaSta)
  connect(idxSta.y, u1Ena.u)
  connect(u1Ena.y1, run.u1)
  connect(u1AvaEqu.y[1:3], run.u2)
  connect(run.y, y1_actual.y1)
  connect(y1_actual.y1_actual, sorRunTim.u1Run)
end SortRuntime;


Validation model for the evaluation of stage availability. This model validates

Buildings.Templates.Plants.Controls.StagingRotation.StageAvailability
in a configuration with three equally sized units (component avaStaEqu)
and in a configuration with one small unit and two large equally sized
units (component avaStaOneTwo).
Only the units of the same size are lead/lag alternated.

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model StageAvailability "Validation model for the evaluation of stage availability"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable u1AvaEqu(
    table=[
      0, 0, 0, 0;
      1, 1, 0, 0;
      2, 0, 1, 0;
      3, 0, 0, 1;
      4, 1, 1, 0;
      5, 0, 1, 1;
      6, 1, 1, 1;
      7, 0, 0, 0],
    timeScale=1,
    period=7)
    "Equipment available signal"
  Buildings.Templates.Plants.Controls.StagingRotation.StageAvailability avaStaEqu(
    staEqu=[
      1 / 3, 1 / 3, 1 / 3;
      2 / 3, 2 / 3, 2 / 3;
      1, 1, 1])
    "Compute stage availability – Equally sized units"
  Buildings.Templates.Plants.Controls.StagingRotation.StageAvailability avaStaOneTwo(
    staEqu=[
      1, 0, 0;
      0, 1 / 2, 1 / 2;
      1, 1 / 2, 1 / 2;
      0, 1, 1;
      1, 1, 1])
    "Compute stage availability – One small equipment, two large equally sized equipment"
equation
  connect(u1AvaEqu.y, avaStaEqu.u1Ava)
  connect(u1AvaEqu.y, avaStaOneTwo.u1Ava)
end StageAvailability;


Validation model for stage change logic. This model validates

Buildings.Templates.Plants.Controls.StagingRotation.StageChangeCommand
in a configuration with one small unit and two large equally sized
units (component avaStaOneTwo).
In response to a varying flow rate, the variation of the
required capacity chaSta.capReq.y triggers stage change
events.
The block

Buildings.Templates.Plants.Controls.Utilities.StageIndex
is used to illustrate how these events translate into
a varying plant stage index idxSta.y.

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model StageChangeCommand "Validation model for stage change logic"
  parameter Real cp_default(
    final unit="J/(kg.K)")=4184
    "Default specific heat capacity"
  parameter Real rho_default(
    final unit="kg/m3")=996
    "Default specific heat capacity"
  final parameter Real capHea_nominal(
    final unit="W")=sum(chaSta.capEqu)
    "Installed heating capacity"
  parameter Real THeaWatSup_nominal(
    final unit="K",
    displayUnit="degC")=50 + 273.15
    "Design HW supply temperature"
  final parameter Real THeaWatRet_nominal(
    final unit="K",
    displayUnit="degC")=42 + 273.15
    "Design HW return temperature"
  parameter Real VHeaWat_flow_nominal(
    final unit="m3/s")=capHea_nominal /(THeaWatSup_nominal - THeaWatRet_nominal) /
    cp_default / rho_default
    "Design primary HW volume flow rate"
  final parameter Integer nSta=size(chaSta.staEqu, 1)
    "Number of stages"
  Buildings.Controls.OBC.CDL.Reals.Sources.TimeTable ratV_flow(
    table=[
      0, 0;
      1, 0;
      5, 0.2;
      6, 0.01;
      11, 1;
      12, 1;
      18, 0],
    timeScale=1000)
    "Source signal for volume flow rate ratio"
  Buildings.Templates.Plants.Controls.StagingRotation.StageChangeCommand chaSta(
    typ=Buildings.Templates.Plants.Controls.Types.Application.Heating,
    have_pumSec=false,
    plrSta=0.9,
    staEqu=[1,0,0; 0,1/2,1/2; 1,1/2,1/2; 0,1,1; 1,1,1],
    capEqu=1E3*{100,450,450},
    cp_default=cp_default,
    rho_default=rho_default,
    dT=2.5) "Generate stage change command"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TSupSet(
    final k=THeaWatSup_nominal)
    "HWST setpoint"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TRet(
    final k=THeaWatRet_nominal)
    "HWRT"
  Utilities.StageIndex idxSta(
    final nSta=nSta, dtRun=900)
    "Compute stage index"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant u1Lea(
    k=true)
    "Lead equipment enable signal"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant u1AvaSta[nSta](
    each k=true)
    "Stage available signal"
  Buildings.Controls.OBC.CDL.Logical.TrueFalseHold y1UpHol(
    final falseHoldDuration=0, trueHoldDuration=1)
    "Hold stage up command for plotting"
  Buildings.Controls.OBC.CDL.Logical.TrueFalseHold y1DowHol(
    final falseHoldDuration=0, trueHoldDuration=1)
    "Hold stage down command for plotting"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter V_flow(
    final k=VHeaWat_flow_nominal)
    "Scale by design flow"
  Buildings.Templates.Plants.Controls.StagingRotation.EquipmentEnable enaEqu(
    final staEqu=chaSta.staEqu)
    "Enable equipment"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant idxEquLeaLag[2](
    final k={2, 3})
    "Indices of lead/lag equipment"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant u1AvaEqu[3](
    each final k=true)
    "Equipment available signal"
  Components.Controls.StatusEmulator staEqu[3](
    each delayTime=15)
    "Evaluate equipment status"
  Buildings.Templates.Plants.Controls.StagingRotation.StageCompletion comSta(
    nin=3)
    "Check completion of stage change"
equation
  connect(TRet.y, chaSta.TRet)
  connect(TSupSet.y, chaSta.TSupSet)
  connect(chaSta.y1Up, idxSta.u1Up)
  connect(chaSta.y1Dow, idxSta.u1Dow)
  connect(u1Lea.y, idxSta.u1Lea)
  connect(u1AvaSta.y, idxSta.u1AvaSta)
  connect(idxSta.y, chaSta.uSta)
  connect(chaSta.y1Up, y1UpHol.u)
  connect(chaSta.y1Dow, y1DowHol.u)
  connect(u1AvaSta.y, chaSta.u1AvaSta)
  connect(ratV_flow.y[1], V_flow.u)
  connect(V_flow.y, chaSta.V_flow)
  connect(idxSta.y, enaEqu.uSta)
  connect(idxEquLeaLag.y, enaEqu.uIdxAltSor)
  connect(u1AvaEqu.y, enaEqu.u1Ava)
  connect(enaEqu.y1, staEqu.y1)
  connect(comSta.y1, chaSta.u1StaPro)
  connect(enaEqu.y1, comSta.u1)
  connect(staEqu.y1_actual, comSta.u1_actual)
  connect(idxSta.y, comSta.uSta)
  connect(TSupSet.y, chaSta.TPriSup)
end StageChangeCommand;


Validation model for the evaluation of stage completion. This model validates

Buildings.Templates.Plants.Controls.StagingRotation.StageCompletion
in a configuration with one small unit and two large equally sized
units (component avaStaOneTwo).
In response to a varying flow rate, the variation of the
required capacity chaSta.capReq.y triggers stage change
events.
The block

Buildings.Templates.Plants.Controls.Utilities.StageIndex
is used to illustrate how these events translate into
a varying plant stage index idxSta.y.

within Buildings.Templates.Plants.Controls.StagingRotation.Validation;
model StageCompletion "Validation model for the evaluation of stage completion"
  Buildings.Templates.Plants.Controls.StagingRotation.StageCompletion comSta(
    nin=2)
    "Check successful completion of stage change"
  Buildings.Controls.OBC.CDL.Logical.Sources.TimeTable booTimTab(
    table=[
      0, 0, 0, 0, 0;
      1, 0, 0, 0, 0;
      3, 1, 0, 0, 0;
      6, 1, 0, 1, 0;
      21, 1, 0, 1, 0;
      22, 0, 0, 1, 0;
      24, 0, 0, 0, 0],
    timeScale=60,
    period=1800)
    "Source for Boolean signals"
  Buildings.Controls.OBC.CDL.Logical.TrueFalseHold y1ComSta(
    final falseHoldDuration=0, trueHoldDuration=1)
    "Hold stage completion signal for plotting"
  Buildings.Controls.OBC.CDL.Integers.Sources.TimeTable idxSta(
    table=[
      0, 0;
      1, 1;
      21, 0],
    timeScale=60,
    period=1800)
    "Stage index"
initial equation
  Modelica.Utilities.Streams.print("At initialization: comSta.y1End = " + String(comSta.y1End));
equation
  when initial() then
    Modelica.Utilities.Streams.print("When initial(): comSta.y1End = " + String(comSta.y1End));
  end when;
  connect(comSta.y1End, y1ComSta.u)
  connect(booTimTab.y[1:2], comSta.u1[1:2])
  connect(booTimTab.y[3:4], comSta.u1_actual[1:2])
  connect(idxSta.y[1], comSta.uSta)
end StageCompletion;


Output the number of true elements in a Boolean array. Counts the number of true elements in the Boolean
input vector.

within Buildings.Templates.Plants.Controls.Utilities;
block CountTrue
  "Output the number of true elements in a Boolean array"
  parameter Integer nin(
    min=0)=0
    "Size of input array"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1[nin]
    "Array of Boolean signals"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerOutput y
    "Index of first element being true"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToInteger booToInt[nin](
    each final integerTrue=1,
    each final integerFalse=0)
    "Cast to integer"
  Buildings.Controls.OBC.CDL.Integers.MultiSum sumMul(
    final nin=nin)
    "Sum over array elements"
equation
  connect(u1, booToInt.u)
  connect(sumMul.y, y)
  connect(booToInt.y, sumMul.u)
end CountTrue;


Size of input array. Returns the index of the first true element of the Boolean
input vector.
If no element is true, the block returns 0.

within Buildings.Templates.Plants.Controls.Utilities;
block FirstTrueIndex
  parameter Integer nin(
    min=0)=0
    "Size of input array"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1[nin]
    "Array of Boolean signals"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerOutput y
    "Index of first element being true"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToInteger booToInt[nin](
    final integerTrue={i for i in 1:nin},
    each final integerFalse=nin + 1)
    "Return index if element is true, 0 otherwise"
  MultiMinInteger mulMin(nin=nin) "Return minimum index"
  Buildings.Controls.OBC.CDL.Integers.Equal intEqu
  Buildings.Controls.OBC.CDL.Integers.Switch intSwi
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant conInt(
    k=nin + 1)
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant zer(
    k=0)
equation
  connect(booToInt.y, mulMin.u)
  connect(u1, booToInt.u)
  connect(conInt.y, intEqu.u2)
  connect(mulMin.y, intSwi.u3)
  connect(intEqu.y, intSwi.u2)
  connect(zer.y, intSwi.u1)
  connect(mulMin.y, intEqu.u1)
  connect(intSwi.y, y)
end FirstTrueIndex;


Hold value of real signal based on timer and Boolean signal. Holds input value fixed at its last value while the Boolean signal
u1 is true, and for at least the hold time dtHol.

within Buildings.Templates.Plants.Controls.Utilities;
block HoldReal "Hold value of real signal based on timer and Boolean signal"
  parameter Real dtHol(
    final unit="s")=0
    "Hold time";
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1
    "Boolean signal that triggers fixed output value"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput u
    "Input signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput y
    "Output signal"
  Buildings.Controls.OBC.CDL.Discrete.TriggeredSampler triSam
    "Sample signal when the Boolean input switches to true"
  Buildings.Controls.OBC.CDL.Reals.Switch swi
    "Switch between actual and fixed value"
  Buildings.Controls.OBC.CDL.Logical.TrueFalseHold truHol(
    final falseHoldDuration=0, final trueHoldDuration=dtHol)
    "Hold true value of input signal for given time"
equation
  connect(u, triSam.u)
  connect(swi.y, y)
  connect(u1, triSam.trigger)
  connect(u1, truHol.u)
  connect(triSam.y, swi.u1)
  connect(u, swi.u3)
  connect(truHol.y, swi.u2)
end HoldReal;


Force signal value at initial time. At initial time, this block returns the
value specified by the parameter y1Ini, which
is set to false by default.
Otherwise, this block returns the
value of the input value, i.e., it acts as a direct pass-through.

within Buildings.Templates.Plants.Controls.Utilities;
block Initialization
  "Force signal value at initial time"
  parameter Boolean yIni=false
    "Initial value";
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u
    "Input"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y
    "Output"
equation
  y=if initial() then yIni else u;
end Initialization;


Size of input array. Returns the index of the last true element of the Boolean
input vector.
If no element is true, the block returns 0.

within Buildings.Templates.Plants.Controls.Utilities;
block LastTrueIndex
  parameter Integer nin(
    min=0)=0
    "Size of input array"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1[nin]
    "Array of Boolean signals"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerOutput y
    "Index of first element being true"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToInteger booToInt[nin](
    final integerTrue={i for i in 1:nin},
    each final integerFalse=0)
    "Return index if element is true, 0 otherwise"
  Buildings.Templates.Plants.Controls.Utilities.MultiMaxInteger mulMax(nin=nin)
    "Return minimum index"
equation
  connect(booToInt.y, mulMax.u)
  connect(u1, booToInt.u)
  connect(mulMax.y, y)
end LastTrueIndex;


Output the maximum element of the input vector. Outputs the maximum element of the input vector.

within Buildings.Templates.Plants.Controls.Utilities;
block MultiMaxInteger "Output the maximum element of the input vector"
  parameter Integer nin(
    min=0)=0
    "Size of input array"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput u[nin]
    "Integer input signal"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerOutput y
    "Integer output signal"
equation
  y=max(u);
end MultiMaxInteger;


Output the minimum element of the input vector. Outputs the minimum element of the input vector.

within Buildings.Templates.Plants.Controls.Utilities;
block MultiMinInteger "Output the minimum element of the input vector"
  parameter Integer nin(
    min=0)=0
    "Size of input array"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput u[nin]
    "Integer input signal"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerOutput y
    "Integer output signal"
equation
  y=min(u);
end MultiMinInteger;


Package with utility blocks

within Buildings.Templates.Plants.Controls;
package Utilities "Package with utility blocks"
end Utilities;


PID controller with enable signal. This is an update of

Buildings.Controls.OBC.CDL.Reals.PIDWithReset
with an additional enable signal provided as a Boolean input.

within Buildings.Templates.Plants.Controls.Utilities;
block PIDWithEnable
  "PID controller with enable signal"
  extends Modelica.Blocks.Icons.Block;
  parameter Buildings.Controls.OBC.CDL.Types.SimpleController controllerType=
    Buildings.Controls.OBC.CDL.Types.SimpleController.PI
    "Type of controller";
  parameter Real k(
    min=0)=1
    "Gain of controller";
  parameter Modelica.Units.SI.Time Ti(
    min=Buildings.Controls.OBC.CDL.Constants.small)=0.5
    "Time constant of integrator block"
  parameter Modelica.Units.SI.Time Td(
    min=0)=0.1
    "Time constant of derivative block"
  parameter Real r(
    min=100 * Modelica.Constants.eps)=1
    "Typical range of control error, used for scaling the control error";
  parameter Real yMin=0
    "Lower limit of output";
  parameter Real yMax=1
    "Upper limit of output";
  parameter Boolean reverseActing=true
    "Set to true for reverse acting, or false for direct acting control action";
  parameter Real y_reset=yMin
    "Value to which the controller output is reset if the boolean trigger has a rising edge";
  parameter Real y_neutral=y_reset
    "Value to which the controller output is reset when the controller is disabled";
  Buildings.Controls.OBC.CDL.Interfaces.RealInput u_s
    "Connector of setpoint input signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput u_m
    "Connector of measurement input signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput y
    "Connector of actuator output signal"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput uEna
    "Enable signal"
  Buildings.Controls.OBC.CDL.Reals.PIDWithReset conPID(
    final k=k,
    final Ti=Ti,
    final Td=Td,
    final r=r,
    final controllerType=controllerType,
    final yMin=yMin,
    final yMax=yMax,
    final reverseActing=reverseActing,
    final y_reset=y_reset)
  Buildings.Controls.OBC.CDL.Reals.Switch swi
  Buildings.Controls.OBC.CDL.Reals.Switch swi1
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant valDis(
    final k=y_neutral)
    "Value when disabled"
equation
  connect(conPID.u_s, swi.y)
  connect(uEna, swi.u2)
  connect(u_s, swi.u1)
  connect(u_m, swi.u3)
  connect(uEna, conPID.trigger)
  connect(u_m, conPID.u_m)
  connect(conPID.y, swi1.u1)
  connect(swi1.y, y)
  connect(uEna, swi1.u2)
  connect(valDis.y, swi1.u3)
end PIDWithEnable;


Output a placeholder signal. This block enables replacing a variable that is conditionally
removed with either a parameter or another input variable.

within Buildings.Templates.Plants.Controls.Utilities;
block PlaceholderInteger "Output a placeholder signal"
  parameter Boolean have_inp=true
    "Set to true if input signal is available"
  parameter Boolean have_inpPh=false
    "Set to true if placeholder value is provided with input signal"
  parameter Integer u_internal(
    start=0)
    "Placeholder value if input signal is not available"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput u
    if have_inp
    "Input"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput uPh
    if not have_inp and have_inpPh "Input"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerOutput y
    "Output"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant ph(final k=u_internal)
    if not have_inp and not have_inpPh
    "Placeholder signal if input signal is not available"
equation
  connect(u, y)
  connect(uPh, y)
  connect(ph.y, y)
end PlaceholderInteger;


within Buildings.Templates.Plants.Controls.Utilities;
block PlaceholderLogical "Output a placeholder signal"
  parameter Boolean have_inp=true
    "Set to true if input signal is available"
  parameter Boolean have_inpPh=false
    "Set to true if placeholder value is provided with input signal"
  parameter Boolean u_internal(start=true)
    "Placeholder value if input signal is not available"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u
    if have_inp
    "Input"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput uPh
    if not have_inp and have_inpPh "Input"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y
    "Output"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant ph(final k=u_internal)
    if not have_inp and not have_inpPh
    "Placeholder signal if input signal is not available"
equation
  connect(u, y)
  connect(ph.y, y)
  connect(uPh, y)
end PlaceholderLogical;


within Buildings.Templates.Plants.Controls.Utilities;
block PlaceholderReal "Output a placeholder signal"
  parameter Boolean have_inp=true
    "Set to true if input signal is available"
  parameter Boolean have_inpPh=false
    "Set to true if placeholder value is provided with input signal"
  parameter Real u_internal(
    start=0)
    "Placeholder value if input signal is not available"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput u
    if have_inp
    "Input"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput uPh
    if not have_inp and have_inpPh "Input"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput y
    "Output"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant ph(final k=u_internal)
    if not have_inp and not have_inpPh
    "Placeholder signal if input signal is not available"
equation
  connect(u, y)
  connect(ph.y, y)
  connect(uPh, y)
end PlaceholderReal;


Evaluation of stage index from staging signals. This block is used to compute the stage index for a group of
multiple equipment such as CHW pumps or chillers.

within Buildings.Templates.Plants.Controls.Utilities;
block StageIndex
  "Evaluation of stage index from staging signals"
  parameter Boolean have_inpAva=true
    "Set to true if stage availability is provided with input signal, false for stages always available"
  parameter Integer nSta(
    start=1,
    final min=1)
    "Number of stages"
  parameter Real dtRun(
    final unit="s",
    final min=0,
    displayUnit="min")=0
    "Minimum runtime of each stage"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Lea
    "Lead unit enable signal"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Up
    "Stage up command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1Dow
    "Stage down command"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u1AvaSta[nSta]
    if have_inpAva
    "Stage available signal"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerOutput y
    "Stage index"
  Modelica.StateGraph.InitialStepWithSignal sta0(
    final nOut=nSta,
    final nIn=nSta)
    "Stage 0 – All units disabled"
  Modelica.StateGraph.StepWithSignal sta[nSta](
    each final nIn=nSta + 1,
    each final nOut=nSta + 1)
    "Stage i>0"
  Modelica.StateGraph.TransitionWithSignal sta0ToSta[nSta](
    each final enableTimer=false)
    "Transition from stage 0 to stage i>0"
  inner Modelica.StateGraph.StateGraphRoot stateGraphRoot
    "State graph root"
  Modelica.StateGraph.TransitionWithSignal staToSta[nSta, nSta](
    each final enableTimer=false)
    "Transition to higher or lower stage"
  Buildings.Controls.OBC.CDL.Logical.And runAndTrn[nSta]
    "Runtime condition met AND stage transition command"
  Buildings.Controls.OBC.CDL.Routing.BooleanScalarReplicator rep(
    final nout=nSta)
    "Replicate"
  Modelica.StateGraph.TransitionWithSignal staToSta0[nSta](
    each final enableTimer=false)
    "Transition from stage i>0 to stage 0"
  Buildings.Controls.OBC.CDL.Routing.BooleanScalarReplicator rep2(
    final nout=nSta)
    "Replicate"
  Buildings.Controls.OBC.CDL.Logical.Not notLea
    "True if lead unit is disabled"
  Buildings.Controls.OBC.CDL.Logical.MultiAnd upAndEna(
    nin=3)
    "Stage up and lead unit enabled and higher stage available"
  Utilities.FirstTrueIndex idxFirAct(
    nin=nSta)
    "Return index of first active stage"
  Buildings.Controls.OBC.CDL.Logical.Not una[nSta]
    "True if stage is not available"
  Buildings.Controls.OBC.CDL.Logical.And runAndEna[nSta]
    "Runtime condition met AND lead enable signal false"
  Buildings.Controls.OBC.CDL.Routing.BooleanExtractor staUna(
    final nin=nSta)
    "Return true if current stage is unavailable"
  Buildings.Controls.OBC.CDL.Integers.Max maxInt
    "Maximum between current stage index and 1"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant one(
    final k=1)
    "Constant"
  PlaceholderLogical pas[nSta](each final have_inp=dtRun > 0, each final
      have_inpPh=true) "Direct pass-through when no minimum runtime"
  FirstTrueIndex idxFirAva(
    nin=nSta)
    "Return index of first available stage"
  LastTrueIndex idxLasAva(
    nin=nSta)
    "Return index of last available stage"
  Buildings.Controls.OBC.CDL.Integers.Greater higAva
    "Return true if there is any higher stage available"
  Buildings.Controls.OBC.CDL.Integers.Less lowAva
    "Return true if there is any lower stage available"
  Buildings.Controls.OBC.CDL.Logical.Timer tim[nSta](
    passed(
      each start=false),
    each final t=dtRun)
    if dtRun > 0
    "Timer for minimum runtime"
  PlaceholderLogical phAvaSta[nSta](
    each final have_inp=have_inpAva,
    each final have_inpPh=false,
    each final u_internal=true) "Placeholder value if signal is not available"
  Buildings.Controls.OBC.CDL.Logical.MultiAnd dowAndEna(
    nin=3)
    "Stage down and lead unit enabled and lower stage available"
  Buildings.Controls.OBC.CDL.Logical.Or upOrDow
    "Stage up or down"
  Buildings.Controls.OBC.CDL.Integers.Switch idxNex
    "Return index of next stage to be enabled"
  Buildings.Controls.OBC.CDL.Integers.Less intLesEqu[nSta]
    "Return true if index less or equal to active stage index minus one"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant idxStaAll[nSta](
    final k={i for i in 1:nSta})
    "Stage indices"
  Buildings.Controls.OBC.CDL.Routing.IntegerScalarReplicator intScaRep(
    nout=nSta)
    "Replicate signal"
  Buildings.Controls.OBC.CDL.Logical.And andAva[nSta]
    "True if previous condition met and stage available"
  LastTrueIndex idxNexLowAva(
    final nin=nSta)
    "Return index of next lower available stage"
  FirstTrueIndex idxNexHigAva(
    final nin=nSta)
    "Return index of next higher available stage"
  Buildings.Controls.OBC.CDL.Integers.Greater intGreEqu[nSta]
    "Return true if index greater or equal to active stage index plus one"
  Buildings.Controls.OBC.CDL.Logical.And andAva1[nSta]
    "True if previous condition met and stage available"
  Buildings.Controls.OBC.CDL.Logical.Or upOrActUna
    "Stage up command or active stage unavailable"
  TrueArrayConditional truIdxNex(
    final nout=nSta,
    nin=1)
    "Generate array with true value at index of next stage to be enabled"
  Buildings.Controls.OBC.CDL.Routing.BooleanVectorReplicator repVec(
    final nin=nSta,
    final nout=nSta)
    "Replicate vector"
  Buildings.Controls.OBC.CDL.Routing.BooleanScalarReplicator rep3[nSta](
    each final nout=nSta)
    "Replicate signal"
  Buildings.Controls.OBC.CDL.Logical.And matTrn[nSta, nSta]
    "Generate matrix with a maximum of one true element where transition must fire"
  Buildings.Controls.OBC.CDL.Logical.And actUnaHigAva
    "Active stage unavailable and higher stage available"
  Buildings.Controls.OBC.CDL.Logical.Or upOrDowOrActUna[nSta]
    "Stage up or down command or active stage unavailable (not subject to runtime requirement)"
  Buildings.Controls.OBC.CDL.Routing.BooleanScalarReplicator rep1(
    final nout=nSta)
    "Replicate"
  TrueArrayConditional truNexHigAva(
    final nout=nSta,
    nin=1)
    "Generate array with true element at index of next higher available stage (if any, otherwise all false)"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToInteger booToInt(
    final integerTrue=1,
    final integerFalse=0)
    "Cast to integer"
equation
  for i in 1:nSta loop
    for j in 1:nSta loop
      connect(staToSta[i, j].outPort, sta[j].inPort[i])
      connect(sta[i].outPort[j], staToSta[i, j].inPort)
    end for;
  end for;
  // Transitio to/from stage 0 – All units disabled.
  connect(sta.outPort[nSta + 1], staToSta0.inPort)
  connect(sta0ToSta.outPort, sta.inPort[nSta + 1])
  connect(staToSta0.outPort, sta0.inPort)
  connect(sta0.outPort, sta0ToSta.inPort)
  connect(u1Lea, notLea.u)
  connect(runAndEna.y, staToSta0.condition)
  connect(sta.active, idxFirAct.u1)
  connect(maxInt.y, staUna.index)
  connect(una.y, staUna.u)
  connect(idxFirAct.y, y)
  connect(idxLasAva.y, higAva.u1)
  connect(idxFirAva.y, lowAva.u1)
  connect(idxFirAct.y, higAva.u2)
  connect(idxFirAct.y, lowAva.u2)
  connect(one.y, maxInt.u1)
  connect(idxFirAct.y, maxInt.u2)
  connect(sta.active, tim.u)
  connect(tim.passed, pas.u)
  connect(sta.active, pas.uPh)
  connect(u1AvaSta, phAvaSta.u)
  connect(phAvaSta.y, una.u)
  connect(phAvaSta.y, idxLasAva.u1)
  connect(phAvaSta.y, idxFirAva.u1)
  connect(u1Lea, upAndEna.u[1])
  connect(u1Lea, dowAndEna.u[1])
  connect(u1Up, upAndEna.u[2])
  connect(u1Dow, dowAndEna.u[2])
  connect(higAva.y, upAndEna.u[3])
  connect(lowAva.y, dowAndEna.u[3])
  connect(upOrDow.y, rep.u)
  connect(pas.y, runAndTrn.u1)
  connect(rep.y, runAndTrn.u2)
  connect(intScaRep.y, intLesEqu.u2)
  connect(idxStaAll.y, intLesEqu.u1)
  connect(intLesEqu.y, andAva.u1)
  connect(phAvaSta.y, andAva.u2)
  connect(andAva.y, idxNexLowAva.u1)
  connect(idxStaAll.y, intGreEqu.u1)
  connect(intGreEqu.y, andAva1.u1)
  connect(phAvaSta.y, andAva1.u2)
  connect(andAva1.y, idxNexHigAva.u1)
  connect(u1Up, upOrActUna.u1)
  connect(staUna.y, upOrActUna.u2)
  connect(upOrActUna.y, idxNex.u2)
  connect(andAva1.y, idxNexHigAva.u1)
  connect(idxNexHigAva.y, idxNex.u1)
  connect(idxNexLowAva.y, idxNex.u3)
  connect(one.y, truIdxNex.u)
  connect(truIdxNex.y1, repVec.u)
  connect(rep3.y, matTrn.u1)
  connect(repVec.y, matTrn.u2)
  connect(matTrn.y, staToSta.condition)
  connect(dowAndEna.y, upOrDow.u2)
  connect(staUna.y, actUnaHigAva.u2)
  connect(higAva.y, actUnaHigAva.u1)
  connect(actUnaHigAva.y, rep1.u)
  connect(rep1.y, upOrDowOrActUna.u2)
  connect(idxNex.y, truIdxNex.uIdx[1])
  connect(truNexHigAva.y1, sta0ToSta.condition)
  connect(u1Lea, booToInt.u)
  connect(booToInt.y, truNexHigAva.u)
  connect(idxFirAva.y, truNexHigAva.uIdx[1])
  connect(upAndEna.y, upOrDow.u1)
  connect(notLea.y, rep2.u)
  connect(runAndTrn.y, upOrDowOrActUna.u1)
  connect(upOrDowOrActUna.y, rep3.u)
  connect(rep2.y, runAndEna.u1)
  connect(pas.y, runAndEna.u2)
  connect(idxFirAct.y, intScaRep.u)
  connect(intScaRep.y, intGreEqu.u2)
end StageIndex;


Timer measuring the time from the time instant where the Boolean input became true. This block is similar to

Buildings.Controls.OBC.CDL.Logical.Timer,
but introduces an additional Boolean input signal reset,
which resets the timer.
When reset becomes true:
y=0 and passed=false.

within Buildings.Templates.Plants.Controls.Utilities;
block TimerWithReset
  "Timer measuring the time from the time instant where the Boolean input became true"
  parameter Real t(
    final quantity="Time",
    final unit="s")=0
    "Threshold time for comparison";
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput u
    "Input that switches timer on if true, and off if false"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanInput reset
    "Reset signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput y(
    final quantity="Time",
    final unit="s")
    "Elapsed time"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput passed
    "True if the elapsed time is greater than threshold"
protected
  discrete Real entryTime(
    final quantity="Time",
    final unit="s")
    "Time instant when u became true";
initial equation
  pre(entryTime)=time;
  pre(passed)=t <= 0;
equation
  when u and not reset then
    entryTime=time;
    // When u becomes true, and t=0, we want passed to be true
    // at the first step (in superdense time).
    passed=t <= 0;
  elsewhen reset then
    entryTime=time;
    passed=false;
  elsewhen
          (u and time >= t + pre(entryTime)) then
    passed=true;
    entryTime=pre(entryTime);
  elsewhen not u then
    // Set passed to false.
    // This is the behavior a timer would have if the threshold test is done with a greater block connected to the output of the timer
    passed=false;
    entryTime=pre(entryTime);
  end when;
  y=if u then time - entryTime else 0.0;
end TimerWithReset;


Output a Boolean array with a given number of true elements and a priority order. Accepts indices of true elements that may not be within the
range of indices of the output vector.
In this, the number of true elements will not be met.

within Buildings.Templates.Plants.Controls.Utilities;
block TrueArrayConditional
  "Output a Boolean array with a given number of true elements and a priority order"
  parameter Integer nin(
    min=0)=0
    "Size of input array"
  parameter Integer nout(
    min=0)=nin
    "Size of output array"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput u
    "Number of true elements"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput uIdx[nin]
    "Array of indices by order of priority to be true"
  Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y1[nout]
    "Output"
protected
  Integer iTru
    "Iteration variable - Count number of true elements";
  Integer iIdx
    "Iteration variable - Count number of indices in input vector";
initial equation
  pre(u)=0;
algorithm
  iTru := 0;
  iIdx := 1;
  y1 := fill(false, nout);
  while
       (iTru < u) and (iIdx <= nin) loop
    if (uIdx[iIdx] >= 1) and (uIdx[iIdx] <= nout) then
      y1[uIdx[iIdx]] := true;
      iTru := iTru + 1;
    end if;
    iIdx := iIdx + 1;
  end while;
end TrueArrayConditional;


Validation model. Validation model for the block

Buildings.Templates.Plants.Controls.Utilities.CountTrue.

within Buildings.Templates.Plants.Controls.Utilities.Validation;
model CountTrue "Validation model"
  Utilities.FirstTrueIndex couTru(nin=6) "Return number of true elements"
  Utilities.FirstTrueIndex couTruAllFal(nin=6)
    "Return number of true elements – All elements of input vector are false"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant conAllFal[6](k=fill(false,
        6)) "Constant array"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant con[6](k={false,true,false,
        false,true,false}) "Constant array"
equation
  connect(con.y, couTru.u1)
  connect(conAllFal.y, couTruAllFal.u1)
end CountTrue;


Validation model. Validation model for the block

Buildings.Templates.Plants.Controls.Utilities.FirstTrueIndex.

within Buildings.Templates.Plants.Controls.Utilities.Validation;
model FirstTrueIndex
  "Validation model"
  Utilities.FirstTrueIndex idxFirTru(
    nin=6)
    "Return first true index"
  Utilities.FirstTrueIndex idxFirTruAllFal(nin=6)
    "Return first true index – All elements of input vector are false"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant conAllFal[6](k=fill(false,
        6)) "Constant array"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant con[6](k={false,true,
        false,false,true,false}) "Constant array"
equation
  connect(con.y, idxFirTru.u1)
  connect(conAllFal.y, idxFirTruAllFal.u1)
end FirstTrueIndex;


Validation model. Validation model for the block

Buildings.Templates.Plants.Controls.Utilities.HoldValue.

within Buildings.Templates.Plants.Controls.Utilities.Validation;
model HoldReal "Validation model"
  Buildings.Templates.Plants.Controls.Utilities.HoldReal hol(dtHol=0)
    "Hold signal – No minimum hold time"
  Buildings.Controls.OBC.CDL.Logical.Sources.Pulse booPul(width=0.2, period=30)
    "Source signal for hold trigger"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin sin(freqHz=1/60)
    "Source signal for value to hold"
  Buildings.Templates.Plants.Controls.Utilities.HoldReal holTim(dtHol=20)
    "Hold signal – With minimum hold time"
equation
  connect(booPul.y, hol.u1)
  connect(sin.y, hol.u)
  connect(booPul.y, holTim.u1)
  connect(sin.y, holTim.u)
end HoldReal;


within Buildings.Templates.Plants.Controls.Utilities.Validation;
model LastTrueIndex
  "Validation model"
  Buildings.Templates.Plants.Controls.Utilities.LastTrueIndex idxLasTru(
    nin=6)
    "Return last true index"
  Buildings.Templates.Plants.Controls.Utilities.LastTrueIndex idxLasTruAllFal(nin=6)
    "Return last true index – All elements of input vector are false"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant conAllFal[6](k=fill(false,
        6)) "Constant array"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant con[6](k={false,true,
        false,false,true,false}) "Constant array"
equation
  connect(con.y, idxLasTru.u1)
  connect(conAllFal.y, idxLasTruAllFal.u1)
end LastTrueIndex;


Block that outputs the maximum element of the input vector. Validation model for the block

Buildings.Templates.Plants.Controls.Utilities.MultiMaxInteger.

within Buildings.Templates.Plants.Controls.Utilities.Validation;
model MultiMaxInteger
  Buildings.Templates.Plants.Controls.Utilities.MultiMaxInteger mulMax(nin=5)
    "Block that outputs the maximum element of the input vector"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant con[5](
    k={1, 2, 3, 4, 5})
    "Constant vector"
equation
  connect(con.y,mulMax. u)
end MultiMaxInteger;


Block that outputs the minimum element of the input vector. Validation model for the block

Buildings.Templates.Plants.Controls.Utilities.MultiMinInteger.

within Buildings.Templates.Plants.Controls.Utilities.Validation;
model MultiMinInteger
  Buildings.Templates.Plants.Controls.Utilities.MultiMinInteger mulMin(nin=5)
    "Block that outputs the minimum element of the input vector"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant con[5](
    k={1, 2, 3, 4, 5})
    "Constant vector"
equation
  connect(con.y, mulMin.u)
end MultiMinInteger;


Collection of validation models

within Buildings.Templates.Plants.Controls.Utilities;
package Validation "Collection of validation models"
end Validation;


Validation model. Validation model for the block

Buildings.Templates.Plants.Controls.Utilities.PlaceholderInteger.

within Buildings.Templates.Plants.Controls.Utilities.Validation;
model PlaceholderInteger "Validation model"
  Buildings.Templates.Plants.Controls.Utilities.PlaceholderInteger
                                                                phPar(
    have_inp=false,
      have_inpPh=false, u_internal=1) "Placeholder parameter"
  Buildings.Controls.OBC.CDL.Integers.Sources.Pulse pul(period=1)
    "Source signal for value to replace"
  Buildings.Templates.Plants.Controls.Utilities.PlaceholderInteger
                                                                phInp(have_inp=
        false,
      have_inpPh=true) "Placeholder signal"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant con(k=2) "Constant"
  Buildings.Templates.Plants.Controls.Utilities.PlaceholderInteger
                                                                phNo(
    have_inp=true,
    have_inpPh=false,
    u_internal=3) "No placeholder: use input signal"
equation
  connect(pul.y, phNo.u)
  connect(con.y, phInp.uPh)
  connect(pul.y, phPar.u)
  connect(pul.y, phInp.u)
end PlaceholderInteger;