diff --git a/iot_input_oca/README.rst b/iot_input_oca/README.rst new file mode 100644 index 00000000..24e29b68 --- /dev/null +++ b/iot_input_oca/README.rst @@ -0,0 +1,165 @@ +========= +IoT Input +========= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:966150f524b6593273b979893d6dbd787489574e12986c6c754661081b8351e9 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fiot-lightgray.png?logo=github + :target: https://github.com/OCA/iot/tree/18.0/iot_input_oca + :alt: OCA/iot +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/iot-18-0/iot-18-0-iot_input_oca + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/iot&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This addon allows to use a device in order to input data to odoo +automatically. + +It opens a URL that a device can use to connect (with a password) that +can only execute an specific action. + +Inputs are useful when a device wants to communicate to odoo for a +single and simple action. This way, the device does not need to be +configured with a odoo user and password, it is handled by odoo devices. + +Examples: + +- Sending the temperature every three minutes. +- Sending the RFID that the device has received in order to perform some + action + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +There are two endpoints you can use: Endpoint 1: /iot//action + +Takes application/x-www-form-urlencoded parameters: passphase, value +(where value is a JSON object) + +1. Create a Device on IoT > Config Devices +2. Access the Inputs section of the device +3. Create an input. You must define a serial, passphrase, function and + model + +The function that the system will call must be of the following kind: + +:: + + @api.model + def call_function(self, key): + return {} + +Where key is the input string send by the device and the result must be +a dictionary that will be responded to the device as a JSON. + +Endpoint 2: /iot//multi_input It can be used to +send values with multiple data in one POST request such as: - Values for +inputs of the same device with different address (multi input) - Values +for inputs of the same device with same address, different values (multi +event) - Mix of the above (multi input, multi event) + +Takes application/x-www-form-urlencoded parameters: passphase, values (a +JSON array of JSON objects) + +It is called using device_identification and passing two POST +parameters: device passphrase and a JSON string containing an array of +values for input - The value for the address key can be a string or a +numeric (to conserve bytes in memory restricted devices when creating +the JSON object) and is converted to string when parsing. - The value +for the value key can either be string, number or boolean according to +JSON specs. You can see an example of a valid JSON input object in the +examples folder, using a few combinations. + +It requires the function that the system will call must be of the +following kind: + +:: + + @api.model + def call_function(self, key): + 'do something + if err: + return {'status': 'error', 'message': 'The error message you want to send to the device'} + return {'status': 'ok', 'message': 'Optional success message'} + +Where key is a dict send by the device having at least value for keys: +'address', 'value' + +The function must always return a JSON with status and message. If value +contains a value with 'uuid' as key, it is returned along with the +object for the IoT device to identify success/failure per record. + +It has full error reporting and the return value is a JSON array of +dicts containing at least status and message. Error message respose is +at some points generic, though extended logging is done in Odoo server +logs. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Creu Blanca + +Contributors +------------ + +- Enric Tobella +- Dimitrios Tanis + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-etobella| image:: https://github.com/etobella.png?size=40px + :target: https://github.com/etobella + :alt: etobella + +Current `maintainer `__: + +|maintainer-etobella| + +This module is part of the `OCA/iot `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/iot_input_oca/__init__.py b/iot_input_oca/__init__.py new file mode 100644 index 00000000..5607426d --- /dev/null +++ b/iot_input_oca/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controller diff --git a/iot_input_oca/__manifest__.py b/iot_input_oca/__manifest__.py new file mode 100644 index 00000000..51584276 --- /dev/null +++ b/iot_input_oca/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright (C) 2018 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "IoT Input", + "version": "18.0.1.0.0", + "author": "Creu Blanca, Odoo Community Association (OCA)", + "category": "IoT", + "license": "AGPL-3", + "installable": True, + "summary": "IoT Input module", + "depends": ["iot_oca"], + "website": "https://github.com/OCA/iot", + "maintainers": ["etobella"], + "data": [ + "security/ir.model.access.csv", + "views/iot_device_views.xml", + "views/iot_device_input_views.xml", + ], +} diff --git a/iot_input_oca/controller/__init__.py b/iot_input_oca/controller/__init__.py new file mode 100644 index 00000000..c204f049 --- /dev/null +++ b/iot_input_oca/controller/__init__.py @@ -0,0 +1 @@ +from . import iot_input_controller diff --git a/iot_input_oca/controller/iot_input_controller.py b/iot_input_oca/controller/iot_input_controller.py new file mode 100644 index 00000000..d30a2659 --- /dev/null +++ b/iot_input_oca/controller/iot_input_controller.py @@ -0,0 +1,105 @@ +# Copyright 2018 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json +import logging + +from odoo import _, http + +_logger = logging.getLogger(__name__) + + +class CallIot(http.Controller): + @http.route( + ["/iot//action"], + type="http", + auth="none", + methods=["POST"], + csrf=False, + ) + def call_unauthorized_iot(self, serial, passphrase=False, *args, **kwargs): + request = http.request + if not request.env: + return json.dumps(False) + return json.dumps( + request.env["iot.device.input"] + .sudo() + .get_device(serial, passphrase) + .call_device(**kwargs) + ) + + @http.route( + ["/iot//multi_input"], + type="http", + auth="none", + methods=["POST"], + csrf=False, + ) + def call_unauthorized_iot_multi_input( + self, serial, passphrase=False, values=False, *args, **kwargs + ): + """Controller to write multiple input data to device inputs + + :param string passphrase: + Device passphrase in POST data. + + :param string values: + JSON formatted string containing a JSON an array of JSON objects. + """ + request = http.request + if not request.env: + _logger.warning("env not set") + return json.dumps({"status": "error", "message": _("Server Error")}) + if not passphrase: + _logger.warning("Passphrase is required") + return json.dumps( + {"status": "error", "message": _("Passphrase is required")} + ) + if not values: + _logger.warning("Values is required") + return json.dumps({"status": "error", "message": _("Values is required")}) + # Decode JSON object here and use pure python objects in further calls + try: + if isinstance(values, str): + values = json.loads(values) + if not isinstance(values, list): + raise SyntaxError + except json.decoder.JSONDecodeError: + _logger.warning("Values is not a valid JSON") + return json.dumps( + {"status": "error", "message": _("Values is not a valid JSON")} + ) + except SyntaxError: + _logger.warning("Values should be a JSON array of JSON objects") + return json.dumps( + { + "status": "error", + "message": _("Values should be a JSON array of JSON objects"), + } + ) + # Encode response to JSON and return + result = ( + request.env["iot.device.input"] + .sudo() + .get_device(serial, passphrase) + .call_device(values=values) + ) + if result["status"] != "ok": + return json.dumps({"status": result["status"]}) + return json.dumps(result["result"]) + + @http.route( + ["/iot//check"], type="http", auth="none", methods=["POST"], csrf=False + ) + def check_unauthorized_iot(self, serial, *args, **kwargs): + request = http.request + if not request.env: + return json.dumps(False) + device = ( + request.env["iot.device.input"] + .sudo() + .get_device(serial, kwargs["passphrase"]) + ) + if device: + return json.dumps({"state": True}) + return json.dumps({"state": False}) diff --git a/iot_input_oca/examples/multi_input_values.json b/iot_input_oca/examples/multi_input_values.json new file mode 100644 index 00000000..36b503a3 --- /dev/null +++ b/iot_input_oca/examples/multi_input_values.json @@ -0,0 +1,19 @@ +[ + { + "address": "ZMPT101B_1", + "value": 230 + }, + { + "address": 1, + "value": true + }, + { + "address": 2, + "value": "Door opened", + "uuid": "abcde" + }, + { + "address": 10, + "value": -18.5 + } +] diff --git a/iot_input_oca/i18n/ca.po b/iot_input_oca/i18n/ca.po new file mode 100644 index 00000000..b875689a --- /dev/null +++ b/iot_input_oca/i18n/ca.po @@ -0,0 +1,243 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * iot_input_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-06-03 11:05+0000\n" +"Last-Translator: jabelchi \n" +"Language-Team: none\n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_kanban +msgid "" +"\n" +" Inputs" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_ids +msgid "Action" +msgstr "Acció" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_count +msgid "Action Count" +msgstr "Nombre d'accions" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input_action +msgid "Action of device inputs" +msgstr "Acció d'entrades de dispositiu" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__active +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Active" +msgstr "Actiu" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__address +msgid "Address" +msgstr "Adreça" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Address for Input is required" +msgstr "L'adreça d'entrada és necessària" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__args +msgid "Args" +msgstr "Args" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_function +msgid "Call Function" +msgstr "Crida a la funció" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_model_id +msgid "Call Model" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_uid +msgid "Created by" +msgstr "Creat per" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_date +msgid "Created on" +msgstr "Creat el" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__device_id +msgid "Device" +msgstr "Dispositiu" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Device cannot be found" +msgstr "No es troba el dispositiu" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input +msgid "Device input" +msgstr "Entrada de dispositiu" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__display_name +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__display_name +msgid "Display Name" +msgstr "Nom a mostrar" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__id +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__id +msgid "ID" +msgstr "ID" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Inactive" +msgstr "Inactiu" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_ids +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__input_id +msgid "Input" +msgstr "Entrada" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_count +msgid "Input Count" +msgstr "Nombre d'entrades" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_form +msgid "Inputs" +msgstr "Entrades" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device +msgid "IoT Device" +msgstr "Dispositiu IoT" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "IoT Device Input Search" +msgstr "Cerca de dispositius IoT" + +#. module: iot_input_oca +#: model:ir.actions.act_window,name:iot_input_oca.iot_device_input_action +msgid "IoT Inputs" +msgstr "Entrades IoT" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__kwargs +msgid "Kwargs" +msgstr "Kwargs" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__lang +msgid "Language" +msgstr "Idioma" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_uid +msgid "Last Updated by" +msgstr "Darrera actualització per" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_date +msgid "Last Updated on" +msgstr "Darrera actualització el" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__name +msgid "Name" +msgstr "Nom" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__passphrase +msgid "Passphrase" +msgstr "Contrasenya" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Passphrase is required" +msgstr "Es requereix contrasenya" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__res +msgid "Res" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__serial +msgid "Serial" +msgstr "Número de sèrie" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Serial and passphrase are required" +msgstr "Es requereix número de sèrie i contrasenya" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Server Error" +msgstr "Error de servidor" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Server Error. Check server logs" +msgstr "Error de servidor. Comproveu els registres del servidor" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is not a valid JSON" +msgstr "El valor no és un JSON vàlid" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is required" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values should be a JSON array of JSON objects" +msgstr "" + +#~ msgid "Last Modified on" +#~ msgstr "Darrera modificació el" diff --git a/iot_input_oca/i18n/es.po b/iot_input_oca/i18n/es.po new file mode 100644 index 00000000..e08a160f --- /dev/null +++ b/iot_input_oca/i18n/es.po @@ -0,0 +1,245 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * iot_input_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-09-03 13:40+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_kanban +msgid "" +"\n" +" Inputs" +msgstr "" +"\n" +" Entradas" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_ids +msgid "Action" +msgstr "Acción" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_count +msgid "Action Count" +msgstr "Conteo de acciones" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input_action +msgid "Action of device inputs" +msgstr "Acción de las entradas del dispositivo" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__active +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Active" +msgstr "Activo" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__address +msgid "Address" +msgstr "Dirección" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Address for Input is required" +msgstr "Se requiere la dirección para la entrada" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__args +msgid "Args" +msgstr "Argumentos" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_function +msgid "Call Function" +msgstr "Función de llamada" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_model_id +msgid "Call Model" +msgstr "Modelo de llamada" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__device_id +msgid "Device" +msgstr "Dispositivo" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Device cannot be found" +msgstr "No se puede encontrar el dispositivo" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input +msgid "Device input" +msgstr "Entrada del dispositivo" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__display_name +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__id +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Inactive" +msgstr "Inactivo" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_ids +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__input_id +msgid "Input" +msgstr "Entrada" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_count +msgid "Input Count" +msgstr "Conteo de entradas" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_form +msgid "Inputs" +msgstr "Entradas" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device +msgid "IoT Device" +msgstr "Dispositivo IoT" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "IoT Device Input Search" +msgstr "Búsqueda de entrada de dispositivos IoT" + +#. module: iot_input_oca +#: model:ir.actions.act_window,name:iot_input_oca.iot_device_input_action +msgid "IoT Inputs" +msgstr "Entradas IoT" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__kwargs +msgid "Kwargs" +msgstr "Kwargs" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__lang +msgid "Language" +msgstr "Lenguaje" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_uid +msgid "Last Updated by" +msgstr "Actualizado por Última vez por" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__name +msgid "Name" +msgstr "Nombre" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__passphrase +msgid "Passphrase" +msgstr "Frase de acceso" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Passphrase is required" +msgstr "Se requiere una frase de acceso" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__res +msgid "Res" +msgstr "Resultado" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__serial +msgid "Serial" +msgstr "Serie" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Serial and passphrase are required" +msgstr "Se requiere serie y frase de contraseña" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Server Error" +msgstr "Error del servidor" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Server Error. Check server logs" +msgstr "Error del servidor. Compruebe los registros del servidor" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is not a valid JSON" +msgstr "Los valores no son un JSON válido" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is required" +msgstr "Se requieren valores" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values should be a JSON array of JSON objects" +msgstr "Los valores deben ser una matriz de objetos JSON" + +#~ msgid "Last Modified on" +#~ msgstr "Última Modificación el" diff --git a/iot_input_oca/i18n/fa.po b/iot_input_oca/i18n/fa.po new file mode 100644 index 00000000..6ad88f39 --- /dev/null +++ b/iot_input_oca/i18n/fa.po @@ -0,0 +1,245 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * iot_input_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-10-09 08:13+0000\n" +"Last-Translator: Mostafa Barmshory \n" +"Language-Team: none\n" +"Language: fa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_kanban +msgid "" +"\n" +" Inputs" +msgstr "" +"\n" +" ورودی‌ها" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_ids +msgid "Action" +msgstr "فعالیت" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_count +msgid "Action Count" +msgstr "تعداد فعالیت" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input_action +msgid "Action of device inputs" +msgstr "فعالیت از دستگاه‌های ورودی" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__active +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Active" +msgstr "فعال" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__address +msgid "Address" +msgstr "آدرس" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Address for Input is required" +msgstr "آدرس برای ورودی اجباری است" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__args +msgid "Args" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_function +msgid "Call Function" +msgstr "فراخوانی عملکرد" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_model_id +msgid "Call Model" +msgstr "فراخوانی مدل" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_uid +msgid "Created by" +msgstr "ایجاد شده توسط" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_date +msgid "Created on" +msgstr "ایجاد شده توسط" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__device_id +msgid "Device" +msgstr "دستگاه" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Device cannot be found" +msgstr "دستگاه یافت نشد" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input +msgid "Device input" +msgstr "دستگاه ورودی" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__display_name +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__display_name +msgid "Display Name" +msgstr "نام نمایشی" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__id +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__id +msgid "ID" +msgstr "شناسه" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Inactive" +msgstr "غیر فعال" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_ids +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__input_id +msgid "Input" +msgstr "ورودی" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_count +msgid "Input Count" +msgstr "تعداد ورودی" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_form +msgid "Inputs" +msgstr "ورودی‌ها" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device +msgid "IoT Device" +msgstr "دستگاه اینترنت اشیا" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "IoT Device Input Search" +msgstr "جستجوی دستگاه ورودی اینترنت اشیا" + +#. module: iot_input_oca +#: model:ir.actions.act_window,name:iot_input_oca.iot_device_input_action +msgid "IoT Inputs" +msgstr "ورودی‌های اینترنت اشیا" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__kwargs +msgid "Kwargs" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__lang +msgid "Language" +msgstr "زبان‌ها" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_uid +msgid "Last Updated by" +msgstr "آخرین به روز رسانی با" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_date +msgid "Last Updated on" +msgstr "آخرین به روز رسانی در" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__name +msgid "Name" +msgstr "نام" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__passphrase +msgid "Passphrase" +msgstr "گذرواژه" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Passphrase is required" +msgstr "گذرواژه اجباری است" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__res +msgid "Res" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__serial +msgid "Serial" +msgstr "سریال" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Serial and passphrase are required" +msgstr "سریال و گذرواژه اجباری است" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Server Error" +msgstr "خطای سرور" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Server Error. Check server logs" +msgstr "خطای سرور. لاگ‌های سرور را چک کنید" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is not a valid JSON" +msgstr "مقدار یک JSON معتبر نیست" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is required" +msgstr "مقدار اجباری است" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values should be a JSON array of JSON objects" +msgstr "مقدارها باید یک ارايه JSON از اشیا JSON باشد" + +#~ msgid "Last Modified on" +#~ msgstr "آخرین نگارش در" diff --git a/iot_input_oca/i18n/iot_input.pot b/iot_input_oca/i18n/iot_input.pot new file mode 100644 index 00000000..7635dc30 --- /dev/null +++ b/iot_input_oca/i18n/iot_input.pot @@ -0,0 +1,250 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * iot_input +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__action_ids +msgid "Action" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__action_count +msgid "Action Count" +msgstr "" + +#. module: iot_input +#: model:ir.model,name:iot_input.model_iot_device_input_action +msgid "Action of device inputs" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__active +#: model_terms:ir.ui.view,arch_db:iot_input.iot_device_input_search +msgid "Active" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__address +msgid "Address" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/models/iot_device.py:54 +#, python-format +msgid "Address for Input is required" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__args +msgid "Args" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__call_function +msgid "Call Function" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__call_model_id +msgid "Call Model" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__create_uid +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__create_uid +msgid "Created by" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__create_date +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__create_date +msgid "Created on" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/models/mail_message.py:15 +#, python-format +msgid "Detected automatically by %s" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__device_id +msgid "Device" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/models/iot_device_input.py:61 +#, python-format +msgid "Device cannot be found" +msgstr "" + +#. module: iot_input +#: model:ir.model,name:iot_input.model_iot_device_input +msgid "Device input" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__display_name +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__display_name +msgid "Display Name" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/models/iot_device.py:136 +#, python-format +msgid "Empty values array" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__id +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__id +msgid "ID" +msgstr "" + +#. module: iot_input +#: model_terms:ir.ui.view,arch_db:iot_input.iot_device_input_search +msgid "Inactive" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device__input_ids +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__input_id +msgid "Input" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device__input_count +msgid "Input Count" +msgstr "" + +#. module: iot_input +#: model_terms:ir.ui.view,arch_db:iot_input.iot_device_form +msgid "Inputs" +msgstr "" + +#. module: iot_input +#: model:ir.model,name:iot_input.model_iot_device +msgid "IoT Device" +msgstr "" + +#. module: iot_input +#: model_terms:ir.ui.view,arch_db:iot_input.iot_device_input_search +msgid "IoT Device Input Search" +msgstr "" + +#. module: iot_input +#: model:ir.actions.act_window,name:iot_input.iot_device_input_action +msgid "IoT Inputs" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__lang +msgid "Language" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input____last_update +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action____last_update +msgid "Last Modified on" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__write_uid +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__write_date +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__write_date +msgid "Last Updated on" +msgstr "" + +#. module: iot_input +#: model:ir.model,name:iot_input.model_mail_message +msgid "Message" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__name +msgid "Name" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__passphrase +msgid "Passphrase" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/controller/iot_input_controller.py:43 +#, python-format +msgid "Passphrase is required" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input_action__res +msgid "Res" +msgstr "" + +#. module: iot_input +#: model:ir.model.fields,field_description:iot_input.field_iot_device_input__serial +msgid "Serial" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/models/iot_device_input.py:47 +#, python-format +msgid "Serial and passphrase are required" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/controller/iot_input_controller.py:39 +#, python-format +msgid "Server Error" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/models/iot_device.py:74 +#: code:addons/iot_input/models/iot_device.py:87 +#: code:addons/iot_input/models/iot_device.py:117 +#: code:addons/iot_input/models/iot_device.py:123 +#: code:addons/iot_input/models/iot_device.py:129 +#, python-format +msgid "Server Error. Check server logs" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/models/iot_device.py:61 +#, python-format +msgid "Value for Input is required" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/controller/iot_input_controller.py:56 +#, python-format +msgid "Values is not a valid JSON" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/controller/iot_input_controller.py:47 +#, python-format +msgid "Values is required" +msgstr "" + +#. module: iot_input +#: code:addons/iot_input/controller/iot_input_controller.py:61 +#, python-format +msgid "Values should be a JSON array of JSON objects" +msgstr "" + diff --git a/iot_input_oca/i18n/iot_input_oca.pot b/iot_input_oca/i18n/iot_input_oca.pot new file mode 100644 index 00000000..17bff8cc --- /dev/null +++ b/iot_input_oca/i18n/iot_input_oca.pot @@ -0,0 +1,238 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * iot_input_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_kanban +msgid "" +"\n" +" Inputs" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_ids +msgid "Action" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_count +msgid "Action Count" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input_action +msgid "Action of device inputs" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__active +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Active" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__address +msgid "Address" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Address for Input is required" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__args +msgid "Args" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_function +msgid "Call Function" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_model_id +msgid "Call Model" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_uid +msgid "Created by" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_date +msgid "Created on" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__device_id +msgid "Device" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Device cannot be found" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input +msgid "Device input" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__display_name +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__display_name +msgid "Display Name" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__id +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__id +msgid "ID" +msgstr "" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Inactive" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_ids +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__input_id +msgid "Input" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_count +msgid "Input Count" +msgstr "" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_form +msgid "Inputs" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device +msgid "IoT Device" +msgstr "" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "IoT Device Input Search" +msgstr "" + +#. module: iot_input_oca +#: model:ir.actions.act_window,name:iot_input_oca.iot_device_input_action +msgid "IoT Inputs" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__kwargs +msgid "Kwargs" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__lang +msgid "Language" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_date +msgid "Last Updated on" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__name +msgid "Name" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__passphrase +msgid "Passphrase" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Passphrase is required" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__res +msgid "Res" +msgstr "" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__serial +msgid "Serial" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Serial and passphrase are required" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Server Error" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Server Error. Check server logs" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is not a valid JSON" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is required" +msgstr "" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values should be a JSON array of JSON objects" +msgstr "" diff --git a/iot_input_oca/i18n/it.po b/iot_input_oca/i18n/it.po new file mode 100644 index 00000000..7b2f909a --- /dev/null +++ b/iot_input_oca/i18n/it.po @@ -0,0 +1,245 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * iot_input_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-08-30 10:06+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.6.2\n" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_kanban +msgid "" +"\n" +" Inputs" +msgstr "" +"\n" +" Input" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_ids +msgid "Action" +msgstr "Azione" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__action_count +msgid "Action Count" +msgstr "Conteggio azione" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input_action +msgid "Action of device inputs" +msgstr "Azione degli input dispositivo" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__active +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Active" +msgstr "Attivo" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__address +msgid "Address" +msgstr "Indirizzo" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Address for Input is required" +msgstr "È richiesto un indirizzo per l'input" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__args +msgid "Args" +msgstr "Argomenti" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_function +msgid "Call Function" +msgstr "Richiama funzione" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__call_model_id +msgid "Call Model" +msgstr "Richiama modello" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__create_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__device_id +msgid "Device" +msgstr "Dispositivo" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Device cannot be found" +msgstr "Dispositivo non trovato" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device_input +msgid "Device input" +msgstr "Input dispositivo" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__display_name +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__id +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__id +msgid "ID" +msgstr "ID" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "Inactive" +msgstr "Inattivo" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_ids +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__input_id +msgid "Input" +msgstr "Input" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device__input_count +msgid "Input Count" +msgstr "Conteggio input" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_form +msgid "Inputs" +msgstr "Input" + +#. module: iot_input_oca +#: model:ir.model,name:iot_input_oca.model_iot_device +msgid "IoT Device" +msgstr "Dispositivo IoT" + +#. module: iot_input_oca +#: model_terms:ir.ui.view,arch_db:iot_input_oca.iot_device_input_search +msgid "IoT Device Input Search" +msgstr "Ricerca input dispositivo IoT" + +#. module: iot_input_oca +#: model:ir.actions.act_window,name:iot_input_oca.iot_device_input_action +msgid "IoT Inputs" +msgstr "Input IoT" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__kwargs +msgid "Kwargs" +msgstr "Kwargs" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__lang +msgid "Language" +msgstr "Lingua" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_uid +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__write_date +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__name +msgid "Name" +msgstr "Nome" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__passphrase +msgid "Passphrase" +msgstr "Passphrase" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Passphrase is required" +msgstr "Richiesta passphrase" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input_action__res +msgid "Res" +msgstr "Res" + +#. module: iot_input_oca +#: model:ir.model.fields,field_description:iot_input_oca.field_iot_device_input__serial +msgid "Serial" +msgstr "Seriale" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device_input.py:0 +#, python-format +msgid "Serial and passphrase are required" +msgstr "Richiesti seriale e passphrase" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Server Error" +msgstr "Errore server" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/models/iot_device.py:0 +#, python-format +msgid "Server Error. Check server logs" +msgstr "Errore server: controllare i log del server" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is not a valid JSON" +msgstr "I valori non sono in formato JSON" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values is required" +msgstr "Richiesti valori" + +#. module: iot_input_oca +#. odoo-python +#: code:addons/iot_input_oca/controller/iot_input_controller.py:0 +#, python-format +msgid "Values should be a JSON array of JSON objects" +msgstr "I valori devono essere una matrice o un oggetto JSON" + +#~ msgid "Last Modified on" +#~ msgstr "Ultima modifica il" diff --git a/iot_input_oca/models/__init__.py b/iot_input_oca/models/__init__.py new file mode 100644 index 00000000..330e4d41 --- /dev/null +++ b/iot_input_oca/models/__init__.py @@ -0,0 +1,2 @@ +from . import iot_device +from . import iot_device_input diff --git a/iot_input_oca/models/iot_device.py b/iot_input_oca/models/iot_device.py new file mode 100644 index 00000000..68c46804 --- /dev/null +++ b/iot_input_oca/models/iot_device.py @@ -0,0 +1,77 @@ +import logging + +from odoo import _, api, fields, models + +_logger = logging.getLogger(__name__) + + +class IotDevice(models.Model): + _inherit = "iot.device" + + input_ids = fields.One2many("iot.device.input", inverse_name="device_id") + input_count = fields.Integer(compute="_compute_input_count") + + @api.depends("input_ids") + def _compute_input_count(self): + for r in self: + r.input_count = len(r.input_ids) + + def action_show_input(self): + self.ensure_one() + action = self.env.ref("iot_input_oca.iot_device_input_action") + result = action.read()[0] + + result["context"] = { + "default_device_id": self.id, + } + result["domain"] = [("device_id", "=", self.id)] + if len(self.input_ids) == 1: + result["views"] = [(False, "form")] + result["res_id"] = self.input_ids.id + return result + + def parse_single_input(self, uuid=False, address=False, **kwargs): + """Handle single input for device + + :param dict value: + Dict containing at least keys 'address', 'value' + :returns: dict with keys 'status', 'message' where: + - status='ok' when value is parsed without errors + - status='error' and message='error message' when error occurs + If value contains a value with key 'uuid', it is passed in the return dict + to identify result for each entry at the iot end + :rtype: dict + """ + msg = {} + if uuid: + msg["uuid"] = uuid + if not address: + _logger.warning("Address for Input is required") + msg.update( + {"status": "error", "message": _("Address for Input is required")} + ) + return msg + device_input = self.input_ids.filtered(lambda i: i.address == str(address)) + if len(device_input) == 1: + if not device_input.active: + _logger.warning( + "Input with address %s is inactive, no data will be logged", + device_input.address, + ) + msg.update( + { + "status": "error", + "message": _("Server Error. Check server logs"), + } + ) + return msg + res = device_input.call_device(**kwargs) + if uuid: + res["uuid"] = uuid + return res + else: + _logger.warning("Input with address %s not found", address) + msg.update( + {"status": "error", "message": _("Server Error. Check server logs")} + ) + return msg diff --git a/iot_input_oca/models/iot_device_input.py b/iot_input_oca/models/iot_device_input.py new file mode 100644 index 00000000..8a9693a7 --- /dev/null +++ b/iot_input_oca/models/iot_device_input.py @@ -0,0 +1,146 @@ +import logging +import traceback +from io import StringIO + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError + +_logger = logging.getLogger(__name__) + + +class IotDeviceInput(models.Model): + _name = "iot.device.input" + _description = "Device input" + _order = "name" + + name = fields.Char(required=True) + device_id = fields.Many2one( + "iot.device", required=True, readonly=True, auto_join=True + ) + call_model_id = fields.Many2one("ir.model") + call_function = fields.Char(required=True) + active = fields.Boolean(default=True) + serial = fields.Char() + address = fields.Char() + passphrase = fields.Char() + action_ids = fields.One2many( + "iot.device.input.action", + inverse_name="input_id", + readonly=True, + ) + action_count = fields.Integer(compute="_compute_action_count") + lang = fields.Selection( + selection=lambda self: self.env["res.lang"].get_installed(), + string="Language", + ) + + @api.depends("action_ids") + def _compute_action_count(self): + for r in self: + r.action_count = len(r.action_ids) + + def _call_device(self, *args, **kwargs): + self.ensure_one() + obj = self + if self.call_model_id: + obj = self.env[self.call_model_id.model].with_context( + iot_device_input_id=self.id, + iot_device_name=self.device_id.name, + iot_device_id=self.device_id.id, + ) + if self.lang: + obj = obj.with_context(lang=self.lang) + return getattr(obj, self.call_function)(*args, **kwargs) + + def parse_args(self, serial, passphrase): + if not serial or not passphrase: + raise ValidationError(_("Serial and passphrase are required")) + return [ + ("serial", "=", serial), + ("passphrase", "=", passphrase), + ("device_id.active", "=", True), + ] + + @api.model + def get_device(self, serial, passphrase): + return self.search(self.parse_args(serial, passphrase), limit=1) + + def call_device(self, **kwargs): + if not self: + return {"status": "error", "message": _("Device cannot be found")} + new_kwargs = kwargs.copy() + args = [] + if "value" in new_kwargs and len(new_kwargs) == 1: + args.append(new_kwargs.pop("value")) + try: + # We want to control that if an error happens, + # everything will return to normal but we can process it properly + with self.env.cr.savepoint(): + res = self._call_device(*args, **new_kwargs) + if not res.get("status"): + res["status"] = "ok" + error = False + except self._swallable_exceptions(): + buff = StringIO() + traceback.print_exc(file=buff) + error = buff.getvalue() + _logger.error(error) + res = {"status": "ko"} + self.device_id.last_contact_date = fields.Datetime.now() + self.env["iot.device.input.action"].create( + self._add_action_vals(res, error, args, new_kwargs) + ) + return res + + def _swallable_exceptions(self): + # TODO: improve this list + return (UserError, ValidationError, AttributeError, TypeError) + + def _add_action_vals(self, res, error, args, kwargs): + new_res = res.copy() + if error: + new_res["error"] = error + return { + "input_id": self.id, + "args": str(args or kwargs), + "res": str(res), + } + + def test_input_device(self, value): + return {"value": value} + + def test_model_function(self, value): + return {"status": "ok", "message": value} + + def parse_multi_input(self, values): + """Handle multiple inputs for device + :param list values: + Values is a list of dicts with at least values for keys 'address', 'value' + Each dict in the list can have: + - Different address (multi input) + - Same address, different values (multi event) + - Mix of the above (multi input, multi event) + :returns: JSON encodable list of dicts + :rtype: list + """ + device = self.device_id + if not values: + _logger.warning( + "Empty values array for input with identification %s", + self.serial, + ) + return {"status": "ko"} + res = [] + for d in values: + res.append(device.parse_single_input(**d)) + return {"result": res} + + +class IoTDeviceAction(models.Model): + _name = "iot.device.input.action" + _description = "Action of device inputs" + + input_id = fields.Many2one("iot.device.input") + args = fields.Char() + kwargs = fields.Char() + res = fields.Char() diff --git a/iot_input_oca/pyproject.toml b/iot_input_oca/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/iot_input_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/iot_input_oca/readme/CONTRIBUTORS.md b/iot_input_oca/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..83dc1b76 --- /dev/null +++ b/iot_input_oca/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- Enric Tobella \<\> +- Dimitrios Tanis \<\> diff --git a/iot_input_oca/readme/DESCRIPTION.md b/iot_input_oca/readme/DESCRIPTION.md new file mode 100644 index 00000000..c26282db --- /dev/null +++ b/iot_input_oca/readme/DESCRIPTION.md @@ -0,0 +1,15 @@ +This addon allows to use a device in order to input data to odoo +automatically. + +It opens a URL that a device can use to connect (with a password) that +can only execute an specific action. + +Inputs are useful when a device wants to communicate to odoo for a +single and simple action. This way, the device does not need to be +configured with a odoo user and password, it is handled by odoo devices. + +Examples: + +- Sending the temperature every three minutes. +- Sending the RFID that the device has received in order to perform some + action diff --git a/iot_input_oca/readme/USAGE.md b/iot_input_oca/readme/USAGE.md new file mode 100644 index 00000000..15a87ed1 --- /dev/null +++ b/iot_input_oca/readme/USAGE.md @@ -0,0 +1,58 @@ +There are two endpoints you can use: Endpoint 1: /iot/\/action + +Takes application/x-www-form-urlencoded parameters: passphase, value +(where value is a JSON object) + +1. Create a Device on IoT \> Config Devices +2. Access the Inputs section of the device +3. Create an input. You must define a serial, passphrase, function and + model + +The function that the system will call must be of the following kind: + + @api.model + def call_function(self, key): + return {} + +Where key is the input string send by the device and the result must be +a dictionary that will be responded to the device as a JSON. + +Endpoint 2: /iot/\/multi_input It can be used to +send values with multiple data in one POST request such as: - Values for +inputs of the same device with different address (multi input) - Values +for inputs of the same device with same address, different values (multi +event) - Mix of the above (multi input, multi event) + +Takes application/x-www-form-urlencoded parameters: passphase, values (a +JSON array of JSON objects) + +It is called using device_identification and passing two POST +parameters: device passphrase and a JSON string containing an array of +values for input - The value for the address key can be a string or a +numeric (to conserve bytes in memory restricted devices when creating +the JSON object) and is converted to string when parsing. - The value +for the value key can either be string, number or boolean according to +JSON specs. You can see an example of a valid JSON input object in the +examples folder, using a few combinations. + +It requires the function that the system will call must be of the +following kind: + + @api.model + def call_function(self, key): + 'do something + if err: + return {'status': 'error', 'message': 'The error message you want to send to the device'} + return {'status': 'ok', 'message': 'Optional success message'} + +Where key is a dict send by the device having at least value for keys: +'address', 'value' + +The function must always return a JSON with status and message. If value +contains a value with 'uuid' as key, it is returned along with the +object for the IoT device to identify success/failure per record. + +It has full error reporting and the return value is a JSON array of +dicts containing at least status and message. Error message respose is +at some points generic, though extended logging is done in Odoo server +logs. diff --git a/iot_input_oca/security/ir.model.access.csv b/iot_input_oca/security/ir.model.access.csv new file mode 100644 index 00000000..fe82dfc8 --- /dev/null +++ b/iot_input_oca/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_iot_device_input,access_iot_device_input,model_iot_device_input,iot_oca.group_iot_user,1,1,1,0 +manage_iot_device_input,access_iot_device_input,model_iot_device_input,iot_oca.group_iot_manager,1,1,1,1 +access_iot_device_input_action,access_iot_device_input_action,model_iot_device_input_action,iot_oca.group_iot_user,1,1,1,0 +manage_iot_device_input_action,access_iot_device_input_action,model_iot_device_input_action,iot_oca.group_iot_manager,1,1,1,1 diff --git a/iot_input_oca/static/description/icon.png b/iot_input_oca/static/description/icon.png new file mode 100644 index 00000000..da43f6f0 Binary files /dev/null and b/iot_input_oca/static/description/icon.png differ diff --git a/iot_input_oca/static/description/index.html b/iot_input_oca/static/description/index.html new file mode 100644 index 00000000..81fda61b --- /dev/null +++ b/iot_input_oca/static/description/index.html @@ -0,0 +1,493 @@ + + + + + +IoT Input + + + +
+

