Repository: NREL/OpenStudio (language: python)

Issue:
Add water heaters to supply side of different loops
Enhancement Request
--------------
Support adding a water heater to the supply side of different plant loops.

## Detailed Description
In EnergyPlus, water heaters can be connected to the supply side of two different plant loops through the source/use side connections. It is currently not possible to do so in OpenStudio. One example of this configuration is heat recovery chiller through a ChillerElectricEIR object.
Add water heaters to supply side of different loops
Enhancement Request
--------------
Support adding a water heater to the supply side of different plant loops.

## Detailed Description
In EnergyPlus, water heaters can be connected to the supply side of two different plant loops through the source/use side connections. It is currently not possible to do so in OpenStudio. One example of this configuration is heat recovery chiller through a ChillerElectricEIR object.
Addresses #4311, add water heaters to supply side of different loops
Pull request overview
---------------------

 - Fixes #4311

- Checklist:
  - [x] `WaterHeater:Mixed`
  - [x] `WaterHeater:Stratified`
  - [x] Exclude `sourceSidePlantLoop` from `plantLoop` (like `tertiaryPlantLoop`):
    - [x] Override `plantLoop` from `WaterHeaterMixed`? or
    - [ ] ~Move `sourceSidePlantLoop` to `WaterToWaterComponent`?~
  - [x] Create `useSidePlantLoop` as helper method to `plantLoop`

- Questions:

FT not working correctly?
```
Branch,
  First Plant Loop Supply Inlet Branch,   !- Name
  ,                                       !- Pressure Drop Curve Name
  Pump:ConstantSpeed,                     !- Component Object Type 1
  First Pump,                             !- Component Name 1
  Node 66,                                !- Component Inlet Node Name 1
  Node 73,                                !- Component Outlet Node Name 1
  WaterHeater:Mixed,                      !- Component Object Type 2
  Water Heater Mixed 1,                   !- Component Name 2
  Node 73,                                !- Component Inlet Node Name 2
  Node 72;                                !- Component Outlet Node Name 2
```
```
Branch,
  Second Plant Loop Supply Branch 1,      !- Name
  ,                                       !- Pressure Drop Curve Name
  WaterHeater:Mixed,                      !- Component Object Type 1
  Water Heater Mixed 1,                   !- Component Name 1
  Node 73,                                !- Component Inlet Node Name 1
  Node 72;                                !- Component Outlet Node Name 1
```
Component inlet/outlet nodes are the same. The second needs to be `Source Side Inlet Node Name` and `Source Side Outlet Node Name`, respectively. I think we need some kind of switch [here](https://github.com/NREL/OpenStudio/blob/develop/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp#L354-L355) that sets demand inlet/outlet nodes instead of supply. Or [here](https://github.com/NREL/OpenStudio/blob/develop/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp#L662) with `isSupplyBranch=false`. What should that switch be? **Update**: using `tertiaryPlantLoop` as a guide, I wrote a `sourceSidePlantLoop` method.

### Pull Request Author

<!--- Add to this list or remove from it as applicable.  This is a simple templated set of guidelines. -->

 - [x] Model API Changes / Additions
 - [x] Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
 - [x] Model API methods are tested (in `src/model/test`)
 - [x] EnergyPlus ForwardTranslator Tests (in `src/energyplus/Test`)
 - [x] If a new object or method, added a test in NREL/OpenStudio-resources: https://github.com/NREL/OpenStudio-resources/pull/164
 - [ ] If needed, added VersionTranslation rules for the objects (`src/osversion/VersionTranslator.cpp`)
 - [ ] Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
 - [x] All new and existing tests passes
 - [ ] If methods have been deprecated, update rest of code to use the new methods

**Labels:**

 - [ ] If change to an IDD file, add the label `IDDChange`
 - [ ] If breaking existing API, add the label `APIChange`
 - [x] If deemed ready, add label `Pull Request - Ready for CI` so that CI builds your PR

### Review Checklist

This will not be exhaustively relevant to every PR.
 - [ ] Perform a Code Review on GitHub
 - [ ] Code Style, strip trailing whitespace, etc.
 - [ ] All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
 - [ ] Labeling is ok
 - [ ] If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
 - [ ] If feature, test running new feature, try creative ways to break it
 - [ ] CI status: all green or justified

Add water heaters to supply side of different loops
Enhancement Request
--------------
Support adding a water heater to the supply side of different plant loops.

## Detailed Description
In EnergyPlus, water heaters can be connected to the supply side of two different plant loops through the source/use side connections. It is currently not possible to do so in OpenStudio. One example of this configuration is heat recovery chiller through a ChillerElectricEIR object.
Review PR #4534 (for issue #4311) - Proposed changes
Pull request overview
---------------------

Review PR #4534 (for issue #4311) - Proposed changes

**implement changes for the WaterHeaterMixed class (and only this one)**

### Pull Request Author

<!--- Add to this list or remove from it as applicable.  This is a simple templated set of guidelines. -->

 - [ ] Model API Changes / Additions
 - [ ] Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
 - [ ] Model API methods are tested (in `src/model/test`)
 - [ ] EnergyPlus ForwardTranslator Tests (in `src/energyplus/Test`)
 - [ ] If a new object or method, added a test in NREL/OpenStudio-resources: Add Link
 - [ ] If needed, added VersionTranslation rules for the objects (`src/osversion/VersionTranslator.cpp`)
 - [ ] Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
 - [ ] All new and existing tests passes
 - [ ] If methods have been deprecated, update rest of code to use the new methods

**Labels:**

 - [ ] If change to an IDD file, add the label `IDDChange`
 - [ ] If breaking existing API, add the label `APIChange`
 - [ ] If deemed ready, add label `Pull Request - Ready for CI` so that CI builds your PR

### Review Checklist

This will not be exhaustively relevant to every PR.
 - [ ] Perform a Code Review on GitHub
 - [ ] Code Style, strip trailing whitespace, etc.
 - [ ] All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
 - [ ] Labeling is ok
 - [ ] If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
 - [ ] If feature, test running new feature, try creative ways to break it
 - [ ] CI status: all green or justified



Discussion / hints:
@jmarrec  I think this would affect the OS app.  Do you have any thoughts on this? 
Doesn't matter if it affects the OSApp or not, not the problem of the SDK (but yes, it's manageable on the App side).

I'm more worried about changing the way things are in terms of API. It might be confusing for users to have addToNode suddenly behave differently. Here is the way things work right now

```ruby
m = Model.new
wh = WaterHeaterMixed.new(m)
p1 = PlantLoop.new(m)
p2 = PlantLoop.new(m)

wh.addToNode(p1.supplyInletNode)
raise if wh.plantLoop.empty?
raise if !wh.secondaryPlantLoop.empty?
raise if wh.plantLoop.get != p1
raise if p1.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 1
raise if p2.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 0

# Connect to another node on the supply side? It disconnects from the first plant loop and reconnects to another
wh.addToNode(p2.supplyInletNode)
raise if wh.plantLoop.empty?
raise if !wh.secondaryPlantLoop.empty?
raise if wh.plantLoop.get != p2
raise if p1.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 0
raise if p2.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 1
```

How do you deal with letting users connect to both sides without breaking the way addToNode / PlantLoop::addSupplyBranchForComponent worked historically?

The only sensible thing I can think of is to let those untouched completely, but add a new `WaterHeater::addToSecondaryNode` method that would let you do that and that the user will need to manually call. That will require the user to potentially do some workarounds to first add a branch onto the supply side of the plant loop, with a bypass pipe for eg, get the inlet node of that pipe, WeatherHeaterMixed::addToSecondaryNode to it, then remove pipe.

@kbenne thoughts please?
@tijcolem @jmarrec bumping this enhancement request.  Heat recovery chiller functionality is the top ask from Ladybug Tools for openstudio-standards.
@mdahlhausen Do you know of an [idf test file](https://github.com/NREL/EnergyPlus/tree/develop/testfiles) with this configuration?
@joseph-robertson - The hospital prototype building models for 90.1-2019 should have it: https://www.energycodes.gov/prototype-building-models. Or, this [file](https://raw.githubusercontent.com/NREL/EnergyPlus/develop/testfiles/ASHRAE901_Hospital_STD2019_Denver.idf).
@jmarrec  I think this would affect the OS app.  Do you have any thoughts on this? 
Doesn't matter if it affects the OSApp or not, not the problem of the SDK (but yes, it's manageable on the App side).

I'm more worried about changing the way things are in terms of API. It might be confusing for users to have addToNode suddenly behave differently. Here is the way things work right now

```ruby
m = Model.new
wh = WaterHeaterMixed.new(m)
p1 = PlantLoop.new(m)
p2 = PlantLoop.new(m)

wh.addToNode(p1.supplyInletNode)
raise if wh.plantLoop.empty?
raise if !wh.secondaryPlantLoop.empty?
raise if wh.plantLoop.get != p1
raise if p1.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 1
raise if p2.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 0

# Connect to another node on the supply side? It disconnects from the first plant loop and reconnects to another
wh.addToNode(p2.supplyInletNode)
raise if wh.plantLoop.empty?
raise if !wh.secondaryPlantLoop.empty?
raise if wh.plantLoop.get != p2
raise if p1.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 0
raise if p2.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 1
```

How do you deal with letting users connect to both sides without breaking the way addToNode / PlantLoop::addSupplyBranchForComponent worked historically?

The only sensible thing I can think of is to let those untouched completely, but add a new `WaterHeater::addToSecondaryNode` method that would let you do that and that the user will need to manually call. That will require the user to potentially do some workarounds to first add a branch onto the supply side of the plant loop, with a bypass pipe for eg, get the inlet node of that pipe, WeatherHeaterMixed::addToSecondaryNode to it, then remove pipe.

@kbenne thoughts please?
@tijcolem @jmarrec bumping this enhancement request.  Heat recovery chiller functionality is the top ask from Ladybug Tools for openstudio-standards.
@mdahlhausen Do you know of an [idf test file](https://github.com/NREL/EnergyPlus/tree/develop/testfiles) with this configuration?
@joseph-robertson - The hospital prototype building models for 90.1-2019 should have it: https://www.energycodes.gov/prototype-building-models. Or, this [file](https://raw.githubusercontent.com/NREL/EnergyPlus/develop/testfiles/ASHRAE901_Hospital_STD2019_Denver.idf).

@jmarrec  I think this would affect the OS app.  Do you have any thoughts on this? 
Doesn't matter if it affects the OSApp or not, not the problem of the SDK (but yes, it's manageable on the App side).

