ERV unit supply mass flow rate greater than Supply FanOnOff mass flow rate

asked 2019-09-30 10:06:54 -0600

updated 2019-10-08 04:27:11 -0600


I used Energy Management System to control supply and exhaust FanOnOff (which is used in an ERV unit) air mass flow rate using actuator "Fan" with control type "Fan Air Mass Flow Rate". The EMS worked well but the supply and exhaust flow rate of ERV (Energy Recovery Ventilator) unit were not modified with the values of supply and exhaust FanOnOff air mass flow rate. Therefore, I received a value of zone mechanical ventilation mass flow rate which is different than the FanOnOff mass flow rate but is the same as ERV supply and exhaust flow rate predefined at the beginning of the simulation.

Does anybody have an idea for this? Did I make some mistakes here?

Thank you very much

Please find below my EMS snippet:

def arguments(model)
    args =

    occupation_erv_fan_position = OpenStudio::Measure::OSArgument.makeDoubleArgument('occupation_erv_fan_position', true)
    occupation_erv_fan_position.setDisplayName('Position of fan mass flow rate control during occupation period')
    args << occupation_erv_fan_position

    absent_erv_fan_position = OpenStudio::Measure::OSArgument.makeDoubleArgument('absent_erv_fan_position', true)
    absent_erv_fan_position.setDisplayName('Position of fan mass flow rate control during absent period')
    args << absent_erv_fan_position

    night_erv_fan_position = OpenStudio::Measure::OSArgument.makeDoubleArgument('night_erv_fan_position', true)
    night_erv_fan_position.setDisplayName('Position of fan mass flow rate control during night time')
    args << night_erv_fan_position   

    return args

 def run(model, runner, user_arguments)
    super(model, runner, user_arguments)

    # use the built-in error checking
    if !runner.validateUserArguments(arguments(model), user_arguments)
      return false

    # assign the user inputs to variables
    occupation_erv_fan_position = runner.getDoubleArgumentValue('occupation_erv_fan_position', user_arguments).to_f
    absent_erv_fan_position = runner.getDoubleArgumentValue('absent_erv_fan_position', user_arguments).to_f
    night_erv_fan_position = runner.getDoubleArgumentValue('night_erv_fan_position', user_arguments).to_f    

    # get thermal zones
    thermal_zones = model.getThermalZones

    thermal_zones.each do |thermal_zone|

      zone_eqpt = # vector

      zone_eqpt.each do |eqpt| # for each equipment

        # Zone HVAC Energy Recovery Ventilator
        if eqpt.to_ZoneHVACEnergyRecoveryVentilator.is_initialized

          # get Energy Recovery Ventilator Unit
          erv_unit = eqpt.to_ZoneHVACEnergyRecoveryVentilator.get          
          erv_supply_mass_flow_rate = TPEEHVACHelper.erv_supply_flow_rate(thermal_zone).to_f*1.204   # supply air flow rate is in m3/s and assuming that air density is of 1.204 kg/m3
          erv_exhaust_mass_flow_rate = TPEEHVACHelper.erv_supply_flow_rate(thermal_zone).to_f*1.204 # supply air flow rate is in m3/s and assuming that air density is of 1.204 kg/m3

          erv_supplyAirFan = erv_unit.supplyAirFan.to_FanOnOff.get
          erv_exhaustAirFan = erv_unit.exhaustAirFan.to_FanOnOff.get

          # Create EMS Actuator Objects
          erv_supplyAirFan_ems_actuator =,"Fan","Fan Air Mass Flow Rate")
          runner.registerInfo("EMS Actuator object named '#{}' representing the mass flow rate of erv supply air fan named #{} added to the model.") 

          erv_exhaustAirFan_ems_actuator =,"Fan","Fan Air Mass Flow Rate")
          runner.registerInfo("EMS Actuator object named '#{}' representing the mass flow rate of erv supply air fan named #{} added to the model.") 

          # Create new EnergyManagementSystem:Program object 
          erv_fan_mass_flow_rate_ems_prg =
          erv_fan_mass_flow_rate_ems_prg.addLine("IF ((Hour >= 0) && (Hour <= 6)) || (Hour >= 22)") # night time
          erv_fan_mass_flow_rate_ems_prg.addLine("SET #{} = #{erv_supply_mass_flow_rate}*#{night_erv_fan_position}/3.0")
          erv_fan_mass_flow_rate_ems_prg.addLine("SET #{} = #{erv_exhaust_mass_flow_rate}*#{night_erv_fan_position}/3.0")
          #erv_fan_mass_flow_rate_ems_prg ...
edit retag flag offensive close merge delete


IIUC, you see the EMS program doing its thing, but it's overriden by something else later? Can you show your EMS snippet so we can look at the calling point etc?

Julien Marrec's avatar Julien Marrec  ( 2019-10-04 02:56:46 -0600 )edit

Hi Julien, thank you very much for your comment. Please see my updated question.

vlle's avatar vlle  ( 2019-10-04 03:20:58 -0600 )edit

BeginTimestepBeforePredictor is most likely a wrong calling point as it should be overriden by the normal operation of the program (95% sure...). AfterPredictorAfterHVACManager looks like a better guess (not saying it's right).

Please read the documentation in the EMS Application guide (eg:

Julien Marrec's avatar Julien Marrec  ( 2019-10-04 16:18:30 -0600 )edit

Hi Julien, thanks a lot for your suggestion. I tried to modified ems calling point with AfterPredictorAfterHVACManager but it does not work either (on my updated question you'll see the difference between zone mechanical ventilation and controlled (by ems) fan onoff mass flow rate) when there is no ems these values are the same. By the way, thanks again for your comment, I'll try to dig more on EMS Application Guide to better understand how to chose a correct calling point. Long

vlle's avatar vlle  ( 2019-10-07 02:14:24 -0600 )edit

Below is what I received as answer from for this question

"This type of EMS override can be tricky. The ERV code wasn't written to allow the fan to override the mass flow rates. It assumes the unit is at full flow whenever it is on.

The only direct control on the ERV with EMS is to actuate the ERV Availability Schedule to turn the ERV on or off. If you use a short time step, say 1 minute, then you could get the desired mass flow rate by determining how many minutes it should be on each hour. ..."

vlle's avatar vlle  ( 2019-11-25 06:23:03 -0600 )edit