IoT Input

+ + +

Beta License: AGPL-3 OCA/iot Translate me on Weblate Try me on Runboat

+

This addon allows to use a device in order to input data to odoo +automatically.

+

It opens a URL that a device can use to connect (with a password) that +can only execute an specific action.

+

Inputs are useful when a device wants to communicate to odoo for a +single and simple action. This way, the device does not need to be +configured with a odoo user and password, it is handled by odoo devices.

+

Examples:

+
    +
  • Sending the temperature every three minutes.
  • +
  • Sending the RFID that the device has received in order to perform some +action
  • +
+

Table of contents

+ +
+

Usage

+

There are two endpoints you can use: Endpoint 1: /iot/<serial>/action

+

Takes application/x-www-form-urlencoded parameters: passphase, value +(where value is a JSON object)

+
    +
  1. Create a Device on IoT > Config Devices
  2. +
  3. Access the Inputs section of the device
  4. +
  5. Create an input. You must define a serial, passphrase, function and +model
  6. +
+

The function that the system will call must be of the following kind:

+
+@api.model
+    def call_function(self, key):
+    return {}
+
+

Where key is the input string send by the device and the result must be +a dictionary that will be responded to the device as a JSON.

+

Endpoint 2: /iot/<device_identification>/multi_input It can be used to +send values with multiple data in one POST request such as: - Values for +inputs of the same device with different address (multi input) - Values +for inputs of the same device with same address, different values (multi +event) - Mix of the above (multi input, multi event)