I'm more worried about changing the way things are in terms of API. It might be confusing for users to have addToNode suddenly behave differently. Here is the way things work right now

```ruby
m = Model.new
wh = WaterHeaterMixed.new(m)
p1 = PlantLoop.new(m)
p2 = PlantLoop.new(m)

wh.addToNode(p1.supplyInletNode)
raise if wh.plantLoop.empty?
raise if !wh.secondaryPlantLoop.empty?
raise if wh.plantLoop.get != p1
raise if p1.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 1
raise if p2.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 0

# Connect to another node on the supply side? It disconnects from the first plant loop and reconnects to another
wh.addToNode(p2.supplyInletNode)
raise if wh.plantLoop.empty?
raise if !wh.secondaryPlantLoop.empty?
raise if wh.plantLoop.get != p2
raise if p1.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 0
raise if p2.supplyComponents("OS:WaterHeater:Mixed".to_IddObjectType).size() != 1
```

How do you deal with letting users connect to both sides without breaking the way addToNode / PlantLoop::addSupplyBranchForComponent worked historically?

The only sensible thing I can think of is to let those untouched completely, but add a new `WaterHeater::addToSecondaryNode` method that would let you do that and that the user will need to manually call. That will require the user to potentially do some workarounds to first add a branch onto the supply side of the plant loop, with a bypass pipe for eg, get the inlet node of that pipe, WeatherHeaterMixed::addToSecondaryNode to it, then remove pipe.

