diff --git a/src/device/service/drivers/gnmi/__init__.py b/src/device/service/drivers/gnmi/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..925746998061f4e05c468133dfacaaa0414551c8
--- /dev/null
+++ b/src/device/service/drivers/gnmi/__init__.py
@@ -0,0 +1,27 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES
+
+ALL_RESOURCE_KEYS = [
+    RESOURCE_ENDPOINTS,
+    RESOURCE_INTERFACES,
+    RESOURCE_NETWORK_INSTANCES,
+]
+
+RESOURCE_KEY_MAPPINGS = {
+    RESOURCE_ENDPOINTS        : 'component',
+    RESOURCE_INTERFACES       : 'interface',
+    RESOURCE_NETWORK_INSTANCES: 'network_instance',
+}
diff --git a/src/device/service/drivers/gnmi/gNMI_driver.py b/src/device/service/drivers/gnmi/gNMI_driver.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a39bf222232068c11ccaadf321d566a4eb75d32
--- /dev/null
+++ b/src/device/service/drivers/gnmi/gNMI_driver.py
@@ -0,0 +1,163 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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 anytree, logging, queue, threading
+from typing import Any, Iterator, List, Optional, Tuple, Union
+from common.type_checkers.Checkers import chk_float, chk_length, chk_string, chk_type
+from device.service.driver_api._Driver import (
+    RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES,
+    _Driver)
+
+LOGGER = logging.getLogger(__name__)
+
+SPECIAL_RESOURCE_MAPPINGS = {
+    RESOURCE_ENDPOINTS        : '/endpoints',
+    RESOURCE_INTERFACES       : '/interfaces',
+    RESOURCE_NETWORK_INSTANCES: '/net-instances',
+}
+
+class MonitoringThread(threading.Thread):
+    def __init__(self, in_subscriptions : queue.Queue, out_samples : queue.Queue) -> None:
+        super().__init__(daemon=True)
+        self._in_subscriptions = in_subscriptions
+        self._out_samples = out_samples
+
+    def run(self) -> None:
+        while True:
+            # TODO: req_iterator = generate_requests(self._in_subscriptions)
+            # TODO: stub.Subscribe(req_iterator)
+            self._out_samples.put_nowait((timestamp, resource_key, value))
+
+class EmulatedDriver(_Driver):
+    def __init__(self, address : str, port : int, **settings) -> None: # pylint: disable=super-init-not-called
+        self.__lock = threading.Lock()
+
+        # endpoints = settings.get('endpoints', [])
+
+        self.__started = threading.Event()
+        self.__terminate = threading.Event()
+
+        self.__in_subscriptions = queue.Queue()
+        self.__out_samples = queue.Queue()
+
+        self.__monitoring_thread = MonitoringThread(self.__in_subscriptions, self.__out_samples)
+
+    def Connect(self) -> bool:
+        # If started, assume it is already connected
+        if self.__started.is_set(): return True
+
+        # TODO: check capabilities
+        self.__monitoring_thread.start()
+
+        # Indicate the driver is now connected to the device
+        self.__started.set()
+        return True
+
+    def Disconnect(self) -> bool:
+        # Trigger termination of loops and processes
+        self.__terminate.set()
+
+        # TODO: send unsubscriptions
+        # TODO: terminate monitoring thread
+        # TODO: disconnect gRPC
+        self.__monitoring_thread.join()
+
+        # If not started, assume it is already disconnected
+        if not self.__started.is_set(): return True
+        return True
+
+    def GetInitialConfig(self) -> List[Tuple[str, Any]]:
+        with self.__lock:
+            return []
+
+    def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]:
+        chk_type('resources', resource_keys, list)
+        with self.__lock:
+            results = []
+            for i,resource_key in enumerate(resource_keys):
+                str_resource_name = 'resource_key[#{:d}]'.format(i)
+                try:
+                    chk_string(str_resource_name, resource_key, allow_empty=False)
+                    resource_key = SPECIAL_RESOURCE_MAPPINGS.get(resource_key, resource_key)
+                except Exception as e: # pylint: disable=broad-except
+                    LOGGER.exception('Exception validating {:s}: {:s}'.format(str_resource_name, str(resource_key)))
+                    results.append((resource_key, e)) # if validation fails, store the exception
+                    continue
+
+                # TODO: if resource_key == '/endpoints': retornar lista de endpoints
+                #       results.extend(endpoints)
+            return results
+
+    def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+        chk_type('subscriptions', subscriptions, list)
+        if len(subscriptions) == 0: return []
+        results = []
+        with self.__lock:
+            for i,subscription in enumerate(subscriptions):
+                str_subscription_name = 'subscriptions[#{:d}]'.format(i)
+                try:
+                    chk_type(str_subscription_name, subscription, (list, tuple))
+                    chk_length(str_subscription_name, subscription, min_length=3, max_length=3)
+                    resource_key,sampling_duration,sampling_interval = subscription
+                    chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False)
+                    resource_path = resource_key.split('/')
+                    chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0)
+                    chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0)
+                except Exception as e: # pylint: disable=broad-except
+                    LOGGER.exception('Exception validating {:s}: {:s}'.format(str_subscription_name, str(resource_key)))
+                    results.append(e) # if validation fails, store the exception
+                    continue
+
+                # TODO: format subscription
+                # TODO: self.__in_subscriptions.put_nowait(subscription)
+                results.append(True)
+        return results
+
+    def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+        chk_type('subscriptions', subscriptions, list)
+        if len(subscriptions) == 0: return []
+        results = []
+        resolver = anytree.Resolver(pathattr='name')
+        with self.__lock:
+            for i,resource in enumerate(subscriptions):
+                str_subscription_name = 'resources[#{:d}]'.format(i)
+                try:
+                    chk_type(str_subscription_name, resource, (list, tuple))
+                    chk_length(str_subscription_name, resource, min_length=3, max_length=3)
+                    resource_key,sampling_duration,sampling_interval = resource
+                    chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False)
+                    resource_path = resource_key.split('/')
+                    chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0)
+                    chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0)
+                except Exception as e: # pylint: disable=broad-except
+                    LOGGER.exception('Exception validating {:s}: {:s}'.format(str_subscription_name, str(resource_key)))
+                    results.append(e) # if validation fails, store the exception
+                    continue
+
+                # TODO: format unsubscription
+                # TODO: self.__in_subscriptions.put_nowait(unsubscription)
+                results.append(True)
+        return results
+
+    def GetState(self, blocking=False, terminate : Optional[threading.Event] = None) -> Iterator[Tuple[str, Any]]:
+        while True:
+            if self.__terminate.is_set(): break
+            if terminate is not None and terminate.is_set(): break
+            try:
+                sample = self.__out_samples.get(block=blocking, timeout=0.1)
+            except queue.Empty:
+                if blocking: continue
+                return
+            if sample is None: continue
+            yield sample