+

Takes application/x-www-form-urlencoded parameters: passphase, values (a +JSON array of JSON objects)

+

It is called using device_identification and passing two POST +parameters: device passphrase and a JSON string containing an array of +values for input - The value for the address key can be a string or a +numeric (to conserve bytes in memory restricted devices when creating +the JSON object) and is converted to string when parsing. - The value +for the value key can either be string, number or boolean according to +JSON specs. You can see an example of a valid JSON input object in the +examples folder, using a few combinations.

+

It requires the function that the system will call must be of the +following kind:

+
+@api.model
+    def call_function(self, key):
+    'do something
+    if err:
+        return {'status': 'error', 'message': 'The error message you want to send to the device'}
+    return {'status': 'ok', 'message': 'Optional success message'}
+
+

Where key is a dict send by the device having at least value for keys: +‘address’, ‘value’

+

The function must always return a JSON with status and message. If value +contains a value with ‘uuid’ as key, it is returned along with the +object for the IoT device to identify success/failure per record.

+

It has full error reporting and the return value is a JSON array of +dicts containing at least status and message. Error message respose is +at some points generic, though extended logging is done in Odoo server +logs.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Creu Blanca
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

etobella

+

This module is part of the OCA/iot project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/iot_input_oca/tests/__init__.py b/iot_input_oca/tests/__init__.py new file mode 100644 index 00000000..0a28a8f1 --- /dev/null +++ b/iot_input_oca/tests/__init__.py @@ -0,0 +1,3 @@ +from . import test_iot_in +from . import test_iot_multi_input +from . import test_iot_controller diff --git a/iot_input_oca/tests/models.py b/iot_input_oca/tests/models.py new file mode 100644 index 00000000..e2a2c4c9 --- /dev/null +++ b/iot_input_oca/tests/models.py @@ -0,0 +1,10 @@ +from odoo import models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + def test_fake_iot_input(self, value): + partner = self.browse(value) + partner.message_post(body=str(value)) + return {} diff --git a/iot_input_oca/tests/test_iot_controller.py b/iot_input_oca/tests/test_iot_controller.py new file mode 100644 index 00000000..51139243 --- /dev/null +++ b/iot_input_oca/tests/test_iot_controller.py @@ -0,0 +1,141 @@ +import json + +from odoo.tests.common import HttpCase, tagged + + +@tagged("post_install", "-at_install") +class TestIotController(HttpCase): + def setUp(self): + super().setUp() + self.device_identification = "test_device_name" + self.passphrase = "password" + self.system = self.env["iot.communication.system"].create( + {"name": "Demo system"} + ) + self.device = self.env["iot.device"].create( + { + "name": "Device", + "communication_system_id": self.system.id, + } + ) + self.address_1 = "I0" + self.serial = "testingdeviceserial" + self.input_passphrase = "password" + self.device_input_1 = self.env["iot.device.input"].create( + { + "name": "Input 1", + "device_id": self.device.id, + "address": self.address_1, + "serial": self.serial, + "passphrase": self.input_passphrase, + "call_model_id": self.env.ref( + "iot_input_oca.model_iot_device_input" + ).id, + "call_function": "test_model_function", + } + ) + self.address_2 = "I1" + self.env["iot.device.input"].create( + { + "name": "Input 2", + "device_id": self.device.id, + "address": self.address_2, + "call_model_id": self.env.ref( + "iot_input_oca.model_iot_device_input" + ).id, + "call_function": "test_model_function", + } + ) + self.env["iot.device.input"].create( + { + "name": "Multi Input", + "device_id": self.device.id, + "serial": self.device_identification, + "passphrase": self.passphrase, + "call_function": "parse_multi_input", + } + ) + self.single_input_values = [{"input": self.address_1, "value": "test"}] + self.values = json.dumps( + [ + {"address": self.address_1, "value": "test value 1"}, + { + "address": self.address_1, + "value": 2.3, + }, # Checking that nothing wrong happens with a non string + {"address": self.address_2, "value": "test value 3"}, + ] + ) + + def test_single_controller(self): + res = self.url_open( + f"/iot/{self.serial}/action", + data={"passphrase": self.input_passphrase, "value": "123"}, + ) + self.assertEqual(res.json()["status"], "ok") + + def test_single_controller_archived_device(self): + self.device.write({"active": False}) + res = self.url_open( + f"/iot/{self.serial}/action", + data={"passphrase": self.input_passphrase, "value": "123"}, + ) + self.assertEqual(res.json()["status"], "error") + + def test_multi_controller_archived_device(self): + self.device.write({"active": False}) + res = self.url_open( + f"/iot/{self.serial}/multi_input", + data={"passphrase": self.input_passphrase, "values": self.values}, + ) + self.assertEqual(res.json()["status"], "error") + + def test_multi_input_controller_error_passphrase(self): + res = self.url_open( + f"/iot/{self.device_identification}/multi_input", + data={"values": self.values}, + ).json() + self.assertEqual(res["status"], "error") + + def test_multi_input_controller_error_values(self): + res = self.url_open( + f"/iot/{self.device_identification}/multi_input", + data={"passphrase": self.passphrase}, + ).json() + self.assertEqual(res["status"], "error") + + def test_multi_input_controller_syntax_error(self): + res = self.url_open( + f"/iot/{self.device_identification}/multi_input", + data={"passphrase": self.passphrase, "values": "{}"}, + ).json() + self.assertEqual(res["status"], "error") + + def test_multi_input_controller_malformed_error(self): + res = self.url_open( + f"/iot/{self.device_identification}/multi_input", + data={"passphrase": self.passphrase, "values": "{1234}"}, + ).json() + self.assertEqual(res["status"], "error") + + def test_multi_input_controller(self): + res = self.url_open( + f"/iot/{self.device_identification}/multi_input", + data={"passphrase": self.passphrase, "values": self.values}, + ) + result = res.json() + for response in result: + self.assertEqual(response["status"], "ok") + + def test_multi_input_controller_unauthorized_iot_exists(self): + res = self.url_open( + f"/iot/{self.serial}/check", data={"passphrase": self.input_passphrase} + ).json() + self.assertEqual(res["state"], True) + + def test_multi_input_controller_unauthorized_iot_no_exists(self): + res = self.url_open( + f"/iot/{self.passphrase}/check", + data={"passphrase": self.input_passphrase}, + ).json() + self.assertEqual(res["state"], False) diff --git a/iot_input_oca/tests/test_iot_in.py b/iot_input_oca/tests/test_iot_in.py new file mode 100644 index 00000000..2faba718 --- /dev/null +++ b/iot_input_oca/tests/test_iot_in.py @@ -0,0 +1,122 @@ +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestIotIn(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.serial = "testingdeviceserial" + cls.passphrase = "password" + cls.system = cls.env["iot.communication.system"].create({"name": "Demo system"}) + cls.device = cls.env["iot.device"].create( + {"name": "Device", "communication_system_id": cls.system.id} + ) + cls.device_input = cls.env["iot.device.input"].create( + { + "name": "Input", + "device_id": cls.device.id, + "active": True, + "serial": cls.serial, + "passphrase": cls.passphrase, + "call_model_id": cls.env.ref("iot_input_oca.model_iot_device_input").id, + "call_function": "test_input_device", + } + ) + cls.iot = cls.env["iot.device.input"] + + def test_device_action_count_ids(self): + self.assertEqual(self.device.input_count, 1) + + def _get_devices(self): + action = self.device.action_show_input() + devices = self.env[action["res_model"]] + if action["res_id"]: + devices = devices.browse(action["res_id"]) + else: + devices = devices.search(action["domain"]) + return devices + + def test_device_action(self): + devices = self._get_devices() + self.assertEqual(devices, self.device_input) + device_input_02 = self.env["iot.device.input"].create( + { + "name": "Input", + "device_id": self.device.id, + "active": True, + "serial": self.serial + self.serial, + "passphrase": self.passphrase, + "call_model_id": self.env.ref( + "iot_input_oca.model_iot_device_input" + ).id, + "call_function": "test_input_device", + } + ) + devices = self._get_devices() + self.assertIn(self.device_input, devices) + self.assertIn(device_input_02, devices) + + def test_device_error_wrong_serial(self): + self.assertFalse( + self.iot.get_device( + serial=self.serial + self.serial, passphrase=self.passphrase + ) + ) + + def test_device_error_wrong_passphrase(self): + self.assertFalse( + self.iot.get_device( + serial=self.serial, passphrase=self.passphrase + self.passphrase + ) + ) + + def test_device_error_archived(self): + self.device_input.active = False + self.assertFalse( + self.iot.get_device(serial=self.serial, passphrase=self.passphrase) + ) + + def test_device_error_missing_data(self): + with self.assertRaises(ValidationError): + self.iot.get_device(serial=False, passphrase=self.passphrase) + + def test_error_execution_without_device(self): + res = self.iot.call_device(value="hello") + self.assertEqual(res["status"], "error") + + def test_device_input_calling(self): + iot = self.iot.get_device(serial=self.serial, passphrase=self.passphrase) + self.assertEqual(iot, self.device_input) + self.assertEqual(0, self.device_input.action_count) + args = "hello" + res = iot.call_device(value=args) + self.assertEqual(res, {"status": "ok", "value": args}) + self.assertTrue(self.device_input.action_ids) + self.assertEqual(self.device_input.action_ids.args, str([args])) + self.assertEqual(self.device_input.action_ids.res, str(res)) + self.assertEqual(1, self.device_input.action_count) + + def test_device_input_calling_with_lang(self): + devices = self._get_devices() + self.assertEqual(devices, self.device_input) + device_input_lang = self.env["iot.device.input"].create( + { + "name": "Input", + "lang": "en_US", + "device_id": self.device.id, + "active": True, + "serial": self.serial + self.serial, + "passphrase": self.passphrase, + "call_model_id": self.env.ref( + "iot_input_oca.model_iot_device_input" + ).id, + "call_function": "test_input_device", + } + ) + devices = self._get_devices() + self.assertIn(self.device_input, devices) + self.assertIn(device_input_lang, devices) + args = "hello" + res = device_input_lang.call_device(value=args) + self.assertEqual(res, {"status": "ok", "value": args}) diff --git a/iot_input_oca/tests/test_iot_multi_input.py b/iot_input_oca/tests/test_iot_multi_input.py new file mode 100644 index 00000000..d89d1348 --- /dev/null +++ b/iot_input_oca/tests/test_iot_multi_input.py @@ -0,0 +1,195 @@ +from odoo.tests.common import TransactionCase +from odoo.tools import mute_logger + + +class TestIotIn(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.device_identification = "test_device_name" + cls.passphrase = "password" + + cls.system = cls.env["iot.communication.system"].create({"name": "Demo system"}) + cls.device = cls.env["iot.device"].create( + { + "name": "Device", + "communication_system_id": cls.system.id, + } + ) + cls.address_1 = "I0" + cls.device_input_1 = cls.env["iot.device.input"].create( + { + "name": "Input 1", + "device_id": cls.device.id, + "address": cls.address_1, + "call_model_id": cls.env.ref("iot_input_oca.model_iot_device_input").id, + "call_function": "test_model_function", + } + ) + cls.address_2 = "I1" + cls.env["iot.device.input"].create( + { + "name": "Input 2", + "device_id": cls.device.id, + "address": cls.address_2, + "call_model_id": cls.env.ref("iot_input_oca.model_iot_device_input").id, + "call_function": "test_model_function", + } + ) + cls.env["iot.device.input"].create( + { + "name": "Multi Input", + "device_id": cls.device.id, + "serial": cls.device_identification, + "passphrase": cls.passphrase, + "call_function": "parse_multi_input", + } + ) + cls.single_input_values = [{"input": cls.address_1, "value": "test"}] + cls.iot = cls.env["iot.device.input"] + + def test_multi_input_error_wrong_identification(self): + iot = self.iot.get_device( + serial=self.device_identification + self.device_identification, + passphrase=self.passphrase, + ) + # device not found -> result is error + self.assertEqual( + iot.call_device(values=self.single_input_values)["status"], + "error", + ) + + def test_multi_input_error_no_inputs(self): + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + self.assertEqual( + iot.call_device(values=[])["status"], + "ko", + ) + + def test_multi_input_non_existing_address(self): + non_existing_address = "I3" + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for response in iot.call_device( + values=[{"address": non_existing_address, "value": "test value 1"}], + )["result"]: + self.assertEqual(response["status"], "error") + + @mute_logger("odoo.addons.iot_input_oca.models.iot_device_input") + def test_error_missing_parameter(self): + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for response in iot.call_device(values=[{"address": self.address_1}])["result"]: + self.assertEqual(response["status"], "ko") + + @mute_logger("odoo.addons.iot_input_oca.models.iot_device_input") + def test_error_with_extra_args(self): + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for response in iot.call_device( + values=[{"address": self.address_1, "uuid": "abc"}], + )["result"]: + self.assertEqual(response["status"], "ko") + self.assertTrue("uuid" in response) + + def test_error_no_address_with_extra_args(self): + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for response in iot.call_device(values=[{"uuid": "abc"}])["result"]: + self.assertEqual(response["status"], "error") + self.assertTrue("uuid" in response) + + def test_error_no_address(self): + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for response in iot.call_device(values=[{"value": "test value"}])["result"]: + self.assertEqual(response["status"], "error") + + def test_correct_one_input(self): + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for response in iot.call_device( + values=[{"address": self.address_1, "value": "test"}], + )["result"]: + self.assertEqual(response["status"], "ok") + + def test_correct_two_inputs(self): + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for response in iot.call_device( + values=[ + {"address": self.address_1, "value": "test value 1"}, + { + "address": self.address_1, + "value": 2.3, + }, # Checking that nothing wrong happens with a non string + {"address": self.address_2, "value": "test value 3"}, + ], + )["result"]: + self.assertEqual(response["status"], "ok") + + def test_correct_with_extra_args(self): + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + response_with_uuid = [ + {"address": self.address_1, "value": "test value 1", "uuid": "abc"}, + {"address": self.address_1, "value": "test value 2", "uuid": "def"}, + {"address": self.address_2, "value": "test value 3", "uuid": "ghi"}, + ] + for response in iot.call_device(values=response_with_uuid)["result"]: + self.assertTrue(response["uuid"]) + self.assertEqual( + response["message"], + [x for x in response_with_uuid if x["uuid"] == response["uuid"]][0][ + "value" + ], + ) + + def test_error_archived_device(self): + self.device.active = False + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + self.assertEqual( + iot.call_device( + values=[{"address": self.address_1, "value": "test"}], + )["status"], + "error", + ) + + def test_error_archived_device_input(self): + self.device_input_1.active = False + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for result in iot.call_device( + values=[{"address": self.address_1, "value": "test"}], + )["result"]: + self.assertEqual( + result["status"], + "error", + ) + + def test_error_archived_device_input_extra_args(self): + self.device_input_1.active = False + iot = self.iot.get_device( + serial=self.device_identification, passphrase=self.passphrase + ) + for result in iot.call_device( + values=[{"address": self.address_1, "value": "test", "uuid": "ghi"}], + )["result"]: + self.assertEqual( + result["status"], + "error", + ) + self.assertEqual(result["uuid"], "ghi") diff --git a/iot_input_oca/views/iot_device_input_views.xml b/iot_input_oca/views/iot_device_input_views.xml new file mode 100644 index 00000000..3c1a7c2c --- /dev/null +++ b/iot_input_oca/views/iot_device_input_views.xml @@ -0,0 +1,79 @@ + + + + iot.device.input.list + iot.device.input + + + + + + + + + + iot.device.input.form + iot.device.input + +
+
+ + + +
+
+

+ +

+
+ + + + + + + + + + + + + + + + + + + iot.device.input.search + iot.device.input + + + + + + + + + + + IoT Inputs + ir.actions.act_window + iot.device.input + list,form + + diff --git a/iot_input_oca/views/iot_device_views.xml b/iot_input_oca/views/iot_device_views.xml new file mode 100644 index 00000000..d5b59ed7 --- /dev/null +++ b/iot_input_oca/views/iot_device_views.xml @@ -0,0 +1,38 @@ + + + + iot.device.form + iot.device + + + + + + + + + iot.device.kanban + iot.device + + + + + + Inputs + + + + +