Allows Monsoons to specify their type.

Bug: 147211051
Test: UTs added
Change-Id: I2fc058f9b908b20e274046e190205bb7f94beae8
diff --git a/acts/framework/acts/controllers/monsoon.py b/acts/framework/acts/controllers/monsoon.py
index 9488837..a514e99 100644
--- a/acts/framework/acts/controllers/monsoon.py
+++ b/acts/framework/acts/controllers/monsoon.py
@@ -13,25 +13,57 @@
 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
-
 from acts.controllers.monsoon_lib.api.hvpm.monsoon import Monsoon as HvpmMonsoon
-from acts.controllers.monsoon_lib.api.lvpm_stock.monsoon import Monsoon as LvpmStockMonsoon
+from acts.controllers.monsoon_lib.api.lvpm_stock.monsoon import \
+    Monsoon as LvpmStockMonsoon
 
 ACTS_CONTROLLER_CONFIG_NAME = 'Monsoon'
 ACTS_CONTROLLER_REFERENCE_NAME = 'monsoons'
 
 
 def create(configs):
+    """Takes a list of Monsoon configs and returns Monsoon Controllers.
+
+    Args:
+        configs: A list of serial numbers, or dicts in the form:
+            {
+                'type': anyof('LvpmStockMonsoon', 'HvpmMonsoon')
+                'serial': int
+            }
+
+    Returns:
+        a list of Monsoon configs
+
+    Raises:
+        ValueError if the configuration does not provide the required info.
+    """
     objs = []
-    for serial in configs:
-        serial_number = int(serial)
-        if serial_number < 20000:
-            # This code assumes the LVPM has not been updated to have a
-            # non-stock firmware. If someone has updated the firmware,
-            # power measurement will fail.
-            objs.append(LvpmStockMonsoon(serial=serial_number))
+    for config in configs:
+        monsoon_type = None
+        if isinstance(config, dict):
+            if isinstance(config.get('type', None), str):
+                if 'lvpm' in config['type'].lower():
+                    monsoon_type = LvpmStockMonsoon
+                elif 'hvpm' in config['type'].lower():
+                    monsoon_type = HvpmMonsoon
+                else:
+                    raise ValueError('Unknown monsoon type %s in Monsoon '
+                                     'config %s' % (config['type'], config))
+            if 'serial' not in config:
+                raise ValueError('Monsoon config must specify "serial".')
+            serial_number = int(config.get('serial'))
         else:
-            objs.append(HvpmMonsoon(serial=serial_number))
+            serial_number = int(config)
+        if monsoon_type is None:
+            if serial_number < 20000:
+                # This code assumes the LVPM has firmware version 20. If
+                # someone has updated the firmware, or somehow found an older
+                # version, the power measurement will fail.
+                monsoon_type = LvpmStockMonsoon
+            else:
+                monsoon_type = HvpmMonsoon
+
+        objs.append(monsoon_type(serial=serial_number))
     return objs
 
 
diff --git a/acts/framework/tests/controllers/monsoon_test.py b/acts/framework/tests/controllers/monsoon_test.py
new file mode 100755
index 0000000..71b83c9
--- /dev/null
+++ b/acts/framework/tests/controllers/monsoon_test.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+
+import mock
+
+from acts.controllers import monsoon
+from acts.controllers.monsoon_lib.api.hvpm.monsoon import Monsoon as HvpmMonsoon
+from acts.controllers.monsoon_lib.api.lvpm_stock.monsoon import Monsoon as LvpmStockMonsoon
+
+
+@mock.patch('acts.controllers.monsoon_lib.api.lvpm_stock.monsoon.MonsoonProxy')
+@mock.patch('acts.controllers.monsoon_lib.api.hvpm.monsoon.HVPM')
+class MonsoonTest(unittest.TestCase):
+    """Tests the acts.controllers.iperf_client module functions."""
+    def test_create_can_create_lvpm_from_id_only(self, *_):
+        monsoons = monsoon.create([12345])
+        self.assertIsInstance(monsoons[0], LvpmStockMonsoon)
+
+    def test_create_can_create_lvpm_from_dict(self, *_):
+        monsoons = monsoon.create([{'type': 'LvpmStockMonsoon', 'serial': 10}])
+        self.assertIsInstance(monsoons[0], LvpmStockMonsoon)
+        self.assertEqual(monsoons[0].serial, 10)
+
+    def test_create_can_create_hvpm_from_id_only(self, *_):
+        monsoons = monsoon.create([23456])
+        self.assertIsInstance(monsoons[0], HvpmMonsoon)
+
+    def test_create_can_create_hvpm_from_dict(self, *_):
+        monsoons = monsoon.create([{'type': 'HvpmMonsoon', 'serial': 10}])
+        self.assertIsInstance(monsoons[0], HvpmMonsoon)
+        self.assertEqual(monsoons[0].serial, 10)
+
+    def test_raises_error_if_monsoon_type_is_unknown(self, *_):
+        with self.assertRaises(ValueError):
+            monsoon.create([{'type': 'UNKNOWN', 'serial': 10}])
+
+    def test_raises_error_if_monsoon_serial_not_provided(self, *_):
+        with self.assertRaises(ValueError):
+            monsoon.create([{'type': 'LvpmStockMonsoon'}])
+
+
+if __name__ == '__main__':
+    unittest.main()