diff --git a/src/device/service/driver_api/DriverFactory.py b/src/device/service/driver_api/DriverFactory.py
new file mode 100644
index 0000000000000000000000000000000000000000..c226e434c75286d99c2051098e0d18d9cf483caa
--- /dev/null
+++ b/src/device/service/driver_api/DriverFactory.py
@@ -0,0 +1,50 @@
+from typing import Dict, Set
+from device.service.driver_api.QueryFields import QUERY_FIELDS
+from device.service.driver_api._Driver import _Driver
+from device.service.driver_api.Exceptions import MultipleResultsForQueryException, UnsatisfiedQueryException, \
+    UnsupportedDriverClassException, UnsupportedQueryFieldException, UnsupportedQueryFieldValueException
+
+class DriverFactory:
+    def __init__(self) -> None:
+        self.__indices : Dict[str, Dict[str, Set[_Driver]]] = {} # Dict{field_name => Dict{field_value => Set{Driver}}}
+
+    def register_driver_class(self, driver_class, **query_fields):
+        if not issubclass(driver_class, _Driver): raise UnsupportedDriverClassException(str(driver_class))
+
+        driver_name = driver_class.__name__
+        unsupported_query_fields = set(query_fields.keys()).difference(set(QUERY_FIELDS.keys()))
+        if len(unsupported_query_fields) > 0:
+            raise UnsupportedQueryFieldException(unsupported_query_fields, driver_class_name=driver_name)
+
+        for field_name, field_value in query_fields.items():
+            field_indice = self.__indices.setdefault(field_name, dict())
+            field_enum_values = QUERY_FIELDS.get(field_name)
+            if field_enum_values is not None and field_value not in field_enum_values:
+                raise UnsupportedQueryFieldValueException(
+                    field_name, field_value, field_enum_values, driver_class_name=driver_name)
+            field_indice_drivers = field_indice.setdefault(field_name, set())
+            field_indice_drivers.add(driver_class)
+
+    def get_driver_class(self, **query_fields) -> _Driver:
+        unsupported_query_fields = set(query_fields.keys()).difference(set(QUERY_FIELDS.keys()))
+        if len(unsupported_query_fields) > 0: raise UnsupportedQueryFieldException(unsupported_query_fields)
+
+        candidate_driver_classes = None
+
+        for field_name, field_value in query_fields.items():
+            field_indice = self.__indices.get(field_name)
+            if field_indice is None: continue
+            field_enum_values = QUERY_FIELDS.get(field_name)
+            if field_enum_values is not None and field_value not in field_enum_values:
+                raise UnsupportedQueryFieldValueException(field_name, field_value, field_enum_values)
+            field_indice_drivers = field_indice.get(field_name)
+            if field_indice_drivers is None: continue
+
+            candidate_driver_classes = set().union(field_indice_drivers) if candidate_driver_classes is None else \
+                candidate_driver_classes.intersection(field_indice_drivers)
+
+        if len(candidate_driver_classes) == 0: raise UnsatisfiedQueryException(query_fields)
+        if len(candidate_driver_classes) >  1:
+            # TODO: Consider choosing driver with more query fields being satisfied (i.e., the most restrictive one)
+            raise MultipleResultsForQueryException(query_fields, {d.__name__ for d in candidate_driver_classes})
+        return candidate_driver_classes.pop()
diff --git a/src/device/service/driver_api/Exceptions.py b/src/device/service/driver_api/Exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..2275bb04cbf9334a6823544bc48e8ad345d2dc20
--- /dev/null
+++ b/src/device/service/driver_api/Exceptions.py
@@ -0,0 +1,30 @@
+class MultipleResultsForQueryException(Exception):
+    def __init__(self, query_fields, driver_names):
+        super().__init__('Multiple Drivers({}) satisfy QueryFields({})'.format(str(driver_names), str(query_fields)))
+
+class UnsatisfiedQueryException(Exception):
+    def __init__(self, query_fields):
+        super().__init__('No Driver satisfies QueryFields({})'.format(str(query_fields)))
+
+class UnsupportedDriverClassException(Exception):
+    def __init__(self, driver_class_name):
+        super().__init__('Class({}) is not a subclass of _Driver'.format(str(driver_class_name)))
+
+class UnsupportedQueryFieldException(Exception):
+    def __init__(self, unsupported_query_fields, driver_class_name=None):
+        if driver_class_name:
+            msg = 'QueryFields({}) specified by Driver({}) are not supported'.format(
+                str(unsupported_query_fields), str(driver_class_name))
+        else:
+            msg = 'QueryFields({}) specified in query are not supported'.format(str(unsupported_query_fields))
+        super().__init__(msg)
+
+class UnsupportedQueryFieldValueException(Exception):
+    def __init__(self, query_field_name, query_field_value, allowed_query_field_values, driver_class_name=None):
+        if driver_class_name:
+            msg = 'QueryField({}={}) specified by Driver({}) is not supported. Allowed values are {}'.format(
+                str(query_field_name), str(query_field_value), str(driver_class_name), str(allowed_query_field_values))
+        else:
+            msg = 'QueryField({}={}) specified in query is not supported. Allowed values are {}'.format(
+                str(query_field_name), str(query_field_value), str(allowed_query_field_values))
+        super().__init__(msg)
diff --git a/src/device/service/driver_api/QueryFields.py b/src/device/service/driver_api/QueryFields.py
new file mode 100644
index 0000000000000000000000000000000000000000..15b3f5b7582283083c9ea1080dc4f3d6f5390501
--- /dev/null
+++ b/src/device/service/driver_api/QueryFields.py
@@ -0,0 +1,33 @@
+from enum import Enum
+
+class DeviceTypeQueryFieldEnum(Enum):
+    OPTICAL_ROADM       = 'optical-roadm'
+    OPTICAL_TRANDPONDER = 'optical-trandponder'
+    PACKET_ROUTER       = 'packet-router'
+    PACKET_SWITCH       = 'packet-switch'
+
+class ProtocolQueryFieldEnum(Enum):
+    SOFTWARE = 'software'
+    GRPC     = 'grpc'
+    RESTAPI  = 'restapi'
+    NETCONF  = 'netconf'
+    GNMI     = 'gnmi'
+    RESTCONF = 'restconf'
+
+class DataModelQueryFieldEnum(Enum):
+    EMULATED              = 'emu'
+    OPENCONFIG            = 'oc'
+    P4                    = 'p4'
+    TRANSPORT_API         = 'tapi'
+    IETF_NETWORK_TOPOLOGY = 'ietf-netw-topo'
+    ONF_TR_352            = 'onf-tr-352'
+
+# Map allowed query fields to allowed values per query field. If no restriction (free text) None is specified
+QUERY_FIELDS = {
+    'device_type'  : {i.value for i in DeviceTypeQueryFieldEnum},
+    'protocol'     : {i.value for i in ProtocolQueryFieldEnum},
+    'data_model'   : {i.value for i in DataModelQueryFieldEnum},
+    'vendor'       : None,
+    'model'        : None,
+    'serial_number': None,
+}
diff --git a/src/device/service/driver_api/_Driver.py b/src/device/service/driver_api/_Driver.py
new file mode 100644
index 0000000000000000000000000000000000000000..129628a5d44aff7b1b51fcb39bb52eb81d92cb81
--- /dev/null
+++ b/src/device/service/driver_api/_Driver.py
@@ -0,0 +1,113 @@
+from typing import Any, Iterator, List, Tuple, Union
+
+class _Driver:
+    def __init__(self, address : str, port : int, **kwargs) -> None:
+        """ Initialize Driver.
+            Parameters:
+                address : str
+                    The address of the device
+                port : int
+                    The port of the device
+                **kwargs
+                    Extra attributes can be configured using kwargs.
+        """
+        raise NotImplementedError()
+
+    def Connect(self) -> bool:
+        """ Connect to the Device.
+            Returns:
+                succeeded : bool
+                    Boolean variable indicating if connection succeeded.
+        """
+        raise NotImplementedError()
+
+    def Disconnect(self) -> bool:
+        """ Disconnect from the Device.
+            Returns:
+                succeeded : bool
+                    Boolean variable indicating if disconnection succeeded.
+        """
+        raise NotImplementedError()
+
+    def GetConfig(self, resource : List[str]) -> List[Union[str, None]]:
+        """ Retrieve running configuration of entire device, or selected resources.
+            Parameters:
+                resources : List[str]
+                    List of keys pointing to the resources to be retrieved.
+            Returns:
+                values : List[Union[str, None]]
+                    List of values for resource keys requested. Values should be in the same order than resource keys
+                    requested. If a resource is not found, None should be specified in the List for that resource.
+        """
+        raise NotImplementedError()
+
+    def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[bool]:
+        """ Create/Update configuration for a list of resources.
+            Parameters:
+                resources : List[Tuple[str, Any]]
+                    List of tuples, each containing a resource_key pointing the resource to be modified, and a
+                    resource_value containing the new value to be set.
+            Returns:
+                results : List[bool]
+                    List of results for changes in resource keys requested. Each result is a boolean value indicating
+                    if the change succeeded. Results should be in the same order than resource keys requested.
+        """
+        raise NotImplementedError()
+
+    def DeleteConfig(self, resource : List[str]) -> List[bool]:
+        """ Delete configuration for a list of resources.
+            Parameters:
+                resources : List[str]
+                    List of keys pointing to the resources to be deleted.
+            Returns:
+                results : List[bool]
+                    List of results for delete resource keys requested. Each result is a boolean value indicating
+                    if the delete succeeded. Results should be in the same order than resource keys requested.
+        """
+        raise NotImplementedError()
+
+    def SubscribeState(self, resources : List[Tuple[str, float]]) -> List[bool]:
+        """ Subscribe to state information of entire device, or selected resources. Subscriptions are incremental.
+            Driver should keep track of requested resources.
+            Parameters:
+                resources : List[Tuple[str, float]]
+                    List of tuples, each containing a resource_key pointing the resource to be subscribed, and a
+                    sampling_rate in seconds defining the desired monitoring periodicity for that resource.
+                    Note: sampling_rate values must be a strictly positive floats.
+            Returns:
+                results : List[bool]
+                    List of results for subscriptions to resource keys requested. Each result is a boolean value
+                    indicating if the subscription succeeded. Results should be in the same order than resource keys
+                    requested.
+        """
+        raise NotImplementedError()
+
+    def UnsubscribeState(self, resources : List[str]) -> List[bool]:
+        """ Unsubscribe from state information of entire device, or selected resources. Subscriptions are incremental.
+            Driver should keep track of requested resources.
+            Parameters:
+                resources : List[str]
+                    List of resource_keys pointing the resource to be unsubscribed.
+            Returns:
+                results : List[bool]
+                    List of results for unsubscriptions from resource keys requested. Each result is a boolean value
+                    indicating if the unsubscription succeeded. Results should be in the same order than resource keys
+                    requested.
+        """
+        raise NotImplementedError()
+
+    def GetState(self) -> Iterator[Tuple[str, Any]]:
+        """ Retrieve last collected values for subscribed resources. Operates as a generator, so this method should be
+            called once and will block until values are available. When values are available, it should yield each of
+            them and block again until new values are available. When the driver is destroyed, GetState() can return
+            instead of yield to terminate the loop.
+            Example:
+                for resource_key,resource_value in my_driver.GetState():
+                    process(resource_key,resource_value)
+            Returns:
+                results : Iterator[Tuple[str, Any]]
+                    List of tuples with state samples, each containing a resource_key and a resource_value. Only
+                    resources with an active subscription must be retrieved. Periodicity of the samples is specified
+                    when creating the subscription using method SubscribeState(). Order of yielded values is arbitrary.
+        """
+        raise NotImplementedError()
diff --git a/src/device/service/driver_api/__init__.py b/src/device/service/driver_api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/device/service/drivers/emulated/QueryFields.py b/src/device/service/drivers/emulated/QueryFields.py
new file mode 100644
index 0000000000000000000000000000000000000000..6db43e5b5d4ffe1bbcc652d305981757bd960c3e
--- /dev/null
+++ b/src/device/service/drivers/emulated/QueryFields.py
@@ -0,0 +1,8 @@
+from enum import Enum
+
+VENDOR_CTTC = 'cttc'
+
+DEVICE_MODEL_EMULATED_OPTICAL_ROADM       = 'cttc_emu_opt_rdm'
+DEVICE_MODEL_EMULATED_OPTICAL_TRANDPONDER = 'cttc_emu_opt_tp'
+DEVICE_MODEL_EMULATED_PACKET_ROUTER       = 'cttc_emu_pkt_rtr'
+DEVICE_MODEL_EMULATED_PACKET_SWITCH       = 'cttc_emu_pkt_swt'
diff --git a/src/device/service/drivers/emulated/__init__.py b/src/device/service/drivers/emulated/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/device/service/drivers/openconfig/__init__.py b/src/device/service/drivers/openconfig/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391