@kbenne thoughts please?
@tijcolem @jmarrec bumping this enhancement request.  Heat recovery chiller functionality is the top ask from Ladybug Tools for openstudio-standards.
@mdahlhausen Do you know of an [idf test file](https://github.com/NREL/EnergyPlus/tree/develop/testfiles) with this configuration?
@joseph-robertson - The hospital prototype building models for 90.1-2019 should have it: https://www.energycodes.gov/prototype-building-models. Or, this [file](https://raw.githubusercontent.com/NREL/EnergyPlus/develop/testfiles/ASHRAE901_Hospital_STD2019_Denver.idf).


Fix patch:
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp b/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp
index 531fb1ba1e6..9712c31f76d 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp
@@ -353,6 +353,26 @@ namespace energyplus {
           } else if (isSupplyBranch) {
             inletNode = waterToWaterComponent->supplyInletModelObject()->optionalCast<Node>();
             outletNode = waterToWaterComponent->supplyOutletModelObject()->optionalCast<Node>();
+
+            // WaterHeater:Mixed with Source Side nodes on supply branch
+            if (auto water_heater_mixed = modelObject.optionalCast<WaterHeaterMixed>()) {
+              if (boost::optional<PlantLoop> sourceSidePlantLoop = water_heater_mixed->sourceSidePlantLoop()) {
+                if (loop.handle() == sourceSidePlantLoop->handle()) {
+                  inletNode = waterToWaterComponent->demandInletModelObject()->optionalCast<Node>();
+                  outletNode = waterToWaterComponent->demandOutletModelObject()->optionalCast<Node>();
+                }
+              }
+            }
+
+            // WaterHeater:Stratified with Source Side nodes on supply branch
+            if (auto water_heater_stratified = modelObject.optionalCast<WaterHeaterStratified>()) {
+              if (boost::optional<PlantLoop> sourceSidePlantLoop = water_heater_stratified->sourceSidePlantLoop()) {
+                if (loop.handle() == sourceSidePlantLoop->handle()) {
+                  inletNode = waterToWaterComponent->demandInletModelObject()->optionalCast<Node>();
+                  outletNode = waterToWaterComponent->demandOutletModelObject()->optionalCast<Node>();
+                }
+              }
+            }
           } else {
             if (auto tertiaryInletModelObject = waterToWaterComponent->tertiaryInletModelObject()) {
               if (auto tertiaryInletNode = tertiaryInletModelObject->optionalCast<Node>()) {
diff --git a/src/energyplus/Test/WaterHeaterMixed_GTest.cpp b/src/energyplus/Test/WaterHeaterMixed_GTest.cpp
index 51a00c80c1a..4129e9b427a 100644
--- a/src/energyplus/Test/WaterHeaterMixed_GTest.cpp
+++ b/src/energyplus/Test/WaterHeaterMixed_GTest.cpp
@@ -40,14 +40,23 @@
 
 #include "../../model/PlantLoop.hpp"
 #include "../../model/PlantLoop_Impl.hpp"
+#include "../../model/PipeAdiabatic.hpp"
+#include "../../model/PipeAdiabatic_Impl.hpp"
+#include "../../model/Node.hpp"
+#include "../../model/Node_Impl.hpp"
 
 #include "../../utilities/idf/IdfFile.hpp"
 #include "../../utilities/idf/Workspace.hpp"
 #include "../../utilities/idf/IdfObject.hpp"
 #include "../../utilities/idf/WorkspaceObject.hpp"
 #include "../../utilities/idf/IdfExtensibleGroup.hpp"
+#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
 
 #include <utilities/idd/WaterHeater_Mixed_FieldEnums.hxx>
+#include <utilities/idd/PlantLoop_FieldEnums.hxx>
+#include <utilities/idd/PlantEquipmentList_FieldEnums.hxx>
+#include <utilities/idd/PlantEquipmentOperationSchemes_FieldEnums.hxx>
+#include <utilities/idd/PlantEquipmentOperation_HeatingLoad_FieldEnums.hxx>
 #include <utilities/idd/IddEnums.hxx>
 
 using namespace openstudio::energyplus;
@@ -110,3 +119,131 @@ TEST_F(EnergyPlusFixture, ForwardTranslatorWaterHeaterMixed_Condition) {
     EXPECT_EQ(1u, w.getObjectsByType(IddObjectType::WaterHeater_Mixed).size());
   }
 }
+
+TEST_F(EnergyPlusFixture, ForwardTranslatorWaterHeaterMixed_PlantLoopConnections) {
+  ForwardTranslator ft;
+  Model m;
+
+  WaterHeaterMixed wh(m);
+
+  PlantLoop useSidePlantLoop(m);
+
+  EXPECT_TRUE(useSidePlantLoop.addSupplyBranchForComponent(wh));
+  std::string useSideOutletNodeName;
+  {
+    Workspace w = ft.translateModel(m);
+
+    EXPECT_EQ(1, w.getObjectsByType(IddObjectType::PlantLoop).size());
+
+    std::vector<WorkspaceObject> idfWHMixeds(w.getObjectsByType(IddObjectType::WaterHeater_Mixed));
+    ASSERT_EQ(1, idfWHMixeds.size());
+    WorkspaceObject idfWHMixed(idfWHMixeds[0]);
+
+    EXPECT_FALSE(idfWHMixed.isEmpty(WaterHeater_MixedFields::UseSideInletNodeName));
+    ASSERT_FALSE(idfWHMixed.isEmpty(WaterHeater_MixedFields::UseSideOutletNodeName));
+    useSideOutletNodeName = idfWHMixed.getString(WaterHeater_MixedFields::UseSideOutletNodeName, false).get();
+    EXPECT_NE("", useSideOutletNodeName);
+
+    EXPECT_TRUE(idfWHMixed.isEmpty(WaterHeater_MixedFields::SourceSideInletNodeName));
+    EXPECT_TRUE(idfWHMixed.isEmpty(WaterHeater_MixedFields::SourceSideOutletNodeName));
+
+    EXPECT_EQ("", idfWHMixed.getString(WaterHeater_MixedFields::SourceSideInletNodeName, false).get());
+    EXPECT_EQ("", idfWHMixed.getString(WaterHeater_MixedFields::SourceSideOutletNodeName, false).get());
+
+    // Check PlantEquipmentOperationScheme for Use Side
+    {
+      auto _wo = w.getObjectByTypeAndName(IddObjectType::PlantLoop, useSidePlantLoop.nameString());
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_coolingPlant = _wo.get();
+      WorkspaceObject idf_plant_op = idf_coolingPlant.getTarget(PlantLoopFields::PlantEquipmentOperationSchemeName).get();
+
+      // Should have created a Heating Load one only
+      ASSERT_EQ(1, idf_plant_op.extensibleGroups().size());
+      auto w_eg = idf_plant_op.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
+      ASSERT_EQ("PlantEquipmentOperation:HeatingLoad", w_eg.getString(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeObjectType).get());
+
+      // Get the Operation Scheme
+      _wo = w_eg.getTarget(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeName);
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_op_scheme = _wo.get();
+
+      // Get the Plant Equipment List of this HeatingLoad scheme
+      // There should only be one Load Range
+      ASSERT_EQ(1, idf_op_scheme.extensibleGroups().size());
+      // Load range 1
+      w_eg = idf_op_scheme.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
+      _wo = w_eg.getTarget(PlantEquipmentOperation_HeatingLoadExtensibleFields::RangeEquipmentListName);
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_peq_list = _wo.get();
+
+      // Should have one equipment on it: WaterHeater
+      ASSERT_EQ(1, idf_peq_list.extensibleGroups().size());
+      IdfExtensibleGroup idf_eg(idf_peq_list.extensibleGroups()[0]);
+
+      ASSERT_EQ(wh.nameString(), idf_eg.getString(PlantEquipmentListExtensibleFields::EquipmentName).get());
+    }
+  }
+
+  // Now add connect it on a supply side Source plant
+  PlantLoop sourceSidePlantLoop(m);
+  EXPECT_TRUE(sourceSidePlantLoop.addSupplyBranchForComponent(wh));
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  ASSERT_TRUE(wh.demandInletModelObject());
+  ASSERT_TRUE(wh.demandOutletModelObject());
+
+  {
+    Workspace w = ft.translateModel(m);
+
+    EXPECT_EQ(2, w.getObjectsByType(IddObjectType::PlantLoop).size());
+
+    std::vector<WorkspaceObject> idfWHMixeds(w.getObjectsByType(IddObjectType::WaterHeater_Mixed));
+    ASSERT_EQ(1, idfWHMixeds.size());
+    WorkspaceObject idfWHMixed(idfWHMixeds[0]);
+
+    EXPECT_FALSE(idfWHMixed.isEmpty(WaterHeater_MixedFields::UseSideInletNodeName));
+    EXPECT_FALSE(idfWHMixed.isEmpty(WaterHeater_MixedFields::UseSideOutletNodeName));
+    EXPECT_FALSE(idfWHMixed.isEmpty(WaterHeater_MixedFields::SourceSideInletNodeName));
+    EXPECT_FALSE(idfWHMixed.isEmpty(WaterHeater_MixedFields::SourceSideOutletNodeName));
+
+    EXPECT_EQ(wh.supplyInletModelObject()->nameString(), idfWHMixed.getString(WaterHeater_MixedFields::UseSideInletNodeName, false).get());
+    EXPECT_EQ(wh.supplyOutletModelObject()->nameString(), idfWHMixed.getString(WaterHeater_MixedFields::UseSideOutletNodeName, false).get());
+    EXPECT_EQ(useSideOutletNodeName, idfWHMixed.getString(WaterHeater_MixedFields::UseSideOutletNodeName, false).get());  // doesn't change
+    EXPECT_EQ(wh.demandInletModelObject()->nameString(), idfWHMixed.getString(WaterHeater_MixedFields::SourceSideInletNodeName, false).get());
+    EXPECT_EQ(wh.demandOutletModelObject()->nameString(), idfWHMixed.getString(WaterHeater_MixedFields::SourceSideOutletNodeName, false).get());
+
+    // Check PlantEquipmentOperationScheme for Use Side AND Source side
+    for (const auto& plant : {useSidePlantLoop, sourceSidePlantLoop}) {
+      auto _wo = w.getObjectByTypeAndName(IddObjectType::PlantLoop, plant.nameString());
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_coolingPlant = _wo.get();
+      WorkspaceObject idf_plant_op = idf_coolingPlant.getTarget(PlantLoopFields::PlantEquipmentOperationSchemeName).get();
+
+      // Should have created a Heating Load one only
+      ASSERT_EQ(1, idf_plant_op.extensibleGroups().size());
+      auto w_eg = idf_plant_op.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
+      ASSERT_EQ("PlantEquipmentOperation:HeatingLoad", w_eg.getString(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeObjectType).get());
+
+      // Get the Operation Scheme
+      _wo = w_eg.getTarget(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeName);
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_op_scheme = _wo.get();
+
+      // Get the Plant Equipment List of this HeatingLoad scheme
+      // There should only be one Load Range
+      ASSERT_EQ(1, idf_op_scheme.extensibleGroups().size());
+      // Load range 1
+      w_eg = idf_op_scheme.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
+      _wo = w_eg.getTarget(PlantEquipmentOperation_HeatingLoadExtensibleFields::RangeEquipmentListName);
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_peq_list = _wo.get();
+
+      // Should have one equipment on it: WaterHeater
+      ASSERT_EQ(1, idf_peq_list.extensibleGroups().size());
+      IdfExtensibleGroup idf_eg(idf_peq_list.extensibleGroups()[0]);
+
+      ASSERT_EQ(wh.nameString(), idf_eg.getString(PlantEquipmentListExtensibleFields::EquipmentName).get());
+    }
+  }
+}
diff --git a/src/energyplus/Test/WaterHeaterStratified_GTest.cpp b/src/energyplus/Test/WaterHeaterStratified_GTest.cpp
index 71f7371f7dd..a66b51cec7f 100644
--- a/src/energyplus/Test/WaterHeaterStratified_GTest.cpp
+++ b/src/energyplus/Test/WaterHeaterStratified_GTest.cpp
@@ -40,14 +40,23 @@
 
 #include "../../model/PlantLoop.hpp"
 #include "../../model/PlantLoop_Impl.hpp"
+#include "../../model/PipeAdiabatic.hpp"
+#include "../../model/PipeAdiabatic_Impl.hpp"
+#include "../../model/Node.hpp"
+#include "../../model/Node_Impl.hpp"
 
 #include "../../utilities/idf/IdfFile.hpp"
 #include "../../utilities/idf/Workspace.hpp"
 #include "../../utilities/idf/IdfObject.hpp"
 #include "../../utilities/idf/WorkspaceObject.hpp"
 #include "../../utilities/idf/IdfExtensibleGroup.hpp"
+#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
 
 #include <utilities/idd/WaterHeater_Stratified_FieldEnums.hxx>
+#include <utilities/idd/PlantLoop_FieldEnums.hxx>
+#include <utilities/idd/PlantEquipmentList_FieldEnums.hxx>
+#include <utilities/idd/PlantEquipmentOperationSchemes_FieldEnums.hxx>
+#include <utilities/idd/PlantEquipmentOperation_HeatingLoad_FieldEnums.hxx>
 #include <utilities/idd/IddEnums.hxx>
 
 using namespace openstudio::energyplus;
@@ -110,3 +119,134 @@ TEST_F(EnergyPlusFixture, ForwardTranslatorWaterHeaterStratified_Condition) {
     EXPECT_EQ(1u, w.getObjectsByType(IddObjectType::WaterHeater_Stratified).size());
   }
 }
+
+TEST_F(EnergyPlusFixture, ForwardTranslatorWaterHeaterStratified_PlantLoopConnections) {
+  ForwardTranslator ft;
+  Model m;
+
+  WaterHeaterStratified wh(m);
+
+  PlantLoop useSidePlantLoop(m);
+
+  EXPECT_TRUE(useSidePlantLoop.addSupplyBranchForComponent(wh));
+  std::string useSideOutletNodeName;
+  {
+    Workspace w = ft.translateModel(m);
+
+    EXPECT_EQ(1, w.getObjectsByType(IddObjectType::PlantLoop).size());
+
+    std::vector<WorkspaceObject> idfWHStratifieds(w.getObjectsByType(IddObjectType::WaterHeater_Stratified));
+    ASSERT_EQ(1, idfWHStratifieds.size());
+    WorkspaceObject idfWHStratified(idfWHStratifieds[0]);
+
+    EXPECT_FALSE(idfWHStratified.isEmpty(WaterHeater_StratifiedFields::UseSideInletNodeName));
+    ASSERT_FALSE(idfWHStratified.isEmpty(WaterHeater_StratifiedFields::UseSideOutletNodeName));
+    useSideOutletNodeName = idfWHStratified.getString(WaterHeater_StratifiedFields::UseSideOutletNodeName, false).get();
+    EXPECT_NE("", useSideOutletNodeName);
+
+    EXPECT_TRUE(idfWHStratified.isEmpty(WaterHeater_StratifiedFields::SourceSideInletNodeName));
+    EXPECT_TRUE(idfWHStratified.isEmpty(WaterHeater_StratifiedFields::SourceSideOutletNodeName));
+
+    EXPECT_EQ("", idfWHStratified.getString(WaterHeater_StratifiedFields::SourceSideInletNodeName, false).get());
+    EXPECT_EQ("", idfWHStratified.getString(WaterHeater_StratifiedFields::SourceSideOutletNodeName, false).get());
+
+    // Check PlantEquipmentOperationScheme for Use Side
+    {
+      auto _wo = w.getObjectByTypeAndName(IddObjectType::PlantLoop, useSidePlantLoop.nameString());
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_coolingPlant = _wo.get();
+      WorkspaceObject idf_plant_op = idf_coolingPlant.getTarget(PlantLoopFields::PlantEquipmentOperationSchemeName).get();
+
+      // Should have created a Heating Load one only
+      ASSERT_EQ(1, idf_plant_op.extensibleGroups().size());
+      auto w_eg = idf_plant_op.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
+      ASSERT_EQ("PlantEquipmentOperation:HeatingLoad", w_eg.getString(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeObjectType).get());
+
+      // Get the Operation Scheme
+      _wo = w_eg.getTarget(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeName);
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_op_scheme = _wo.get();
+
+      // Get the Plant Equipment List of this HeatingLoad scheme
+      // There should only be one Load Range
+      ASSERT_EQ(1, idf_op_scheme.extensibleGroups().size());
+      // Load range 1
+      w_eg = idf_op_scheme.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
+      _wo = w_eg.getTarget(PlantEquipmentOperation_HeatingLoadExtensibleFields::RangeEquipmentListName);
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_peq_list = _wo.get();
+
+      // Should have one equipment on it: WaterHeater
+      ASSERT_EQ(1, idf_peq_list.extensibleGroups().size());
+      IdfExtensibleGroup idf_eg(idf_peq_list.extensibleGroups()[0]);
+
+      ASSERT_EQ(wh.nameString(), idf_eg.getString(PlantEquipmentListExtensibleFields::EquipmentName).get());
+    }
+  }
+
+  // Now add connect it on a supply side Source plant
+  PlantLoop sourceSidePlantLoop(m);
+  EXPECT_TRUE(sourceSidePlantLoop.addSupplyBranchForComponent(wh));
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  ASSERT_TRUE(wh.demandInletModelObject());
+  ASSERT_TRUE(wh.demandOutletModelObject());
+
+  {
+    Workspace w = ft.translateModel(m);
+
+    EXPECT_EQ(2, w.getObjectsByType(IddObjectType::PlantLoop).size());
+
+    std::vector<WorkspaceObject> idfWHStratifieds(w.getObjectsByType(IddObjectType::WaterHeater_Stratified));
+    ASSERT_EQ(1, idfWHStratifieds.size());
+    WorkspaceObject idfWHStratified(idfWHStratifieds[0]);
+
+    EXPECT_FALSE(idfWHStratified.isEmpty(WaterHeater_StratifiedFields::UseSideInletNodeName));
+    EXPECT_FALSE(idfWHStratified.isEmpty(WaterHeater_StratifiedFields::UseSideOutletNodeName));
+    EXPECT_FALSE(idfWHStratified.isEmpty(WaterHeater_StratifiedFields::SourceSideInletNodeName));
+    EXPECT_FALSE(idfWHStratified.isEmpty(WaterHeater_StratifiedFields::SourceSideOutletNodeName));
+
+    EXPECT_EQ(wh.supplyInletModelObject()->nameString(), idfWHStratified.getString(WaterHeater_StratifiedFields::UseSideInletNodeName, false).get());
+    EXPECT_EQ(wh.supplyOutletModelObject()->nameString(),
+              idfWHStratified.getString(WaterHeater_StratifiedFields::UseSideOutletNodeName, false).get());
+    EXPECT_EQ(useSideOutletNodeName, idfWHStratified.getString(WaterHeater_StratifiedFields::UseSideOutletNodeName, false).get());  // doesn't change
+    EXPECT_EQ(wh.demandInletModelObject()->nameString(),
+              idfWHStratified.getString(WaterHeater_StratifiedFields::SourceSideInletNodeName, false).get());
+    EXPECT_EQ(wh.demandOutletModelObject()->nameString(),
+              idfWHStratified.getString(WaterHeater_StratifiedFields::SourceSideOutletNodeName, false).get());
+
+    // Check PlantEquipmentOperationScheme for Use Side AND Source side
+    for (const auto& plant : {useSidePlantLoop, sourceSidePlantLoop}) {
+      auto _wo = w.getObjectByTypeAndName(IddObjectType::PlantLoop, plant.nameString());
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_coolingPlant = _wo.get();
+      WorkspaceObject idf_plant_op = idf_coolingPlant.getTarget(PlantLoopFields::PlantEquipmentOperationSchemeName).get();
+
+      // Should have created a Heating Load one only
+      ASSERT_EQ(1, idf_plant_op.extensibleGroups().size());
+      auto w_eg = idf_plant_op.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
+      ASSERT_EQ("PlantEquipmentOperation:HeatingLoad", w_eg.getString(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeObjectType).get());
+
+      // Get the Operation Scheme
+      _wo = w_eg.getTarget(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeName);
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_op_scheme = _wo.get();
+
+      // Get the Plant Equipment List of this HeatingLoad scheme
+      // There should only be one Load Range
+      ASSERT_EQ(1, idf_op_scheme.extensibleGroups().size());
+      // Load range 1
+      w_eg = idf_op_scheme.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
+      _wo = w_eg.getTarget(PlantEquipmentOperation_HeatingLoadExtensibleFields::RangeEquipmentListName);
+      ASSERT_TRUE(_wo.is_initialized());
+      WorkspaceObject idf_peq_list = _wo.get();
+
+      // Should have one equipment on it: WaterHeater
+      ASSERT_EQ(1, idf_peq_list.extensibleGroups().size());
+      IdfExtensibleGroup idf_eg(idf_peq_list.extensibleGroups()[0]);
+
+      ASSERT_EQ(wh.nameString(), idf_eg.getString(PlantEquipmentListExtensibleFields::EquipmentName).get());
+    }
+  }
+}
diff --git a/src/model/WaterHeaterMixed.cpp b/src/model/WaterHeaterMixed.cpp
index bcc1cb9ea69..39c3efa8d4b 100644
--- a/src/model/WaterHeaterMixed.cpp
+++ b/src/model/WaterHeaterMixed.cpp
@@ -45,6 +45,10 @@
 #include "ThermalZone_Impl.hpp"
 #include "WaterHeaterSizing.hpp"
 #include "WaterHeaterSizing_Impl.hpp"
+#include "PlantLoop.hpp"
+#include "PlantLoop_Impl.hpp"
+#include "Node.hpp"
+#include "Node_Impl.hpp"
 
 #include <utilities/idd/IddFactory.hxx>
 #include <utilities/idd/OS_WaterHeater_Mixed_FieldEnums.hxx>
@@ -1023,22 +1027,6 @@ namespace model {
       return true;
     }
 
-    unsigned WaterHeaterMixed_Impl::supplyInletPort() const {
-      return OS_WaterHeater_MixedFields::UseSideInletNodeName;
-    }
-
-    unsigned WaterHeaterMixed_Impl::supplyOutletPort() const {
-      return OS_WaterHeater_MixedFields::UseSideOutletNodeName;
-    }
-
-    unsigned WaterHeaterMixed_Impl::demandInletPort() const {
-      return OS_WaterHeater_MixedFields::SourceSideInletNodeName;
-    }
-
-    unsigned WaterHeaterMixed_Impl::demandOutletPort() const {
-      return OS_WaterHeater_MixedFields::SourceSideOutletNodeName;
-    }
-
     boost::optional<ZoneHVACComponent> WaterHeaterMixed_Impl::containingZoneHVACComponent() const {
       auto hpwhs = model().getConcreteModelObjects<model::WaterHeaterHeatPump>();
       auto t_Handle = handle();
@@ -1216,6 +1204,193 @@ namespace model {
       }
     }
 
+    // Loop connections
+
+    unsigned WaterHeaterMixed_Impl::supplyInletPort() const {
+      return OS_WaterHeater_MixedFields::UseSideInletNodeName;
+    }
+
+    unsigned WaterHeaterMixed_Impl::supplyOutletPort() const {
+      return OS_WaterHeater_MixedFields::UseSideOutletNodeName;
+    }
+
+    unsigned WaterHeaterMixed_Impl::demandInletPort() const {
+      return OS_WaterHeater_MixedFields::SourceSideInletNodeName;
+    }
+
+    unsigned WaterHeaterMixed_Impl::demandOutletPort() const {
+      return OS_WaterHeater_MixedFields::SourceSideOutletNodeName;
+    }
+
+    boost::optional<PlantLoop> WaterHeaterMixed_Impl::plantLoop() const {
+      if (m_plantLoop) {
+        return m_plantLoop;
+      } else {
+        // Note: checking for supply side only isn't sufficient for this object, as it can be on the supply of two plant loops
+        // Here we only want to return the Use Side one, so we need to filter out the source Side one
+        boost::optional<PlantLoop> sourceSidePlantLoop = this->sourceSidePlantLoop();
+
+        std::vector<PlantLoop> plantLoops = this->model().getConcreteModelObjects<PlantLoop>();
+
+        for (const auto& elem : plantLoops) {
+          OptionalPlantLoop plantLoop = elem.optionalCast<PlantLoop>();
+          if (plantLoop) {
+            // Check that the component is on the supply side of the PlantLoop
+            if (plantLoop->supplyComponent(this->handle())) {
+              // Skip the sourceSide one
+              if (sourceSidePlantLoop) {
+                if (plantLoop->handle() == sourceSidePlantLoop->handle()) {
+                  continue;
+                }
+              }
+              m_plantLoop = plantLoop;
+
+              return plantLoop;
+            }
+          }
+        }
+      }
+
+      return boost::none;
+    }
+
+    boost::optional<PlantLoop> WaterHeaterMixed_Impl::secondaryPlantLoop() const {
+      if (m_secondaryPlantLoop) {
+        return m_secondaryPlantLoop;
+      } else {
+
+        // Regular case, it's on the demand side: use the base class implementation
+        m_secondaryPlantLoop = WaterToWaterComponent_Impl::secondaryPlantLoop();
+        if (m_secondaryPlantLoop) {
+          return m_secondaryPlantLoop;
+        }
+
+        // Less common case: it's also on the supply side (like for the PlantLoop one)
+        // so we explicitly ensure this is connected to the **Source** Side Nodes
+        if (auto sourceSideOutletModelObject_ = sourceSideOutletModelObject()) {
+          if (auto sourceSideOutletHVACComponent_ = sourceSideOutletModelObject_->optionalCast<HVACComponent>()) {
+            auto plantLoops = this->model().getConcreteModelObjects<PlantLoop>();
+
+            for (const auto& plantLoop : plantLoops) {
+              if (!plantLoop.supplyComponents(plantLoop.supplyInletNode(), sourceSideOutletHVACComponent_.get()).empty()) {
+                m_secondaryPlantLoop = plantLoop;
+                return plantLoop;
+              }
+            }
+          }
+        }
+      }
+
+      return boost::none;
+    }
+
+    bool WaterHeaterMixed_Impl::removeFromSecondaryPlantLoop() {
+
+      if (auto plant_ = secondaryPlantLoop()) {
+        m_secondaryPlantLoop = boost::none;
+
+        auto outletNode = sourceSideOutletModelObject()->cast<HVACComponent>();
+
+        auto supplyNode = plant_->supplyInletNode();
+        if (!plant_->supplyComponents(supplyNode, outletNode).empty()) {
+          return HVACComponent_Impl::removeFromLoop(supplyNode, plant_->supplyOutletNode(), demandInletPort(), demandOutletPort());
+        } else {
+          return HVACComponent_Impl::removeFromLoop(plant_->demandInletNode(), plant_->demandOutletNode(), demandInletPort(), demandOutletPort());
+        }
+      }
+
+      return false;
+    }
+
+    bool WaterHeaterMixed_Impl::addToNode(Node& node) {
+      boost::optional<PlantLoop> t_plantLoop = node.plantLoop();
+
+      if (t_plantLoop) {
+        if (t_plantLoop->supplyComponent(node.handle())) {
+          // If these is already a use side Plant Loop
+          boost::optional<PlantLoop> useSidePlant = this->useSidePlantLoop();
+          if (useSidePlant) {
+            // And it's not the same as the node's loop
+            if (t_plantLoop.get() != useSidePlant.get()) {
+              // Then try to add it to the source side one
+              LOG(Warn, "Calling addToSecondaryNode to connect it to the source side loop for " << briefDescription());
+              return this->addToSourceSideNode(node);
+            }
+          }
+        }
+      }
+
+      // All other cases, call the base class implementation
+      return WaterToWaterComponent_Impl::addToNode(node);
+    }
+
+    bool WaterHeaterMixed_Impl::addToSourceSideNode(Node& node) {
+      auto thisModelObject = getObject<ModelObject>();
+      auto t_plantLoop = node.plantLoop();
+
+      boost::optional<unsigned> componentInletPort = demandInletPort();
+      boost::optional<unsigned> componentOutletPort = demandOutletPort();
+
+      boost::optional<HVACComponent> systemStartComponent;
+      boost::optional<HVACComponent> systemEndComponent;
+
+      if (node.getImpl<Node_Impl>()->isConnected(thisModelObject)) {
+        return false;
+      }
+
+      if (t_plantLoop) {
+        if (t_plantLoop->supplyComponent(node.handle())) {
+
+          systemStartComponent = t_plantLoop->supplyInletNode();
+          systemEndComponent = t_plantLoop->supplyOutletNode();
+
+          removeFromSourceSidePlantLoop();
+        } else if (t_plantLoop->demandComponent(node.handle())) {
+          systemStartComponent = t_plantLoop->demandInletNode();
+          systemEndComponent = t_plantLoop->demandOutletNode();
+
+          removeFromSourceSidePlantLoop();
+        }
+      }
+
+      if (systemStartComponent && systemEndComponent && componentOutletPort && componentInletPort) {
+        return HVACComponent_Impl::addToNode(node, systemStartComponent.get(), systemEndComponent.get(), componentInletPort.get(),
+                                             componentOutletPort.get());
+      }
+
+      return false;
+    }
+
+    // Name aliases
+
+    boost::optional<PlantLoop> WaterHeaterMixed_Impl::sourceSidePlantLoop() const {
+      return secondaryPlantLoop();
+    }
+
+    boost::optional<PlantLoop> WaterHeaterMixed_Impl::useSidePlantLoop() const {
+      return plantLoop();
+    }
+
+    bool WaterHeaterMixed_Impl::removeFromSourceSidePlantLoop() {
+      return removeFromSecondaryPlantLoop();
+    }
+
+    boost::optional<ModelObject> WaterHeaterMixed_Impl::useSideInletModelObject() const {
+      return supplyInletModelObject();
+    }
+
+    boost::optional<ModelObject> WaterHeaterMixed_Impl::useSideOutletModelObject() const {
+      return supplyOutletModelObject();
+    }
+
+    boost::optional<ModelObject> WaterHeaterMixed_Impl::sourceSideInletModelObject() const {
+      return demandInletModelObject();
+    }
+
+    boost::optional<ModelObject> WaterHeaterMixed_Impl::sourceSideOutletModelObject() const {
+      return demandOutletModelObject();
+    }
+
   }  // namespace detail
 
   WaterHeaterMixed::WaterHeaterMixed(const Model& model) : WaterToWaterComponent(WaterHeaterMixed::iddObjectType(), model) {
@@ -1830,5 +2005,39 @@ namespace model {
     return getImpl<detail::WaterHeaterMixed_Impl>()->waterHeaterSizing();
   }
 
+  // Helper
+  bool WaterHeaterMixed::addToSourceSideNode(Node& node) {
+    return getImpl<detail::WaterHeaterMixed_Impl>()->addToSourceSideNode(node);
+  }
+
+  // Name aliases
+  boost::optional<PlantLoop> WaterHeaterMixed::sourceSidePlantLoop() const {
+    return getImpl<detail::WaterHeaterMixed_Impl>()->sourceSidePlantLoop();
+  }
+
+  boost::optional<PlantLoop> WaterHeaterMixed::useSidePlantLoop() const {
+    return getImpl<detail::WaterHeaterMixed_Impl>()->useSidePlantLoop();
+  }
+
+  bool WaterHeaterMixed::removeFromSourceSidePlantLoop() {
+    return getImpl<detail::WaterHeaterMixed_Impl>()->removeFromSourceSidePlantLoop();
+  }
+
+  boost::optional<ModelObject> WaterHeaterMixed::useSideInletModelObject() const {
+    return getImpl<detail::WaterHeaterMixed_Impl>()->useSideInletModelObject();
+  }
+
+  boost::optional<ModelObject> WaterHeaterMixed::useSideOutletModelObject() const {
+    return getImpl<detail::WaterHeaterMixed_Impl>()->useSideOutletModelObject();
+  }
+
+  boost::optional<ModelObject> WaterHeaterMixed::sourceSideInletModelObject() const {
+    return getImpl<detail::WaterHeaterMixed_Impl>()->sourceSideInletModelObject();
+  }
+
+  boost::optional<ModelObject> WaterHeaterMixed::sourceSideOutletModelObject() const {
+    return getImpl<detail::WaterHeaterMixed_Impl>()->sourceSideOutletModelObject();
+  }
+
 }  // namespace model
 }  // namespace openstudio
