diff --git a/addons/hw_drivers/controllers/driver.py b/addons/hw_drivers/controllers/driver.py index b82800e55a8..3f1c526cb66 100755 --- a/addons/hw_drivers/controllers/driver.py +++ b/addons/hw_drivers/controllers/driver.py @@ -30,6 +30,15 @@ class DriverController(http.Controller): if iot_device: iot_device.data['owner'] = session_id data = json.loads(data) + + # Skip the request if it was already executed (duplicated action calls) + iot_idempotent_id = data.get("iot_idempotent_id") + if iot_idempotent_id: + idempotent_session = iot_device._check_idempotency(iot_idempotent_id, session_id) + if idempotent_session: + _logger.info("Ignored request from %s as iot_idempotent_id %s already received from session %s", + session_id, iot_idempotent_id, idempotent_session) + return False iot_device.action(data) return True return False diff --git a/addons/hw_drivers/driver.py b/addons/hw_drivers/driver.py index 0eaf5e1c226..812c7e5a733 100644 --- a/addons/hw_drivers/driver.py +++ b/addons/hw_drivers/driver.py @@ -4,6 +4,7 @@ from threading import Thread, Event from odoo.addons.hw_drivers.main import drivers, iot_devices +from odoo.tools.lru import LRU class DriverMetaClass(type): @@ -35,6 +36,9 @@ class Driver(Thread, metaclass=DriverMetaClass): self._actions = {} self._stopped = Event() + # Least Recently Used (LRU) Cache that will store the idempotent keys already seen. + self._iot_idempotent_ids_cache = LRU(500) + @classmethod def supported(cls, device): """ @@ -54,3 +58,18 @@ class Driver(Thread, metaclass=DriverMetaClass): def disconnect(self): self._stopped.set() del iot_devices[self.device_identifier] + + def _check_idempotency(self, iot_idempotent_id, session_id): + """ + Some IoT requests for the same action might be received several times. + To avoid duplicating the resulting actions, we check if the action was "recently" executed. + If this is the case, we will simply ignore the action + + :return: the `session_id` of the same `iot_idempotent_id` if any. False otherwise, + which means that it is the first time that the IoT box received the request with this ID + """ + cache = self._iot_idempotent_ids_cache + if iot_idempotent_id in cache: + return cache[iot_idempotent_id] + cache[iot_idempotent_id] = session_id + return False