diff --git a/src/model/WaterHeaterMixed.hpp b/src/model/WaterHeaterMixed.hpp
index 8dce260a523..45879d4ed3b 100644
--- a/src/model/WaterHeaterMixed.hpp
+++ b/src/model/WaterHeaterMixed.hpp
@@ -41,6 +41,8 @@ namespace model {
   class CurveCubic;
   class ThermalZone;
   class WaterHeaterSizing;
+  class Node;
+  class PlantLoop;
 
   namespace detail {
 
@@ -364,6 +366,29 @@ namespace model {
 
     WaterHeaterSizing waterHeaterSizing() const;
 
+    // A helper method to specifically connect the tank on its Source Side, regardless of whether it is on the demand or supply side (without
+    // disconnecting the Use Side, in case it's a supply node given)
+    bool addToSourceSideNode(Node& node);
+
+    // This is a more conveniently named method to return the PlantLoop connected to the Use Side Inlet/Outlet Nodes,
+    // which is necessarilly on the supply side. It does exactly the same as the overriden secondaryPlantLoop() method
+    boost::optional<PlantLoop> useSidePlantLoop() const;
+
+    // This is a more conveniently named helper to return the PlantLoop connected to the Source Side Inlet/Outlet Nodes,
+    // whether that is on the demand or supply side. It does exactly the same as the overriden plantLoop() method
+    boost::optional<PlantLoop> sourceSidePlantLoop() const;
+
+    // Convenience name for removeFromSecondaryPlantLoop()
+    bool removeFromSourceSidePlantLoop();
+
+    boost::optional<ModelObject> useSideInletModelObject() const;
+
+    boost::optional<ModelObject> useSideOutletModelObject() const;
+
+    boost::optional<ModelObject> sourceSideInletModelObject() const;
+
+    boost::optional<ModelObject> sourceSideOutletModelObject() const;
+
     //@}
    protected:
     /// @cond
diff --git a/src/model/WaterHeaterMixed_Impl.hpp b/src/model/WaterHeaterMixed_Impl.hpp
index 257c3ea9f70..4e17fe4149d 100644
--- a/src/model/WaterHeaterMixed_Impl.hpp
+++ b/src/model/WaterHeaterMixed_Impl.hpp
@@ -41,6 +41,8 @@ namespace model {
   class CurveCubic;
   class ThermalZone;
   class WaterHeaterSizing;
+  class Node;
+  class PlantLoop;
 
   namespace detail {
 
@@ -69,12 +71,12 @@ namespace model {
 
       virtual std::vector<ScheduleTypeKey> getScheduleTypeKeys(const Schedule& schedule) const override;
 
+      // Correspond to the Use Side Nodes
       virtual unsigned supplyInletPort() const override;
-
       virtual unsigned supplyOutletPort() const override;
 
+      // Correspond to the Source Side Nodes
       virtual unsigned demandInletPort() const override;
-
       virtual unsigned demandOutletPort() const override;
 
       virtual void autosize() override;
@@ -87,6 +89,16 @@ namespace model {
 
       virtual ModelObject clone(Model model) const override;
 
+      // We have to override to avoid returning the source side plant loop if it is also on the supply side of this one
+      virtual boost::optional<PlantLoop> plantLoop() const override;
+
+      // Override to return the PlantLoop connected to the Source Side Inlet/Outlet Nodes whether that is on the demand or supply side
+      virtual boost::optional<PlantLoop> secondaryPlantLoop() const override;
+
+      virtual bool removeFromSecondaryPlantLoop() override;
+
+      virtual bool addToNode(Node& node) override;
+
       //@}
       /** @name Getters */
       //@{
@@ -370,6 +382,17 @@ namespace model {
 
       WaterHeaterSizing waterHeaterSizing() const;
 
+      bool addToSourceSideNode(Node& node);
+
+      // Name aliases
+      boost::optional<ModelObject> useSideInletModelObject() const;
+      boost::optional<ModelObject> useSideOutletModelObject() const;
+      boost::optional<ModelObject> sourceSideInletModelObject() const;
+      boost::optional<ModelObject> sourceSideOutletModelObject() const;
+      boost::optional<PlantLoop> sourceSidePlantLoop() const;
+      boost::optional<PlantLoop> useSidePlantLoop() const;
+      bool removeFromSourceSidePlantLoop();
+
       //@}
      protected:
      private:
diff --git a/src/model/WaterHeaterStratified.cpp b/src/model/WaterHeaterStratified.cpp
index e03bf84db82..7174aced2c6 100644
--- a/src/model/WaterHeaterStratified.cpp
+++ b/src/model/WaterHeaterStratified.cpp
@@ -44,8 +44,13 @@
 #include "ScheduleRuleset.hpp"
 #include "ScheduleRuleset_Impl.hpp"
 #include "Model.hpp"
+#include "Model_Impl.hpp"
 #include "WaterHeaterSizing.hpp"
 #include "WaterHeaterSizing_Impl.hpp"
+#include "PlantLoop.hpp"
+#include "PlantLoop_Impl.hpp"
+#include "Node.hpp"
+#include "Node_Impl.hpp"
 
 #include <utilities/idd/IddFactory.hxx>
 #include <utilities/idd/OS_WaterHeater_Stratified_FieldEnums.hxx>
@@ -184,22 +189,6 @@ namespace model {
       return result;
     }
 
-    unsigned WaterHeaterStratified_Impl::supplyInletPort() const {
-      return OS_WaterHeater_StratifiedFields::UseSideInletNodeName;
-    }
-
-    unsigned WaterHeaterStratified_Impl::supplyOutletPort() const {
-      return OS_WaterHeater_StratifiedFields::UseSideOutletNodeName;
-    }
-
-    unsigned WaterHeaterStratified_Impl::demandInletPort() const {
-      return OS_WaterHeater_StratifiedFields::SourceSideInletNodeName;
-    }
-
-    unsigned WaterHeaterStratified_Impl::demandOutletPort() const {
-      return OS_WaterHeater_StratifiedFields::SourceSideOutletNodeName;
-    }
-
     std::string WaterHeaterStratified_Impl::endUseSubcategory() const {
       boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::EndUseSubcategory, true);
       OS_ASSERT(value);
@@ -1192,6 +1181,193 @@ namespace model {
       }
     }
 
+    // Loop connections
+
+    unsigned WaterHeaterStratified_Impl::supplyInletPort() const {
+      return OS_WaterHeater_StratifiedFields::UseSideInletNodeName;
+    }
+
+    unsigned WaterHeaterStratified_Impl::supplyOutletPort() const {
+      return OS_WaterHeater_StratifiedFields::UseSideOutletNodeName;
+    }
+
+    unsigned WaterHeaterStratified_Impl::demandInletPort() const {
+      return OS_WaterHeater_StratifiedFields::SourceSideInletNodeName;
+    }
+
+    unsigned WaterHeaterStratified_Impl::demandOutletPort() const {
+      return OS_WaterHeater_StratifiedFields::SourceSideOutletNodeName;
+    }
+
+    boost::optional<PlantLoop> WaterHeaterStratified_Impl::plantLoop() const {
+      if (m_plantLoop) {
+        return m_plantLoop;
+      } else {
+        // Note: checking for supply side only isn't sufficient for this object, as it can be on the supply of two plant loops
+        // Here we only want to return the Use Side one, so we need to filter out the source Side one
+        boost::optional<PlantLoop> sourceSidePlantLoop = this->sourceSidePlantLoop();
+
+        std::vector<PlantLoop> plantLoops = this->model().getConcreteModelObjects<PlantLoop>();
+
+        for (const auto& elem : plantLoops) {
+          OptionalPlantLoop plantLoop = elem.optionalCast<PlantLoop>();
+          if (plantLoop) {
+            // Check that the component is on the supply side of the PlantLoop
+            if (plantLoop->supplyComponent(this->handle())) {
+              // Skip the sourceSide one
+              if (sourceSidePlantLoop) {
+                if (plantLoop->handle() == sourceSidePlantLoop->handle()) {
+                  continue;
+                }
+              }
+              m_plantLoop = plantLoop;
+
+              return plantLoop;
+            }
+          }
+        }
+      }
+
+      return boost::none;
+    }
+
+    boost::optional<PlantLoop> WaterHeaterStratified_Impl::secondaryPlantLoop() const {
+      if (m_secondaryPlantLoop) {
+        return m_secondaryPlantLoop;
+      } else {
+
+        // Regular case, it's on the demand side: use the base class implementation
+        m_secondaryPlantLoop = WaterToWaterComponent_Impl::secondaryPlantLoop();
+        if (m_secondaryPlantLoop) {
+          return m_secondaryPlantLoop;
+        }
+
+        // Less common case: it's also on the supply side (like for the PlantLoop one)
+        // so we explicitly ensure this is connected to the **Source** Side Nodes
+        if (auto sourceSideOutletModelObject_ = sourceSideOutletModelObject()) {
+          if (auto sourceSideOutletHVACComponent_ = sourceSideOutletModelObject_->optionalCast<HVACComponent>()) {
+            auto plantLoops = this->model().getConcreteModelObjects<PlantLoop>();
+
+            for (const auto& plantLoop : plantLoops) {
+              if (!plantLoop.supplyComponents(plantLoop.supplyInletNode(), sourceSideOutletHVACComponent_.get()).empty()) {
+                m_secondaryPlantLoop = plantLoop;
+                return plantLoop;
+              }
+            }
+          }
+        }
+      }
+
+      return boost::none;
+    }
+
+    bool WaterHeaterStratified_Impl::removeFromSecondaryPlantLoop() {
+
+      if (auto plant_ = secondaryPlantLoop()) {
+        m_secondaryPlantLoop = boost::none;
+
+        auto outletNode = sourceSideOutletModelObject()->cast<HVACComponent>();
+
+        auto supplyNode = plant_->supplyInletNode();
+        if (!plant_->supplyComponents(supplyNode, outletNode).empty()) {
+          return HVACComponent_Impl::removeFromLoop(supplyNode, plant_->supplyOutletNode(), demandInletPort(), demandOutletPort());
+        } else {
+          return HVACComponent_Impl::removeFromLoop(plant_->demandInletNode(), plant_->demandOutletNode(), demandInletPort(), demandOutletPort());
+        }
+      }
+
+      return false;
+    }
+
+    bool WaterHeaterStratified_Impl::addToNode(Node& node) {
+      boost::optional<PlantLoop> t_plantLoop = node.plantLoop();
+
+      if (t_plantLoop) {
+        if (t_plantLoop->supplyComponent(node.handle())) {
+          // If these is already a use side Plant Loop
+          boost::optional<PlantLoop> useSidePlant = this->useSidePlantLoop();
+          if (useSidePlant) {
+            // And it's not the same as the node's loop
+            if (t_plantLoop.get() != useSidePlant.get()) {
+              // Then try to add it to the source side one
+              LOG(Warn, "Calling addToSecondaryNode to connect it to the source side loop for " << briefDescription());
+              return this->addToSourceSideNode(node);
+            }
+          }
+        }
+      }
+
+      // All other cases, call the base class implementation
+      return WaterToWaterComponent_Impl::addToNode(node);
+    }
+
+    bool WaterHeaterStratified_Impl::addToSourceSideNode(Node& node) {
+      auto thisModelObject = getObject<ModelObject>();
+      auto t_plantLoop = node.plantLoop();
+
+      boost::optional<unsigned> componentInletPort = demandInletPort();
+      boost::optional<unsigned> componentOutletPort = demandOutletPort();
+
+      boost::optional<HVACComponent> systemStartComponent;
+      boost::optional<HVACComponent> systemEndComponent;
+
+      if (node.getImpl<Node_Impl>()->isConnected(thisModelObject)) {
+        return false;
+      }
+
+      if (t_plantLoop) {
+        if (t_plantLoop->supplyComponent(node.handle())) {
+
+          systemStartComponent = t_plantLoop->supplyInletNode();
+          systemEndComponent = t_plantLoop->supplyOutletNode();
+
+          removeFromSourceSidePlantLoop();
+        } else if (t_plantLoop->demandComponent(node.handle())) {
+          systemStartComponent = t_plantLoop->demandInletNode();
+          systemEndComponent = t_plantLoop->demandOutletNode();
+
+          removeFromSourceSidePlantLoop();
+        }
+      }
+
+      if (systemStartComponent && systemEndComponent && componentOutletPort && componentInletPort) {
+        return HVACComponent_Impl::addToNode(node, systemStartComponent.get(), systemEndComponent.get(), componentInletPort.get(),
+                                             componentOutletPort.get());
+      }
+
+      return false;
+    }
+
+    // Name aliases
+
+    boost::optional<PlantLoop> WaterHeaterStratified_Impl::sourceSidePlantLoop() const {
+      return secondaryPlantLoop();
+    }
+
+    boost::optional<PlantLoop> WaterHeaterStratified_Impl::useSidePlantLoop() const {
+      return plantLoop();
+    }
+
+    bool WaterHeaterStratified_Impl::removeFromSourceSidePlantLoop() {
+      return removeFromSecondaryPlantLoop();
+    }
+
+    boost::optional<ModelObject> WaterHeaterStratified_Impl::useSideInletModelObject() const {
+      return supplyInletModelObject();
+    }
+
+    boost::optional<ModelObject> WaterHeaterStratified_Impl::useSideOutletModelObject() const {
+      return supplyOutletModelObject();
+    }
+
+    boost::optional<ModelObject> WaterHeaterStratified_Impl::sourceSideInletModelObject() const {
+      return demandInletModelObject();
+    }
+
+    boost::optional<ModelObject> WaterHeaterStratified_Impl::sourceSideOutletModelObject() const {
+      return demandOutletModelObject();
+    }
+
   }  // namespace detail
 
   WaterHeaterStratified::WaterHeaterStratified(const Model& model) : WaterToWaterComponent(WaterHeaterStratified::iddObjectType(), model) {
@@ -1936,5 +2112,39 @@ namespace model {
     return getImpl<detail::WaterHeaterStratified_Impl>()->waterHeaterSizing();
   }
 
+  // Helper
+  bool WaterHeaterStratified::addToSourceSideNode(Node& node) {
+    return getImpl<detail::WaterHeaterStratified_Impl>()->addToSourceSideNode(node);
+  }
+
+  // Name aliases
+  boost::optional<PlantLoop> WaterHeaterStratified::sourceSidePlantLoop() const {
+    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSidePlantLoop();
+  }
+
+  boost::optional<PlantLoop> WaterHeaterStratified::useSidePlantLoop() const {
+    return getImpl<detail::WaterHeaterStratified_Impl>()->useSidePlantLoop();
+  }
+
+  bool WaterHeaterStratified::removeFromSourceSidePlantLoop() {
+    return getImpl<detail::WaterHeaterStratified_Impl>()->removeFromSourceSidePlantLoop();
+  }
+
+  boost::optional<ModelObject> WaterHeaterStratified::useSideInletModelObject() const {
+    return getImpl<detail::WaterHeaterStratified_Impl>()->useSideInletModelObject();
+  }
+
+  boost::optional<ModelObject> WaterHeaterStratified::useSideOutletModelObject() const {
+    return getImpl<detail::WaterHeaterStratified_Impl>()->useSideOutletModelObject();
+  }
+
+  boost::optional<ModelObject> WaterHeaterStratified::sourceSideInletModelObject() const {
+    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideInletModelObject();
+  }
+
+  boost::optional<ModelObject> WaterHeaterStratified::sourceSideOutletModelObject() const {
+    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideOutletModelObject();
+  }
+
 }  // namespace model
 }  // namespace openstudio
diff --git a/src/model/WaterHeaterStratified.hpp b/src/model/WaterHeaterStratified.hpp
index d4f56821078..6de8f51525e 100644
--- a/src/model/WaterHeaterStratified.hpp
+++ b/src/model/WaterHeaterStratified.hpp
@@ -40,6 +40,8 @@ namespace model {
   class Schedule;
   class ThermalZone;
   class WaterHeaterSizing;
+  class Node;
+  class PlantLoop;
 
   namespace detail {
 
@@ -397,6 +399,29 @@ namespace model {
 
     WaterHeaterSizing waterHeaterSizing() const;
 
+    // A helper method to specifically connect the tank on its Source Side, regardless of whether it is on the demand or supply side (without
+    // disconnecting the Use Side, in case it's a supply node given)
+    bool addToSourceSideNode(Node& node);
+
+    // This is a more conveniently named method to return the PlantLoop connected to the Use Side Inlet/Outlet Nodes,
+    // which is necessarilly on the supply side. It does exactly the same as the overriden secondaryPlantLoop() method
+    boost::optional<PlantLoop> useSidePlantLoop() const;
+
+    // This is a more conveniently named helper to return the PlantLoop connected to the Source Side Inlet/Outlet Nodes,
+    // whether that is on the demand or supply side. It does exactly the same as the overriden plantLoop() method
+    boost::optional<PlantLoop> sourceSidePlantLoop() const;
+
+    // Convenience name for removeFromSecondaryPlantLoop()
+    bool removeFromSourceSidePlantLoop();
+
+    boost::optional<ModelObject> useSideInletModelObject() const;
+
+    boost::optional<ModelObject> useSideOutletModelObject() const;
+
+    boost::optional<ModelObject> sourceSideInletModelObject() const;
+
+    boost::optional<ModelObject> sourceSideOutletModelObject() const;
+
     //@}
    protected:
     /// @cond
diff --git a/src/model/WaterHeaterStratified_Impl.hpp b/src/model/WaterHeaterStratified_Impl.hpp
index 91dde0737ac..94e6d3716d2 100644
--- a/src/model/WaterHeaterStratified_Impl.hpp
+++ b/src/model/WaterHeaterStratified_Impl.hpp
@@ -39,6 +39,8 @@ namespace model {
   class Schedule;
   class ThermalZone;
   class WaterHeaterSizing;
+  class Node;
+  class PlantLoop;
 
   namespace detail {
 
@@ -67,18 +69,34 @@ namespace model {
 
       virtual std::vector<ScheduleTypeKey> getScheduleTypeKeys(const Schedule& schedule) const override;
 
+      // Correspond to the Use Side Nodes
       virtual unsigned supplyInletPort() const override;
-
       virtual unsigned supplyOutletPort() const override;
 
+      // Correspond to the Source Side Nodes
       virtual unsigned demandInletPort() const override;
-
       virtual unsigned demandOutletPort() const override;
 
+      virtual void autosize() override;
+
+      virtual void applySizingValues() override;
+
+      boost::optional<ZoneHVACComponent> containingZoneHVACComponent() const override;
+
       virtual std::vector<ModelObject> children() const override;
 
       virtual ModelObject clone(Model model) const override;
 
+      // We have to override to avoid returning the source side plant loop if it is also on the supply side of this one
+      virtual boost::optional<PlantLoop> plantLoop() const override;
+
+      // Override to return the PlantLoop connected to the Source Side Inlet/Outlet Nodes whether that is on the demand or supply side
+      virtual boost::optional<PlantLoop> secondaryPlantLoop() const override;
+
+      virtual bool removeFromSecondaryPlantLoop() override;
+
+      virtual bool addToNode(Node& node) override;
+
       //@}
       /** @name Getters */
       //@{
@@ -231,10 +249,6 @@ namespace model {
 
       boost::optional<double> autosizedSourceSideDesignFlowRate() const;
 
-      virtual void autosize() override;
-
-      virtual void applySizingValues() override;
-
       //@}
       /** @name Setters */
       //@{
@@ -402,10 +416,19 @@ namespace model {
       /** @name Other */
       //@{
 
-      boost::optional<ZoneHVACComponent> containingZoneHVACComponent() const override;
-
       WaterHeaterSizing waterHeaterSizing() const;
 
+      bool addToSourceSideNode(Node& node);
+
+      // Name aliases
+      boost::optional<ModelObject> useSideInletModelObject() const;
+      boost::optional<ModelObject> useSideOutletModelObject() const;
+      boost::optional<ModelObject> sourceSideInletModelObject() const;
+      boost::optional<ModelObject> sourceSideOutletModelObject() const;
+      boost::optional<PlantLoop> sourceSidePlantLoop() const;
+      boost::optional<PlantLoop> useSidePlantLoop() const;
+      bool removeFromSourceSidePlantLoop();
+
       //@}
      protected:
      private:
diff --git a/src/model/WaterToWaterComponent_Impl.hpp b/src/model/WaterToWaterComponent_Impl.hpp
index 9a2c3267f1a..a643d93e846 100644
--- a/src/model/WaterToWaterComponent_Impl.hpp
+++ b/src/model/WaterToWaterComponent_Impl.hpp
@@ -95,7 +95,7 @@ namespace model {
   * This method checks for presence of the WaterToWaterComponent on the demand side of plantLoops
   * and checks that it isn't the tertiary plant loop
   */
-      boost::optional<PlantLoop> secondaryPlantLoop() const;
+      virtual boost::optional<PlantLoop> secondaryPlantLoop() const;
 
       bool removeFromPlantLoop();
 


Test patch:
diff --git a/src/model/test/WaterHeaterMixed_GTest.cpp b/src/model/test/WaterHeaterMixed_GTest.cpp
index cbbdd5a9997..5c1b6eaee3e 100644
--- a/src/model/test/WaterHeaterMixed_GTest.cpp
+++ b/src/model/test/WaterHeaterMixed_GTest.cpp
@@ -33,6 +33,12 @@
 
 #include "../WaterHeaterMixed.hpp"
 #include "../WaterHeaterMixed_Impl.hpp"
+#include "../PlantLoop.hpp"
+#include "../PlantLoop_Impl.hpp"
+#include "../PipeAdiabatic.hpp"
+#include "../PipeAdiabatic_Impl.hpp"
+#include "../Node.hpp"
+#include "../Node_Impl.hpp"
 
 #include "../ScheduleConstant.hpp"
 
@@ -87,3 +93,192 @@ TEST_F(ModelFixture, WaterHeaterMixed_NewFields) {
   EXPECT_TRUE(wh.setEndUseSubcategory("SomethingElse"));
   EXPECT_EQ("SomethingElse", wh.endUseSubcategory());
 }
+
+TEST_F(ModelFixture, WaterHeaterMixed_PlantLoopConnections) {
+  Model m;
+
+  WaterHeaterMixed wh(m);
+
+  PlantLoop p1(m);
+  PlantLoop p2(m);
+
+  // plant loop #1
+  auto node1 = p1.supplyInletNode();
+  EXPECT_TRUE(wh.addToNode(node1));
+
+  EXPECT_EQ(1u, p1.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+  EXPECT_EQ(0u, p2.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  EXPECT_FALSE(wh.demandInletModelObject());
+  EXPECT_FALSE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(p1.supplyInletNode().nameString(), wh.supplyInletModelObject()->cast<Node>().nameString());
+  std::string useSideOutletNodeName = wh.supplyOutletModelObject()->cast<Node>().nameString();
+  EXPECT_NE("", useSideOutletNodeName);
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+
+  // plant loop #2
+  PipeAdiabatic bypass_pipe(m);
+  p2.addSupplyBranchForComponent(bypass_pipe);
+  ASSERT_TRUE(bypass_pipe.inletModelObject());
+  ASSERT_TRUE(bypass_pipe.inletModelObject()->optionalCast<Node>());
+  auto node2 = bypass_pipe.inletModelObject()->cast<Node>();
+  EXPECT_TRUE(wh.addToSourceSideNode(node2));
+  bypass_pipe.remove();
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.sourceSidePlantLoop());
+  EXPECT_NE(wh.plantLoop()->handle(), wh.sourceSidePlantLoop()->handle());
+  EXPECT_EQ(wh.sourceSidePlantLoop()->handle(), p2.handle());
+
+  EXPECT_EQ(1u, p1.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+  EXPECT_EQ(1u, p2.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  ASSERT_TRUE(wh.demandInletModelObject());
+  ASSERT_TRUE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(p1.supplyInletNode().nameString(), wh.supplyInletModelObject()->cast<Node>().nameString());  // doesn't change
+  EXPECT_EQ(useSideOutletNodeName, wh.supplyOutletModelObject()->cast<Node>().nameString());             // doesn't change
+  EXPECT_NE(p2.supplyInletNode().nameString(), wh.demandInletModelObject()->cast<Node>().nameString());
+  EXPECT_NE("", wh.demandOutletModelObject()->cast<Node>().nameString());
+
+  EXPECT_TRUE(wh.removeFromSourceSidePlantLoop());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+}
+
+TEST_F(ModelFixture, WaterHeaterMixed_PlantLoopConnections_addToNodeOverride) {
+  Model m;
+
+  WaterHeaterMixed wh(m);
+
+  PlantLoop p1(m);
+  PlantLoop p2(m);
+
+  // plant loop #1
+  auto node1 = p1.supplyInletNode();
+  EXPECT_TRUE(wh.addToNode(node1));
+
+  EXPECT_EQ(1u, p1.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+  EXPECT_EQ(0u, p2.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  EXPECT_FALSE(wh.demandInletModelObject());
+  EXPECT_FALSE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(p1.supplyInletNode().nameString(), wh.supplyInletModelObject()->cast<Node>().nameString());
+  std::string useSideOutletNodeName = wh.supplyOutletModelObject()->cast<Node>().nameString();
+  EXPECT_NE("", useSideOutletNodeName);
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+
+  // plant loop #2
+  PipeAdiabatic bypass_pipe(m);
+  p2.addSupplyBranchForComponent(bypass_pipe);
+  ASSERT_TRUE(bypass_pipe.inletModelObject());
+  ASSERT_TRUE(bypass_pipe.inletModelObject()->optionalCast<Node>());
+  auto node2 = bypass_pipe.inletModelObject()->cast<Node>();
+  EXPECT_TRUE(wh.addToNode(node2));  // try with addToNode instead
+  bypass_pipe.remove();
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.sourceSidePlantLoop());
+  EXPECT_NE(wh.plantLoop()->handle(), wh.sourceSidePlantLoop()->handle());
+  EXPECT_EQ(wh.sourceSidePlantLoop()->handle(), p2.handle());
+
+  EXPECT_EQ(1u, p1.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+  EXPECT_EQ(1u, p2.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  ASSERT_TRUE(wh.demandInletModelObject());
+  ASSERT_TRUE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(p1.supplyInletNode().nameString(), wh.supplyInletModelObject()->cast<Node>().nameString());  // doesn't change
+  EXPECT_EQ(useSideOutletNodeName, wh.supplyOutletModelObject()->cast<Node>().nameString());             // doesn't change
+  EXPECT_NE(p2.supplyInletNode().nameString(), wh.demandInletModelObject()->cast<Node>().nameString());
+  EXPECT_NE("", wh.demandOutletModelObject()->cast<Node>().nameString());
+
+  EXPECT_TRUE(wh.removeFromSourceSidePlantLoop());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+}
+
+TEST_F(ModelFixture, WaterHeaterMixed_PlantLoopConnections_addToNodeOverride_addSupplyBranch) {
+
+  // Since addSupplyBranchForComponent calls addToNode, we should be able to call it twice in a row with two supply side nodes,
+  // and connect it to the Use Side PlantLoop first and Source Side PlantLoop second
+  Model m;
+
+  WaterHeaterMixed wh(m);
+
+  PlantLoop p1(m);
+  PlantLoop p2(m);
+
+  // plant loop #1
+  EXPECT_TRUE(p1.addSupplyBranchForComponent(wh));
+
+  EXPECT_EQ(1, p1.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+  EXPECT_EQ(0, p2.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  EXPECT_FALSE(wh.demandInletModelObject());
+  EXPECT_FALSE(wh.demandOutletModelObject());
+
+  std::string useSideOutletNodeName = wh.supplyOutletModelObject()->cast<Node>().nameString();
+  EXPECT_NE("", useSideOutletNodeName);
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+
+  // plant loop #2
+  EXPECT_TRUE(p2.addSupplyBranchForComponent(wh));
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+
+  ASSERT_TRUE(wh.secondaryPlantLoop());
+  EXPECT_NE(wh.plantLoop()->handle(), wh.secondaryPlantLoop()->handle());
+  EXPECT_EQ(wh.secondaryPlantLoop()->handle(), p2.handle());
+  ASSERT_TRUE(wh.sourceSidePlantLoop());
+  EXPECT_NE(wh.plantLoop()->handle(), wh.sourceSidePlantLoop()->handle());
+  EXPECT_EQ(wh.sourceSidePlantLoop()->handle(), p2.handle());
+
+  EXPECT_EQ(1, p1.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+  EXPECT_EQ(1, p2.supplyComponents(WaterHeaterMixed::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  ASSERT_TRUE(wh.demandInletModelObject());
+  ASSERT_TRUE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(useSideOutletNodeName, wh.supplyOutletModelObject()->cast<Node>().nameString());  // doesn't change
+  EXPECT_NE("", wh.demandOutletModelObject()->cast<Node>().nameString());
+
+  EXPECT_TRUE(wh.removeFromSourceSidePlantLoop());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+}
diff --git a/src/model/test/WaterHeaterStratified_GTest.cpp b/src/model/test/WaterHeaterStratified_GTest.cpp
index 66691e39720..bf88b81591e 100644
--- a/src/model/test/WaterHeaterStratified_GTest.cpp
+++ b/src/model/test/WaterHeaterStratified_GTest.cpp
@@ -35,6 +35,11 @@
 #include "../WaterUseEquipment.hpp"
 #include "../WaterUseEquipmentDefinition.hpp"
 #include "../PlantLoop.hpp"
+#include "../PlantLoop_Impl.hpp"
+#include "../PipeAdiabatic.hpp"
+#include "../PipeAdiabatic_Impl.hpp"
+#include "../Node.hpp"
+#include "../Node_Impl.hpp"
 
 using namespace openstudio;
 using namespace openstudio::model;
@@ -77,3 +82,192 @@ TEST_F(ModelFixture, WaterHeaterStratified_addToPlantLoop) {
   ASSERT_EQ(5u, plantLoop.supplyComponents().size());
   ASSERT_EQ(7u, plantLoop.demandComponents().size());
 }
+
+TEST_F(ModelFixture, WaterHeaterStratified_PlantLoopConnections) {
+  Model m;
+
+  WaterHeaterStratified wh(m);
+
+  PlantLoop p1(m);
+  PlantLoop p2(m);
+
+  // plant loop #1
+  auto node1 = p1.supplyInletNode();
+  EXPECT_TRUE(wh.addToNode(node1));
+
+  EXPECT_EQ(1u, p1.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+  EXPECT_EQ(0u, p2.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  EXPECT_FALSE(wh.demandInletModelObject());
+  EXPECT_FALSE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(p1.supplyInletNode().nameString(), wh.supplyInletModelObject()->cast<Node>().nameString());
+  std::string useSideOutletNodeName = wh.supplyOutletModelObject()->cast<Node>().nameString();
+  EXPECT_NE("", useSideOutletNodeName);
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+
+  // plant loop #2
+  PipeAdiabatic bypass_pipe(m);
+  p2.addSupplyBranchForComponent(bypass_pipe);
+  ASSERT_TRUE(bypass_pipe.inletModelObject());
+  ASSERT_TRUE(bypass_pipe.inletModelObject()->optionalCast<Node>());
+  auto node2 = bypass_pipe.inletModelObject()->cast<Node>();
+  EXPECT_TRUE(wh.addToSourceSideNode(node2));
+  bypass_pipe.remove();
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.sourceSidePlantLoop());
+  EXPECT_NE(wh.plantLoop()->handle(), wh.sourceSidePlantLoop()->handle());
+  EXPECT_EQ(wh.sourceSidePlantLoop()->handle(), p2.handle());
+
+  EXPECT_EQ(1u, p1.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+  EXPECT_EQ(1u, p2.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  ASSERT_TRUE(wh.demandInletModelObject());
+  ASSERT_TRUE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(p1.supplyInletNode().nameString(), wh.supplyInletModelObject()->cast<Node>().nameString());  // doesn't change
+  EXPECT_EQ(useSideOutletNodeName, wh.supplyOutletModelObject()->cast<Node>().nameString());             // doesn't change
+  EXPECT_NE(p2.supplyInletNode().nameString(), wh.demandInletModelObject()->cast<Node>().nameString());
+  EXPECT_NE("", wh.demandOutletModelObject()->cast<Node>().nameString());
+
+  EXPECT_TRUE(wh.removeFromSourceSidePlantLoop());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+}
+
+TEST_F(ModelFixture, WaterHeaterStratified_PlantLoopConnections_addToNodeOverride) {
+  Model m;
+
+  WaterHeaterStratified wh(m);
+
+  PlantLoop p1(m);
+  PlantLoop p2(m);
+
+  // plant loop #1
+  auto node1 = p1.supplyInletNode();
+  EXPECT_TRUE(wh.addToNode(node1));
+
+  EXPECT_EQ(1u, p1.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+  EXPECT_EQ(0u, p2.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  EXPECT_FALSE(wh.demandInletModelObject());
+  EXPECT_FALSE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(p1.supplyInletNode().nameString(), wh.supplyInletModelObject()->cast<Node>().nameString());
+  std::string useSideOutletNodeName = wh.supplyOutletModelObject()->cast<Node>().nameString();
+  EXPECT_NE("", useSideOutletNodeName);
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+
+  // plant loop #2
+  PipeAdiabatic bypass_pipe(m);
+  p2.addSupplyBranchForComponent(bypass_pipe);
+  ASSERT_TRUE(bypass_pipe.inletModelObject());
+  ASSERT_TRUE(bypass_pipe.inletModelObject()->optionalCast<Node>());
+  auto node2 = bypass_pipe.inletModelObject()->cast<Node>();
+  EXPECT_TRUE(wh.addToNode(node2));  // try with addToNode instead
+  bypass_pipe.remove();
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.sourceSidePlantLoop());
+  EXPECT_NE(wh.plantLoop()->handle(), wh.sourceSidePlantLoop()->handle());
+  EXPECT_EQ(wh.sourceSidePlantLoop()->handle(), p2.handle());
+
+  EXPECT_EQ(1u, p1.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+  EXPECT_EQ(1u, p2.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  ASSERT_TRUE(wh.demandInletModelObject());
+  ASSERT_TRUE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(p1.supplyInletNode().nameString(), wh.supplyInletModelObject()->cast<Node>().nameString());  // doesn't change
+  EXPECT_EQ(useSideOutletNodeName, wh.supplyOutletModelObject()->cast<Node>().nameString());             // doesn't change
+  EXPECT_NE(p2.supplyInletNode().nameString(), wh.demandInletModelObject()->cast<Node>().nameString());
+  EXPECT_NE("", wh.demandOutletModelObject()->cast<Node>().nameString());
+
+  EXPECT_TRUE(wh.removeFromSourceSidePlantLoop());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+}
+
+TEST_F(ModelFixture, WaterHeaterStratified_PlantLoopConnections_addToNodeOverride_addSupplyBranch) {
+
+  // Since addSupplyBranchForComponent calls addToNode, we should be able to call it twice in a row with two supply side nodes,
+  // and connect it to the Use Side PlantLoop first and Source Side PlantLoop second
+  Model m;
+
+  WaterHeaterStratified wh(m);
+
+  PlantLoop p1(m);
+  PlantLoop p2(m);
+
+  // plant loop #1
+  EXPECT_TRUE(p1.addSupplyBranchForComponent(wh));
+
+  EXPECT_EQ(1, p1.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+  EXPECT_EQ(0, p2.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  EXPECT_FALSE(wh.demandInletModelObject());
+  EXPECT_FALSE(wh.demandOutletModelObject());
+
+  std::string useSideOutletNodeName = wh.supplyOutletModelObject()->cast<Node>().nameString();
+  EXPECT_NE("", useSideOutletNodeName);
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+
+  // plant loop #2
+  EXPECT_TRUE(p2.addSupplyBranchForComponent(wh));
+
+  ASSERT_TRUE(wh.plantLoop());
+  EXPECT_EQ(wh.plantLoop()->handle(), p1.handle());
+  ASSERT_TRUE(wh.useSidePlantLoop());
+  EXPECT_EQ(wh.useSidePlantLoop()->handle(), p1.handle());
+
+  ASSERT_TRUE(wh.secondaryPlantLoop());
+  EXPECT_NE(wh.plantLoop()->handle(), wh.secondaryPlantLoop()->handle());
+  EXPECT_EQ(wh.secondaryPlantLoop()->handle(), p2.handle());
+  ASSERT_TRUE(wh.sourceSidePlantLoop());
+  EXPECT_NE(wh.plantLoop()->handle(), wh.sourceSidePlantLoop()->handle());
+  EXPECT_EQ(wh.sourceSidePlantLoop()->handle(), p2.handle());
+
+  EXPECT_EQ(1, p1.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+  EXPECT_EQ(1, p2.supplyComponents(WaterHeaterStratified::iddObjectType()).size());
+
+  ASSERT_TRUE(wh.supplyInletModelObject());
+  ASSERT_TRUE(wh.supplyOutletModelObject());
+  ASSERT_TRUE(wh.demandInletModelObject());
+  ASSERT_TRUE(wh.demandOutletModelObject());
+
+  EXPECT_EQ(useSideOutletNodeName, wh.supplyOutletModelObject()->cast<Node>().nameString());  // doesn't change
+  EXPECT_NE("", wh.demandOutletModelObject()->cast<Node>().nameString());
+
+  EXPECT_TRUE(wh.removeFromSourceSidePlantLoop());
+  EXPECT_FALSE(wh.sourceSidePlantLoop());
+}