add missing payment providers and iot modules for 19.0

Add 19 payment provider modules needed by the sale module:
payment_adyen, payment_aps, payment_asiapay, payment_authorize,
payment_buckaroo, payment_demo, payment_dpo, payment_flutterwave,
payment_iyzico, payment_mercado_pago, payment_mollie, payment_nuvei,
payment_paymob, payment_paypal, payment_razorpay, payment_redsys,
payment_stripe, payment_worldline, payment_xendit

Add 3 IoT modules needed for point_of_sale:
iot_base, iot_box_image, iot_drivers

Note: Stripe test API keys replaced with placeholders.

🤖 assisted by claude
This commit is contained in:
Ernad Husremovic 2026-03-09 15:44:59 +01:00
parent 3037cab43e
commit aee3ee8bf7
1472 changed files with 194608 additions and 0 deletions

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'IoT Base',
'version': '1.0',
'category': 'Hidden',
'description': """
Base tools required by all IoT related modules.
===============================================
""",
'depends': ['web'],
'installable': True,
'author': 'Odoo S.A.',
'license': 'LGPL-3',
'assets': {
'web.assets_backend': [
'iot_base/static/src/network_utils/*',
'iot_base/static/src/device_controller.js',
],
},
}

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,32 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
# "Noemi Pla Garcia (nopl)" <nopl@odoo.com>, 2025.
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-24 19:23+0000\n"
"Last-Translator: \"Noemi Pla Garcia (nopl)\" <nopl@odoo.com>\n"
"Language-Team: Catalan <https://translate.odoo.com/projects/odoo-19/iot_base/"
"ca/>\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 5.12.2\n"
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr "Ha fallat la connexió a la IoT Box"
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,32 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
# "Patricia Gutiérrez (pagc)" <pagc@odoo.com>, 2025.
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-18 19:00+0000\n"
"Last-Translator: \"Patricia Gutiérrez (pagc)\" <pagc@odoo.com>\n"
"Language-Team: Spanish (Latin America) <https://translate.odoo.com/projects/"
"odoo-19/iot_base/es_419/>\n"
"Language: es_419\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.12.2\n"
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr "Falló la conexión a la caja IoT"
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr "No fue posible contactar a la caja IoT en %s"

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,32 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
# "Maitê Dietze (madi)" <madi@odoo.com>, 2025.
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-24 19:23+0000\n"
"Last-Translator: \"Maitê Dietze (madi)\" <madi@odoo.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.odoo.com/projects/"
"odoo-19/iot_base/pt_BR/>\n"
"Language: pt_BR\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.12.2\n"
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr "Conexão ao IoT Box falhou"
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr "Não foi possível contatar a IoT Box em %s"

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-11 13:57+0000\n"
"PO-Revision-Date: 2025-09-11 13:57+0000\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_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr ""
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,32 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * iot_base
#
# Translators:
# Wil Odoo, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~18.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-09 20:36+0000\n"
"PO-Revision-Date: 2025-05-17 15:02+0000\n"
"Last-Translator: Wil Odoo, 2025\n"
"Language-Team: Chinese (Taiwan) (https://app.transifex.com/odoo/teams/41243/zh_TW/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: zh_TW\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Connection to IoT Box failed"
msgstr "連接到IoT Box失敗"
#. module: iot_base
#. odoo-javascript
#: code:addons/iot_base/static/src/network_utils/longpolling.js:0
msgid "Failed to reach IoT Box at %s"
msgstr ""

View file

@ -0,0 +1,7 @@
declare module "services" {
import { iotLongpollingService } from "@iot_base/network_utils/longpolling";
export interface Services {
iot_longpolling: typeof iotLongpollingService
}
}

View file

@ -0,0 +1,40 @@
import { uniqueId } from "@web/core/utils/functions";
/**
* Used to communicate to the iot devices.
*/
export class DeviceController {
/**
* @param {import("@iot_base/network_utils/longpolling").IoTLongpolling} iotLongpolling
* @param {{ iot_ip: string, identifier: string, iot_id: Object, manual_measurement: string }} deviceInfo - Representation of an iot device
*/
constructor(iotLongpolling, deviceInfo) {
this.id = uniqueId('listener-');
this.iotIp = deviceInfo.iot_ip;
this.identifier = deviceInfo.identifier;
this.iotId = deviceInfo.iot_id?.id; // if class is instantiated without providing the full device record, iot_id will be undefined
this.manual_measurement = deviceInfo.manual_measurement;
this.iotLongpolling = iotLongpolling;
}
/**
* Send an action to the device.
* @param data - action to send to the device
* @param fallback - if true, no notification will be displayed on fail
*/
action(data, fallback = false) {
return this.iotLongpolling.action(this.iotIp, this.identifier, data, fallback);
}
/**
* Add a listener to the device.
* @param callback - function to call when the listener is triggered
* @param fallback - if true, no notification will be displayed on fail
*/
addListener(callback, fallback = true) {
return this.iotLongpolling.addListener(this.iotIp, [this.identifier], this.id, callback, fallback);
}
removeListener() {
return this.iotLongpolling.removeListener(this.iotIp, this.identifier, this.id);
}
}

View file

@ -0,0 +1,39 @@
import { browser } from "@web/core/browser/browser";
/**
* Format the endpoint to send the request to
* Used to ensure the request is sent with the same protocol as the current page
* (e.g. if the current page is HTTPS, the request will be sent to the IoT Box using HTTPS)
* @param ip IP Address of the IoT Box
* @param route Route to send the request to
* @returns {string} The formatted endpoint
*/
export function formatEndpoint(ip, route) {
const url = new URL(window.location.href);
url.search = "";
url.hostname = ip;
url.pathname = route;
if (url.port) url.port = "8069";
return url.toString();
}
/**
* Send a POST request to the IoT Box
* @param ip IP Address of the IoT Box
* @param route Endpoint to send the request to
* @param params Parameters to send with the request (optional)
* @param timeout Time before the request times out (default: 6000ms)
* @param headers HTTP headers to send with the request (optional)
* @returns {Promise<any>}
*/
export async function post(ip, route, params = {}, timeout = 6000, headers = {}) {
const endpoint = formatEndpoint(ip, route);
const response = await browser.fetch(endpoint, {
body: JSON.stringify({'params': params}),
method: "POST",
headers: {"Content-Type": "application/json", ...headers},
signal: AbortSignal.timeout(timeout),
});
return response.json();
}

View file

@ -0,0 +1,229 @@
import { registry } from '@web/core/registry';
import { post } from '@iot_base/network_utils/http';
import { uuid } from "@web/core/utils/strings";
import { _t } from '@web/core/l10n/translation';
export class IoTLongpolling {
static serviceDependencies = ["notification", "orm"];
actionRoute = '/iot_drivers/action';
pollRoute = '/iot_drivers/event';
rpcDelay = 1500;
maxRpcDelay = 15000;
_retries = 0;
_listeners = {};
constructor() {
this.setup(...arguments);
}
/**
* Setup in addition to constructor to allow patching
*/
setup({ notification, orm }) {
this._session_id = uuid();
this._delayedStartPolling(this.rpcDelay);
this.notification = notification;
this.orm = orm;
}
/**
* Add a device_identifier to listeners[iot_ip] and restart polling
*
* @param {string} iot_ip
* @param {Array} devices list of devices
* @param {string} listener_id
* @param {boolean} fallback if true, no notification will be displayed on fail
* @param {Callback} callback
*/
async addListener(iot_ip, devices, listener_id, callback, fallback = true) {
if (!this._listeners[iot_ip]) {
this._listeners[iot_ip] = {
last_event: 0,
devices: {},
session_id: this._session_id,
rpc: false,
};
}
for (const device of devices) {
this._listeners[iot_ip].devices[device] = {
listener_id: listener_id,
device_identifier: device,
callback: callback,
};
}
this.stopPolling(iot_ip);
this.startPolling(iot_ip, fallback);
}
/**
* Stop listening to iot device with id `device_identifier`
* @param {string} iot_ip
* @param {string} device_identifier
* @param {string} listener_id
*/
removeListener(iot_ip, device_identifier, listener_id) {
const device = this._listeners[iot_ip].devices[device_identifier];
if (device && device.listener_id === listener_id) {
delete this._listeners[iot_ip].devices[device_identifier];
}
}
/**
* Execute an action on device_identifier
* Action depends on the driver that supports the device
*
* @param {string} iot_ip
* @param {string} device_identifier
* @param {Object} data contains the information needed to perform an action on this device_identifier
* @param {boolean} fallback if true, no notification will be displayed on fail
* @param {string} route endpoint to call on the IoT Box (default: /iot_drivers/action)
*/
action(iot_ip, device_identifier, data, fallback = false, route = null) {
this.protocol = window.location.protocol;
const body = {
session_id: this._session_id,
device_identifier: device_identifier,
data,
};
return this._rpcIoT(iot_ip, route || this.actionRoute, body, undefined, fallback);
}
/**
* Start a long polling, i.e. it continually opens a long poll
* connection as long as it is not stopped (@see `stopPolling`)
* @param {string} iot_ip
* @param {boolean} fallback if true, no notification will be displayed on fail
*/
startPolling(iot_ip, fallback = true) {
if (iot_ip) {
if (!this._listeners[iot_ip].rpc) {
this._poll(iot_ip, fallback);
}
} else {
const self = this;
Object.keys(this._listeners).forEach((ip) => {
self.startPolling(ip);
});
}
}
/**
* Stops any started long polling
*
* Aborts a pending long-poll so that we immediately remove ourselves
* from listening on notifications on this channel.
*/
stopPolling(iot_ip) {
if (this._listeners[iot_ip].rpc) {
this._listeners[iot_ip].rpc.abort();
this._listeners[iot_ip].rpc = false;
}
}
_delayedStartPolling(delay) {
// ``fallback: true`` to avoid error notification on longpolling setup
setTimeout(() => this.startPolling(null, true), delay);
}
/**
* Execute an RPC to the box
* Used to do both polling or action
*
* @param {string} iot_ip IP of the IoT Box
* @param {string} route endpoint to call on the IoT Box
* @param {Object} params information needed to perform an action or the listener for the polling
* @param {number} timeout time before the request times out (undefined to use default timeout from http.js)
* @param {boolean} fallback if true, no notification will be displayed on fail
* @param {Object} headers headers to send with the request (optional, allows patching)
*/
async _rpcIoT(iot_ip, route, params, timeout = undefined, fallback = false, headers = undefined) {
try {
const result = await post(iot_ip, route, params, timeout, headers);
if (this._listeners[iot_ip] && route === this.pollRoute) {
this._listeners[iot_ip].rpc = result;
return this._listeners[iot_ip].rpc;
} else {
return result;
}
} catch {
if (!fallback) {
this._doWarnFail(iot_ip);
}
throw new Error("Longpolling action failed");
}
}
/**
* Make a poll request to an IoT Box
*
* @param {string} iot_ip
* @param {boolean} fallback if true, no notification will be displayed on fail
*/
_poll(iot_ip, fallback = true) {
const listener = this._listeners[iot_ip];
// The backend has a maximum cycle time of 50 seconds so give +10 seconds
this._rpcIoT(iot_ip, this.pollRoute, { listener: listener }, 60000, fallback).then(
(result) => {
this._retries = 0;
this._listeners[iot_ip].rpc = false;
const remainingDevices = Object.keys(this._listeners[iot_ip].devices || {});
if (result.result) {
if (this._session_id === result.result.session_id) {
this._onSuccess(iot_ip, result.result);
}
} else if (remainingDevices.length > 0) {
this._poll(iot_ip);
}
},
(e) => {
if (e.name === "TimeoutError") {
this._onError();
}
}
);
}
_onSuccess(iot_ip, result) {
this._listeners[iot_ip].last_event = result.time;
const devices = this._listeners[iot_ip].devices;
devices[result.device_identifier]?.callback(result);
if (Object.keys(devices || {}).length > 0) {
this._poll(iot_ip);
}
this._retries = 0;
}
_onError() {
this._retries++;
this._delayedStartPolling(Math.min(this.rpcDelay * this._retries, this.maxRpcDelay));
}
/**
* This method is needed in _poll.
* @param {string} url
*/
_doWarnFail(url) {
this.notification.add(
_t("Failed to reach IoT Box at %s", url),
{
title: _t("Connection to IoT Box failed"),
type: "danger",
}
);
}
}
export const iotLongpollingService = {
dependencies: IoTLongpolling.serviceDependencies,
start(_, deps) {
return new IoTLongpolling(deps);
},
};
registry.category('services').add('iot_longpolling', iotLongpollingService);

View file

@ -0,0 +1,3 @@
root_mount/
*.img
**/ngrok

View file

@ -0,0 +1,9 @@
{
'name': 'IoT Box Image Build Tools',
'category': 'Hidden/Tools',
'summary': 'Build tools for the IoT Box image',
'description': "This module allows you to build an image for the IoT Box.",
'installable': False,
'author': 'Odoo S.A.',
'license': 'LGPL-3',
}

View file

@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source ${__dir}/build_utils/methods.sh # Load useful functions
ensure_root # Check if script is run as root, exit if not
require_command losetup
require_command qemu-arm-static
require_command zerofree
MOUNT_POINT="${__dir}/root_mount"
OVERWRITE_FILES_BEFORE_INIT_DIR="${__dir}/overwrite_before_init"
OVERWRITE_FILES_AFTER_INIT_DIR="${__dir}/overwrite_after_init"
BUILD_UTILS_DIR="${__dir}/build_utils"
VERSION_IOTBOX="$(date '+%y.%m')"
if [[ "${1:-}" == "-c" || "${1:-}" == "--cleanup" ]]; then
echo "Cleaning up..."
umount -fv "${MOUNT_POINT}/boot/" > /dev/null 2>&1 || true
umount -lv "${MOUNT_POINT}" > /dev/null 2>&1 || true
rm -rfv "${MOUNT_POINT}"
rm -rf overwrite_before_init/usr
rm -rf overwrite_before_init/home/pi/odoo
rm -rfv iotbox.img
losetup -d /dev/loop0 > /dev/null 2>&1 || true
losetup -d /dev/loop1 > /dev/null 2>&1 || true
echo "Cleanup done."
exit 0
fi
# Download and extract Raspberry Pi OS, ngrok and clone Odoo repository
source ${BUILD_UTILS_DIR}/download_requirements.sh "${__dir}"
# Clone the Raspberry Pi OS image into the IoT Box image.
rsync -avh --progress "${RASPIOS}" iotbox.img
# Prepare the image for the IoT Box system: partition and format.
# source to share variables
source ${BUILD_UTILS_DIR}/partition_image.sh "iotbox.img"
# Mount system partition and customize the active system
mkdir -pv "${MOUNT_POINT}"
mount -v "${LOOP_IOT_SYS}" "${MOUNT_POINT}"
mount -v "${LOOP_IOT_BOOT}" "${MOUNT_POINT}/boot/"
QEMU_ARM_STATIC="/usr/bin/qemu-arm-static"
cp -v "${QEMU_ARM_STATIC}" "${MOUNT_POINT}/usr/bin/"
# 'Overlay' the pre-init overwrite directory onto the mounted image filesystem.
cp -av "${OVERWRITE_FILES_BEFORE_INIT_DIR}"/* "${MOUNT_POINT}"
# Reload network manager is mandatory in order to apply DNS configurations:
# it needs to be reloaded after copying the 'overwrite_before_init' files in the new image
# it needs to be performed in the classic filesystem, as 'systemctl' commands are not available in /root_bypass_ramdisks
sudo systemctl reload NetworkManager
# generate a keypair for the IoT Box SSH Certificate Authority
mkdir -pv ./.ssh
echo "y" | ssh-keygen -t ed25519 -f "./.ssh/iotbox_ca_${VERSION_IOTBOX}" -N "" -C "Odoo SSH CA ${VERSION_IOTBOX}"
cp -v "./.ssh/iotbox_ca_${VERSION_IOTBOX}.pub" "${MOUNT_POINT}/etc/ssh/ca.pub"
# Run initialization script inside /mount_point (the mounted path of the image)
chroot "${MOUNT_POINT}" /bin/bash -c "/etc/init_image.sh"
# Copy IoT Box version info.
mkdir -pv "${MOUNT_POINT}/var/odoo/"
echo "${VERSION_IOTBOX}" > "${MOUNT_POINT}/var/odoo/iotbox_version"
# 'Overlay' the post-init overwrite directory onto the mounted image filesystem.
cp -a "${OVERWRITE_FILES_AFTER_INIT_DIR}"/* "${MOUNT_POINT}"
# Unmount partitions: zerofree needs partitions to be unmounted (or mounted as read-only)
umount -fv "${MOUNT_POINT}"/boot/
umount -lv "${MOUNT_POINT}"/
echo "Running zerofree..."
zerofree -v "${LOOP_IOT_SYS}" || true
sleep 10
# Final cleanup: unmount partitions and remove loop device mappings
rm -rf "${CLONE_DIR}"
rm -rf "${OVERWRITE_FILES_BEFORE_INIT_DIR}/usr"
rm -rfv "${MOUNT_POINT}"
losetup -d ${LOOP_IOT}
echo ""
echo "Image build finished, you'll find the certificate authority keypair at './.ssh/iotbox_ca_${VERSION_IOTBOX}'"

View file

@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
require_command git
require_command tar
require_command unxz
require_command unzip
require_command wget
__dir=$1
# Ask user for branch/version and repository info
current_branch="$(git branch --show-current)"
read -p "Enter dev branch [${current_branch}]: " VERSION
VERSION=${VERSION:-$current_branch}
current_remote=$(git config branch.$current_branch.remote)
current_repo="$(git remote get-url $current_remote | sed 's/.*github.com[\/:]//' | sed 's/\/odoo.git//')"
read -p "Enter repo [${current_repo}]: " REPO
REPO="https://github.com/${REPO:-$current_repo}/odoo.git"
echo "Using repo: ${REPO}"
CLONE_DIR="${OVERWRITE_FILES_BEFORE_INIT_DIR}/home/pi/odoo"
if [ ! -d "$CLONE_DIR" ]; then
echo "Clone GitHub repo"
mkdir -pv "${CLONE_DIR}"
git clone -b ${VERSION} --no-local --no-checkout --depth=1 ${REPO} "${CLONE_DIR}"
cd "${CLONE_DIR}"
git config core.sparsecheckout true
tee -a .git/info/sparse-checkout < "${BUILD_UTILS_DIR}/sparse-checkout" > /dev/null
git read-tree -mu HEAD
git remote set-url origin "https://github.com/odoo/odoo.git" # ensure remote is the original repo
fi
cd "${__dir}"
# Download and extract the Raspberry Pi OS image if not present.
if ! file_exists *raspios*.img ; then
wget "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-11-19/2024-11-19-raspios-bookworm-armhf-lite.img.xz" -O raspios.img.xz
unxz --verbose raspios.img.xz
fi
RASPIOS=$(echo *raspios*.img)
# Download ngrok for ARM and place it in the overwrite directory.
USR_BIN="${OVERWRITE_FILES_BEFORE_INIT_DIR}/usr/bin/"
mkdir -pv "${USR_BIN}"
if ! file_exists "${USR_BIN}/ngrok" ; then
wget -O /tmp/ngrok.tgz 'https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-arm.tgz'
tar xvzf /tmp/ngrok.tgz -C "${USR_BIN}" --remove-files
fi

View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
ensure_root() {
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
}
file_exists() {
[[ -f $1 ]];
}
require_command() {
type "$1" &> /dev/null || {
echo "Command $1 is missing. Install it (e.g. with 'apt-get install $1'). Aborting." >&2;
exit 1;
}
}

View file

@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
ensure_root # Check if script is run as root, exit if not
require_command fdisk
require_command losetup
require_command parted
require_command e2fsck
require_command resize2fs
require_command e2label
IOTBOX=$1
# Enlarge the image (zero-pad). Sector size is 512 bytes (checked with: sudo fdisk -l raspios.img).
# Note that we can dd using bigger sector size to speed up the process, but we need to be careful with the amount of sectors.
#
# We want to add 2.75GiB to the system partition to have enough space to install programs:
# x (MiB) * 1024 * 1024 / 512 = y (sectors) <=> x (GiB) * 2048 = y (sectors)
# e.g. (2.75 * 1024) MiB * 2048 = 5.5M (sectors)
echo "Enlarging the image..."
SYSTEM_INCREASE_MiB=2816 # 2.75GiB * 1024 = 2816MiB, using MiB to avoid floating numbers
SYSTEM_INCREASE_AMOUNT_SECTORS=$((SYSTEM_INCREASE_MiB * 2048)) # sectors
dd if=/dev/zero of="./${IOTBOX}" bs=512 count=${SYSTEM_INCREASE_AMOUNT_SECTORS} status=progress conv=notrunc oflag=append
# Map partitions from the IoT Box image to loop devices (/dev/loop1pX).
LOOP_IOT=$(losetup -Pf --show ${IOTBOX})
LOOP_IOT_BOOT="${LOOP_IOT}p1)" # /dev/loop1p1
LOOP_IOT_SYS="${LOOP_IOT}p2" # /dev/loop1p2
echo "Loop devices for ${IOTBOX}:"
echo " Boot: ${LOOP_IOT_BOOT}"
echo " System: ${LOOP_IOT_SYS}"
# Resize the system partition to make it bigger:
# We take the last sector of the raspbian image, and add the amount of sectors of the increase (gives us the end sector).
SYSTEM_END_SECTOR=$(fdisk -l ${IOTBOX} | grep "${IOTBOX}2" | awk '{print $3}')
NEW_END_SECTOR_SYSTEM=$((SYSTEM_END_SECTOR + SYSTEM_INCREASE_AMOUNT_SECTORS - 1))
parted -s ${LOOP_IOT} resizepart 2 "${NEW_END_SECTOR_SYSTEM}s"
# We need to remap the whole image to resize the filesystem taking changes into account.
losetup -d ${LOOP_IOT}
LOOP_IOT=$(losetup -Pf --show ${IOTBOX})
LOOP_IOT_BOOT="${LOOP_IOT}p1" # /dev/loop1p1
LOOP_IOT_SYS="${LOOP_IOT}p2" # /dev/loop1p2
# Format and resize system partition filesystem.
e2fsck -fvy ${LOOP_IOT_SYS}
resize2fs ${LOOP_IOT_SYS}
# Rename the system partition (optional but cool).
e2label ${LOOP_IOT_SYS} iotboxfs

View file

@ -0,0 +1,9 @@
addons/web
addons/iot_base
addons/iot_box_image/configuration
addons/iot_drivers
addons/hw_drivers
addons/hw_posbox_homepage
addons/point_of_sale/tools/posbox/configuration
odoo/
odoo-bin

View file

@ -0,0 +1,62 @@
#!/bin/bash
STATUS_UPDATE_DELAY_SECONDS=10
GREEN_LED="/sys/class/leds/ACT/trigger"
RED_LED="/sys/class/leds/PWR/trigger"
IS_PI5=false
if grep -q "Raspberry Pi 5" /proc/device-tree/model 2>/dev/null; then
IS_PI5=true
fi
led_off() {
if $IS_PI5 && [ "$1" == "$GREEN_LED" ]; then
echo "default-on" > "$1"
else
echo "none" > "$1"
fi
}
led_constant() {
if $IS_PI5 && [ "$1" == "$GREEN_LED" ]; then
echo "none" > "$1"
else
echo "default-on" > "$1"
fi
}
led_blink() {
echo "heartbeat" > "$1"
}
# Disable both LEDs initially
led_off "$GREEN_LED"
led_off "$RED_LED"
while true; do
sleep $STATUS_UPDATE_DELAY_SECONDS
if ! ping -q -c 1 -W 2 1.1.1.1 >/dev/null; then
# No network: blink red LED, (green stays off)
led_blink "$RED_LED"
led_off "$GREEN_LED"
continue
fi
if ! systemctl is-active --quiet odoo.service; then
# Odoo service is not running: Red ON, Green OFF
led_constant "$RED_LED"
led_off "$GREEN_LED"
continue
fi
if grep -q "remote_server" /home/pi/odoo.conf 2>/dev/null; then
# Paired with database: Green ON, Red OFF
led_constant "$GREEN_LED"
led_off "$RED_LED"
else
# Not paired: blink green LED (red stays off)
led_blink "$GREEN_LED"
led_off "$RED_LED"
fi
done

View file

@ -0,0 +1,11 @@
[options]
data_dir = /var/run/odoo
log_handler = :INFO,werkzeug:WARNING
log_level = info
pidfile = /var/run/odoo/odoo.pid
limit_time_cpu = 600
limit_time_real = 1200
max_cron_threads = 0
server_wide_modules=iot_drivers,web
list_db = False
http_interface = 0.0.0.0

View file

@ -0,0 +1,64 @@
arp-scan
chromium-browser
console-data
cups
cups-ipp-utils
dbus
dnsmasq
fswebcam
git
hostapd
iw
kpartx
labwc
localepurge
mtr
nginx-full
printer-driver-all
python3
python3-aioice
python3-crc32c
python3-cryptography
python3-cups
python3-babel
python3-dateutil
python3-dbus
python3-docutils
python3-evdev
python3-jinja2
python3-libcamera
python3-netifaces
python3-num2words
python3-openssl
python3-passlib
python3-pil
python3-pip
python3-polib
python3-psutil
python3-psycopg2
python3-pyee
python3-pylibsrtp
python3-pykcs11
python3-pypdf2
python3-pyudev
python3-qrcode
python3-reportlab
python3-requests
python3-rjsmin
python3-schedule
python3-screeninfo
python3-serial
python3-urllib3
python3-usb
python3-vobject
python3-werkzeug
python3-zeep
python3-zeroconf
rsync
screen
seatd
swaybg
vim
wlr-randr
wtype
xdotool

View file

@ -0,0 +1,20 @@
# The following requirements are needed only on Raspberry Pi IoT:
gatt; sys_platform == "linux" and "rpi" in platform_release
/home/pi/odoo/addons/iot_box_image/configuration/aiortc-1.4.0-py3-none-any.whl; sys_platform == "linux" and "rpi" in platform_release
# The following requirements are needed only on Windows Virtual IoT:
aiortc==1.4.0; sys_platform == "win32"
ghostscript==0.7; sys_platform == "win32"
decorator; sys_platform == "win32"
# The following requirements are needed on every platforms, but are installed
# via apt-get on the Raspberry Pi:
PyKCS11==1.5.16; sys_platform == "win32"
schedule==1.2.1; sys_platform == "win32"
# The following requirements are needed on every platforms:
python-escpos==3.1
websocket-client==1.6.3
# The following requirements are needed only on Test IoT server:
./addons/iot_box_image/configuration/aiortc-1.4.0-py3-none-any.whl; sys_platform == "linux" and "rpi" not in platform_release

View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace
create_ramdisk () {
ORIGINAL="${1}"
RAMDISK="${ORIGINAL}_ram"
SIZE="${2}"
echo "Creating ramdisk for ${1} of size ${SIZE}..."
mount -t tmpfs -o size="${SIZE}" tmpfs "${RAMDISK}"
rsync -a --exclude="swap" --exclude="apt" --exclude="dpkg" --exclude=".mozilla" "${ORIGINAL}/" "${RAMDISK}/"
mount --bind "${RAMDISK}" "${ORIGINAL}"
}
echo "Creating ramdisks..."
# Note: As of 2025 we are using 2 Gb ram rpi4 as basic IoT Boxes.
# The 2 Gb limit applies here
create_ramdisk "/var" "512M"
create_ramdisk "/etc" "64M"
create_ramdisk "/tmp" "1G" # big size necessary for chromium kiosk usage
# bind mount / so that we can get to the real /var and /etc
mount --bind / /root_bypass_ramdisks
# allow to cups server to save configuration file of printers
mount --bind /root_bypass_ramdisks/etc/cups /root_bypass_ramdisks/etc/cups

View file

@ -0,0 +1,93 @@
#
# File/directory/user/group configuration file for the CUPS scheduler.
# See "man cups-files.conf" for a complete description of this file.
#
# List of events that are considered fatal errors for the scheduler...
#FatalErrors config
# Do we call fsync() after writing configuration or status files?
#SyncOnClose Yes
# Default user and group for filters/backends/helper programs; this cannot be
# any user or group that resolves to ID 0 for security reasons...
#User lp
#Group lp
# Administrator user group, used to match @SYSTEM in cupsd.conf policy rules...
# This cannot contain the Group value for security reasons...
SystemGroup lpadmin pi
# User that is substituted for unauthenticated (remote) root accesses...
#RemoteRoot remroot
# Do we allow file: device URIs other than to /dev/null?
#FileDevice No
# Permissions for configuration and log files...
#ConfigFilePerm 0640
#LogFilePerm 00640
# Location of the file logging all access to the scheduler; may be the name
# "syslog". If not an absolute path, the value of ServerRoot is used as the
# root directory. Also see the "AccessLogLevel" directive in cupsd.conf.
AccessLog /var/log/cups/access_log
# Location of cache files used by the scheduler...
#CacheDir /var/cache/cups
# Location of data files used by the scheduler...
#DataDir /usr/share/cups
# Location of the static web content served by the scheduler...
#DocumentRoot /usr/share/cups/doc-root
# Location of the file logging all messages produced by the scheduler and any
# helper programs; may be the name "syslog". If not an absolute path, the value
# of ServerRoot is used as the root directory. Also see the "LogLevel"
# directive in cupsd.conf.
ErrorLog /var/log/cups/error_log
# Location of fonts used by older print filters...
#FontPath /usr/share/cups/fonts
# Location of LPD configuration
#LPDConfigFile
# Location of the file logging all pages printed by the scheduler and any
# helper programs; may be the name "syslog". If not an absolute path, the value
# of ServerRoot is used as the root directory. Also see the "PageLogFormat"
# directive in cupsd.conf.
PageLog /var/log/cups/page_log
# Location of the file listing all of the local printers...
#Printcap /run/cups/printcap
# Format of the Printcap file...
#PrintcapFormat bsd
#PrintcapFormat plist
#PrintcapFormat solaris
# Location of all spool files...
#RequestRoot /var/spool/cups
# Location of helper programs...
#ServerBin /usr/lib/cups
# SSL/TLS keychain for the scheduler...
#ServerKeychain ssl
# Location of other configuration files...
ServerRoot /root_bypass_ramdisks/etc/cups
# Location of Samba configuration file...
#SMBConfigFile
# Location of scheduler state files...
#StateDir /run/cups
# Location of scheduler/helper temporary files. This directory is emptied on
# scheduler startup and cannot be one of the standard (public) temporary
# directory locations for security reasons...
#TempDir /var/spool/cups/tmp

View file

@ -0,0 +1,186 @@
#
# Configuration file for the CUPS scheduler. See "man cupsd.conf" for a
# complete description of this file.
#
# Log general information in error_log - change "warn" to "debug"
# for troubleshooting...
LogLevel warn
PageLogFormat
# Deactivate CUPS' internal logrotating, as we provide a better one, especially
# LogLevel debug2 gets usable now
MaxLogSize 0
# Only listen for connections from the local machine.
Listen 0.0.0.0:631
Listen /run/cups/cups.sock
# Show shared printers on the local network.
Browsing On
BrowseLocalProtocols dnssd
# Local printers are not shared by default
DefaultShared No
# Default authentication type, when authentication is required...
DefaultAuthType Basic
# Web interface setting...
WebInterface Yes
# Default paper size depends on the printer
DefaultPaperSize None
# Restrict access to the server...
<Location />
Order allow,deny
Allow all
</Location>
# Restrict access to the admin pages...
<Location /admin>
Order allow,deny
Allow all
</Location>
# Restrict access to configuration files...
<Location /admin/conf>
Order allow,deny
Allow all
</Location>
# Restrict access to log files...
<Location /admin/log>
Order allow,deny
Allow all
</Location>
# Set the default printer/job policies...
<Policy default>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
Order allow,deny
Allow all
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
Order allow,deny
Allow all
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default CUPS-Get-Devices>
Order allow,deny
Allow all
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
Order allow,deny
Allow all
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
Order allow,deny
Allow all
</Limit>
<Limit All>
Order allow,deny
Allow all
</Limit>
</Policy>
# Set the authenticated printer/job policies...
<Policy authenticated>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
Order allow,deny
Allow all
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
Order allow,deny
Allow all
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
Order allow,deny
Allow all
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
Order allow,deny
Allow all
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
Order allow,deny
Allow all
</Limit>
<Limit All>
Order allow,deny
Allow all
</Limit>
</Policy>
# Set the kerberized printer/job policies...
<Policy kerberos>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
Order allow,deny
Allow all
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
Order allow,deny
Allow all
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
Order allow,deny
Allow all
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
Order allow,deny
Allow all
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
Order allow,deny
Allow all
</Limit>
<Limit All>
Order allow,deny
Allow all
</Limit>
</Policy>

View file

@ -0,0 +1,20 @@
# Defaults for hostapd initscript
#
# See /usr/share/doc/hostapd/README.Debian for information about alternative
# methods of managing hostapd.
#
# Uncomment and set DAEMON_CONF to the absolute path of a hostapd configuration
# file and hostapd will be started during system boot. An example configuration
# file can be found at /usr/share/doc/hostapd/examples/hostapd.conf.gz
#
DAEMON_CONF="/etc/hostapd/hostapd.conf"
# Additional daemon options to be appended to hostapd command:-
# -d show more debug messages (-dd for even more)
# -K include key data in debug messages
# -t include timestamps in some debug messages
#
# Note that -B (daemon mode) and -P (pidfile) options are automatically
# configured by the init.d script and must not be added to DAEMON_OPTS.
#
DAEMON_OPTS="-d"

View file

@ -0,0 +1,17 @@
# This file may be changed either manually or by running dpkg-reconfigure.
#
# N.B.: dpkg-reconfigure deletes everything from this file except for
# the assignments to variables INTERFACES, HOTPLUG_INTERFACES, ARGS and
# SUSPEND_ACTION. When run it uses the current values of those variables
# as their default values, thus preserving the administrator's changes.
#
# This file is sourced by both the init script /etc/init.d/ifplugd and
# the udev script /lib/udev/ifplugd.agent to give default values.
# The init script starts ifplugd for all interfaces listed in
# INTERFACES, and the udev script starts ifplugd for all interfaces
# listed in HOTPLUG_INTERFACES. The special value all starts one
# ifplugd for all interfaces being present.
INTERFACES="eth0" # auto
HOTPLUG_INTERFACES="eth0" # all
ARGS="-q -f -u0 -d10 -w -I"
SUSPEND_ACTION="stop"

View file

@ -0,0 +1,669 @@
# Configuration file for dnsmasq.
#
# Format is one option per line, legal options are the same
# as the long options legal on the command line. See
# "/usr/sbin/dnsmasq --help" or "man 8 dnsmasq" for details.
# Listen on this specific port instead of the standard DNS port
# (53). Setting this to zero completely disables DNS function,
# leaving only DHCP and/or TFTP.
#port=5353
# The following two options make you a better netizen, since they
# tell dnsmasq to filter out queries which the public DNS cannot
# answer, and which load the servers (especially the root servers)
# unnecessarily. If you have a dial-on-demand link they also stop
# these requests from bringing up the link unnecessarily.
# Never forward plain names (without a dot or domain part)
#domain-needed
# Never forward addresses in the non-routed address spaces.
bogus-priv
# Uncomment these to enable DNSSEC validation and caching:
# (Requires dnsmasq to be built with DNSSEC option.)
#conf-file=%%PREFIX%%/share/dnsmasq/trust-anchors.conf
#dnssec
# Replies which are not DNSSEC signed may be legitimate, because the domain
# is unsigned, or may be forgeries. Setting this option tells dnsmasq to
# check that an unsigned reply is OK, by finding a secure proof that a DS
# record somewhere between the root and the domain does not exist.
# The cost of setting this is that even queries in unsigned domains will need
# one or more extra DNS queries to verify.
#dnssec-check-unsigned
# Uncomment this to filter useless windows-originated DNS requests
# which can trigger dial-on-demand links needlessly.
# Note that (amongst other things) this blocks all SRV requests,
# so don't use it if you use eg Kerberos, SIP, XMMP or Google-talk.
# This option only affects forwarding, SRV records originating for
# dnsmasq (via srv-host= lines) are not suppressed by it.
#filterwin2k
# Change this line if you want dns to get its upstream servers from
# somewhere other that /etc/resolv.conf
#resolv-file=
# By default, dnsmasq will send queries to any of the upstream
# servers it knows about and tries to favour servers to are known
# to be up. Uncommenting this forces dnsmasq to try each query
# with each server strictly in the order they appear in
# /etc/resolv.conf
#strict-order
# If you don't want dnsmasq to read /etc/resolv.conf or any other
# file, getting its servers from this file instead (see below), then
# uncomment this.
#no-resolv
# If you don't want dnsmasq to poll /etc/resolv.conf or other resolv
# files for changes and re-read them then uncomment this.
#no-poll
# Add other name servers here, with domain specs if they are for
# non-public domains.
server=/localnet/10.11.12.1
# Example of routing PTR queries to nameservers: this will send all
# address->name queries for 192.168.3/24 to nameserver 10.1.2.3
#server=/3.168.192.in-addr.arpa/10.1.2.3
# Add local-only domains here, queries in these domains are answered
# from /etc/hosts or DHCP only.
local=/localnet/
# Add domains which you want to force to an IP address here.
# The example below send any host in double-click.net to a local
# web-server.
#address=/double-click.net/127.0.0.1
address=/#/10.11.12.1
# --address (and --server) work with IPv6 addresses too.
#address=/www.thekelleys.org.uk/fe80::20d:60ff:fe36:f83
# Add the IPs of all queries to yahoo.com, google.com, and their
# subdomains to the vpn and search ipsets:
#ipset=/yahoo.com/google.com/vpn,search
# You can control how dnsmasq talks to a server: this forces
# queries to 10.1.2.3 to be routed via eth1
# server=10.1.2.3@eth1
# and this sets the source (ie local) address used to talk to
# 10.1.2.3 to 192.168.1.1 port 55 (there must be a interface with that
# IP on the machine, obviously).
# server=10.1.2.3@192.168.1.1#55
# If you want dnsmasq to change uid and gid to something other
# than the default, edit the following lines.
#user=
#group=
# If you want dnsmasq to listen for DHCP and DNS requests only on
# specified interfaces (and the loopback) give the name of the
# interface (eg eth0) here.
# Repeat the line for more than one interface.
interface=wlan0
# Or you can specify which interface _not_ to listen on
#except-interface=
# Or which to listen on by address (remember to include 127.0.0.1 if
# you use this.)
#listen-address=
# If you want dnsmasq to provide only DNS service on an interface,
# configure it as shown above, and then use the following line to
# disable DHCP and TFTP on it.
#no-dhcp-interface=
# On systems which support it, dnsmasq binds the wildcard address,
# even when it is listening on only some interfaces. It then discards
# requests that it shouldn't reply to. This has the advantage of
# working even when interfaces come and go and change address. If you
# want dnsmasq to really bind only the interfaces it is listening on,
# uncomment this option. About the only time you may need this is when
# running another nameserver on the same machine.
#bind-interfaces
# If you don't want dnsmasq to read /etc/hosts, uncomment the
# following line.
#no-hosts
# or if you want it to read another file, as well as /etc/hosts, use
# this.
#addn-hosts=/etc/banner_add_hosts
# Set this (and domain: see below) if you want to have a domain
# automatically added to simple names in a hosts-file.
#expand-hosts
# Set the domain for dnsmasq. this is optional, but if it is set, it
# does the following things.
# 1) Allows DHCP hosts to have fully qualified domain names, as long
# as the domain part matches this setting.
# 2) Sets the "domain" DHCP option thereby potentially setting the
# domain of all systems configured by DHCP
# 3) Provides the domain part for "expand-hosts"
#domain=thekelleys.org.uk
# Set a different domain for a particular subnet
#domain=wireless.thekelleys.org.uk,192.168.2.0/24
# Same idea, but range rather then subnet
#domain=reserved.thekelleys.org.uk,192.68.3.100,192.168.3.200
# Uncomment this to enable the integrated DHCP server, you need
# to supply the range of addresses available for lease and optionally
# a lease time. If you have more than one network, you will need to
# repeat this for each network on which you want to supply DHCP
# service.
domain=localnet
dhcp-range=10.11.12.2,10.11.12.254
# This is an example of a DHCP range where the netmask is given. This
# is needed for networks we reach the dnsmasq DHCP server via a relay
# agent. If you don't know what a DHCP relay agent is, you probably
# don't need to worry about this.
#dhcp-range=192.168.0.50,192.168.0.150,255.255.255.0,12h
# This is an example of a DHCP range which sets a tag, so that
# some DHCP options may be set only for this network.
#dhcp-range=set:red,192.168.0.50,192.168.0.150
# Use this DHCP range only when the tag "green" is set.
#dhcp-range=tag:green,192.168.0.50,192.168.0.150,12h
# Specify a subnet which can't be used for dynamic address allocation,
# is available for hosts with matching --dhcp-host lines. Note that
# dhcp-host declarations will be ignored unless there is a dhcp-range
# of some type for the subnet in question.
# In this case the netmask is implied (it comes from the network
# configuration on the machine running dnsmasq) it is possible to give
# an explicit netmask instead.
#dhcp-range=192.168.0.0,static
# Enable DHCPv6. Note that the prefix-length does not need to be specified
# and defaults to 64 if missing/
#dhcp-range=1234::2, 1234::500, 64, 12h
# Do Router Advertisements, BUT NOT DHCP for this subnet.
#dhcp-range=1234::, ra-only
# Do Router Advertisements, BUT NOT DHCP for this subnet, also try and
# add names to the DNS for the IPv6 address of SLAAC-configured dual-stack
# hosts. Use the DHCPv4 lease to derive the name, network segment and
# MAC address and assume that the host will also have an
# IPv6 address calculated using the SLAAC algorithm.
#dhcp-range=1234::, ra-names
# Do Router Advertisements, BUT NOT DHCP for this subnet.
# Set the lifetime to 46 hours. (Note: minimum lifetime is 2 hours.)
#dhcp-range=1234::, ra-only, 48h
# Do DHCP and Router Advertisements for this subnet. Set the A bit in the RA
# so that clients can use SLAAC addresses as well as DHCP ones.
#dhcp-range=1234::2, 1234::500, slaac
# Do Router Advertisements and stateless DHCP for this subnet. Clients will
# not get addresses from DHCP, but they will get other configuration information.
# They will use SLAAC for addresses.
#dhcp-range=1234::, ra-stateless
# Do stateless DHCP, SLAAC, and generate DNS names for SLAAC addresses
# from DHCPv4 leases.
#dhcp-range=1234::, ra-stateless, ra-names
# Do router advertisements for all subnets where we're doing DHCPv6
# Unless overridden by ra-stateless, ra-names, et al, the router
# advertisements will have the M and O bits set, so that the clients
# get addresses and configuration from DHCPv6, and the A bit reset, so the
# clients don't use SLAAC addresses.
#enable-ra
# Supply parameters for specified hosts using DHCP. There are lots
# of valid alternatives, so we will give examples of each. Note that
# IP addresses DO NOT have to be in the range given above, they just
# need to be on the same network. The order of the parameters in these
# do not matter, it's permissible to give name, address and MAC in any
# order.
# Always allocate the host with Ethernet address 11:22:33:44:55:66
# The IP address 192.168.0.60
#dhcp-host=11:22:33:44:55:66,192.168.0.60
# Always set the name of the host with hardware address
# 11:22:33:44:55:66 to be "fred"
#dhcp-host=11:22:33:44:55:66,fred
# Always give the host with Ethernet address 11:22:33:44:55:66
# the name fred and IP address 192.168.0.60 and lease time 45 minutes
#dhcp-host=11:22:33:44:55:66,fred,192.168.0.60,45m
# Give a host with Ethernet address 11:22:33:44:55:66 or
# 12:34:56:78:90:12 the IP address 192.168.0.60. Dnsmasq will assume
# that these two Ethernet interfaces will never be in use at the same
# time, and give the IP address to the second, even if it is already
# in use by the first. Useful for laptops with wired and wireless
# addresses.
#dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.60
# Give the machine which says its name is "bert" IP address
# 192.168.0.70 and an infinite lease
#dhcp-host=bert,192.168.0.70,infinite
# Always give the host with client identifier 01:02:02:04
# the IP address 192.168.0.60
#dhcp-host=id:01:02:02:04,192.168.0.60
# Always give the InfiniBand interface with hardware address
# 80:00:00:48:fe:80:00:00:00:00:00:00:f4:52:14:03:00:28:05:81 the
# ip address 192.168.0.61. The client id is derived from the prefix
# ff:00:00:00:00:00:02:00:00:02:c9:00 and the last 8 pairs of
# hex digits of the hardware address.
#dhcp-host=id:ff:00:00:00:00:00:02:00:00:02:c9:00:f4:52:14:03:00:28:05:81,192.168.0.61
# Always give the host with client identifier "marjorie"
# the IP address 192.168.0.60
#dhcp-host=id:marjorie,192.168.0.60
# Enable the address given for "judge" in /etc/hosts
# to be given to a machine presenting the name "judge" when
# it asks for a DHCP lease.
#dhcp-host=judge
# Never offer DHCP service to a machine whose Ethernet
# address is 11:22:33:44:55:66
#dhcp-host=11:22:33:44:55:66,ignore
# Ignore any client-id presented by the machine with Ethernet
# address 11:22:33:44:55:66. This is useful to prevent a machine
# being treated differently when running under different OS's or
# between PXE boot and OS boot.
#dhcp-host=11:22:33:44:55:66,id:*
# Send extra options which are tagged as "red" to
# the machine with Ethernet address 11:22:33:44:55:66
#dhcp-host=11:22:33:44:55:66,set:red
# Send extra options which are tagged as "red" to
# any machine with Ethernet address starting 11:22:33:
#dhcp-host=11:22:33:*:*:*,set:red
# Give a fixed IPv6 address and name to client with
# DUID 00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2
# Note the MAC addresses CANNOT be used to identify DHCPv6 clients.
# Note also the they [] around the IPv6 address are obligatory.
#dhcp-host=id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5]
# Ignore any clients which are not specified in dhcp-host lines
# or /etc/ethers. Equivalent to ISC "deny unknown-clients".
# This relies on the special "known" tag which is set when
# a host is matched.
#dhcp-ignore=tag:!known
# Send extra options which are tagged as "red" to any machine whose
# DHCP vendorclass string includes the substring "Linux"
#dhcp-vendorclass=set:red,Linux
# Send extra options which are tagged as "red" to any machine one
# of whose DHCP userclass strings includes the substring "accounts"
#dhcp-userclass=set:red,accounts
# Send extra options which are tagged as "red" to any machine whose
# MAC address matches the pattern.
#dhcp-mac=set:red,00:60:8C:*:*:*
# If this line is uncommented, dnsmasq will read /etc/ethers and act
# on the ethernet-address/IP pairs found there just as if they had
# been given as --dhcp-host options. Useful if you keep
# MAC-address/host mappings there for other purposes.
#read-ethers
# Send options to hosts which ask for a DHCP lease.
# See RFC 2132 for details of available options.
# Common options can be given to dnsmasq by name:
# run "dnsmasq --help dhcp" to get a list.
# Note that all the common settings, such as netmask and
# broadcast address, DNS server and default route, are given
# sane defaults by dnsmasq. You very likely will not need
# any dhcp-options. If you use Windows clients and Samba, there
# are some options which are recommended, they are detailed at the
# end of this section.
# Override the default route supplied by dnsmasq, which assumes the
# router is the same machine as the one running dnsmasq.
#dhcp-option=3,1.2.3.4
dhcp-option=3,10.11.12.1
dhcp-option=6,10.11.12.1
# Do the same thing, but using the option name
#dhcp-option=option:router,1.2.3.4
# Override the default route supplied by dnsmasq and send no default
# route at all. Note that this only works for the options sent by
# default (1, 3, 6, 12, 28) the same line will send a zero-length option
# for all other option numbers.
#dhcp-option=3
# Set the NTP time server addresses to 192.168.0.4 and 10.10.0.5
#dhcp-option=option:ntp-server,192.168.0.4,10.10.0.5
# Send DHCPv6 option. Note [] around IPv6 addresses.
#dhcp-option=option6:dns-server,[1234::77],[1234::88]
# Send DHCPv6 option for namservers as the machine running
# dnsmasq and another.
#dhcp-option=option6:dns-server,[::],[1234::88]
# Ask client to poll for option changes every six hours. (RFC4242)
#dhcp-option=option6:information-refresh-time,6h
# Set option 58 client renewal time (T1). Defaults to half of the
# lease time if not specified. (RFC2132)
#dhcp-option=option:T1:1m
# Set option 59 rebinding time (T2). Defaults to 7/8 of the
# lease time if not specified. (RFC2132)
#dhcp-option=option:T2:2m
# Set the NTP time server address to be the same machine as
# is running dnsmasq
#dhcp-option=42,0.0.0.0
# Set the NIS domain name to "welly"
#dhcp-option=40,welly
# Set the default time-to-live to 50
#dhcp-option=23,50
# Set the "all subnets are local" flag
#dhcp-option=27,1
# Send the etherboot magic flag and then etherboot options (a string).
#dhcp-option=128,e4:45:74:68:00:00
#dhcp-option=129,NIC=eepro100
# Specify an option which will only be sent to the "red" network
# (see dhcp-range for the declaration of the "red" network)
# Note that the tag: part must precede the option: part.
#dhcp-option = tag:red, option:ntp-server, 192.168.1.1
# The following DHCP options set up dnsmasq in the same way as is specified
# for the ISC dhcpcd in
# http://www.samba.org/samba/ftp/docs/textdocs/DHCP-Server-Configuration.txt
# adapted for a typical dnsmasq installation where the host running
# dnsmasq is also the host running samba.
# you may want to uncomment some or all of them if you use
# Windows clients and Samba.
#dhcp-option=19,0 # option ip-forwarding off
#dhcp-option=44,0.0.0.0 # set netbios-over-TCP/IP nameserver(s) aka WINS server(s)
#dhcp-option=45,0.0.0.0 # netbios datagram distribution server
#dhcp-option=46,8 # netbios node type
# Send an empty WPAD option. This may be REQUIRED to get windows 7 to behave.
#dhcp-option=252,"\n"
# Send RFC-3397 DNS domain search DHCP option. WARNING: Your DHCP client
# probably doesn't support this......
#dhcp-option=option:domain-search,eng.apple.com,marketing.apple.com
# Send RFC-3442 classless static routes (note the netmask encoding)
#dhcp-option=121,192.168.1.0/24,1.2.3.4,10.0.0.0/8,5.6.7.8
# Send vendor-class specific options encapsulated in DHCP option 43.
# The meaning of the options is defined by the vendor-class so
# options are sent only when the client supplied vendor class
# matches the class given here. (A substring match is OK, so "MSFT"
# matches "MSFT" and "MSFT 5.0"). This example sets the
# mtftp address to 0.0.0.0 for PXEClients.
#dhcp-option=vendor:PXEClient,1,0.0.0.0
# Send microsoft-specific option to tell windows to release the DHCP lease
# when it shuts down. Note the "i" flag, to tell dnsmasq to send the
# value as a four-byte integer - that's what microsoft wants. See
# http://technet2.microsoft.com/WindowsServer/en/library/a70f1bb7-d2d4-49f0-96d6-4b7414ecfaae1033.mspx?mfr=true
#dhcp-option=vendor:MSFT,2,1i
# Send the Encapsulated-vendor-class ID needed by some configurations of
# Etherboot to allow is to recognise the DHCP server.
#dhcp-option=vendor:Etherboot,60,"Etherboot"
# Send options to PXELinux. Note that we need to send the options even
# though they don't appear in the parameter request list, so we need
# to use dhcp-option-force here.
# See http://syslinux.zytor.com/pxe.php#special for details.
# Magic number - needed before anything else is recognised
#dhcp-option-force=208,f1:00:74:7e
# Configuration file name
#dhcp-option-force=209,configs/common
# Path prefix
#dhcp-option-force=210,/tftpboot/pxelinux/files/
# Reboot time. (Note 'i' to send 32-bit value)
#dhcp-option-force=211,30i
# Set the boot filename for netboot/PXE. You will only need
# this is you want to boot machines over the network and you will need
# a TFTP server; either dnsmasq's built in TFTP server or an
# external one. (See below for how to enable the TFTP server.)
#dhcp-boot=pxelinux.0
# The same as above, but use custom tftp-server instead machine running dnsmasq
#dhcp-boot=pxelinux,server.name,192.168.1.100
# Boot for Etherboot gPXE. The idea is to send two different
# filenames, the first loads gPXE, and the second tells gPXE what to
# load. The dhcp-match sets the gpxe tag for requests from gPXE.
#dhcp-match=set:gpxe,175 # gPXE sends a 175 option.
#dhcp-boot=tag:!gpxe,undionly.kpxe
#dhcp-boot=mybootimage
# Encapsulated options for Etherboot gPXE. All the options are
# encapsulated within option 175
#dhcp-option=encap:175, 1, 5b # priority code
#dhcp-option=encap:175, 176, 1b # no-proxydhcp
#dhcp-option=encap:175, 177, string # bus-id
#dhcp-option=encap:175, 189, 1b # BIOS drive code
#dhcp-option=encap:175, 190, user # iSCSI username
#dhcp-option=encap:175, 191, pass # iSCSI password
# Test for the architecture of a netboot client. PXE clients are
# supposed to send their architecture as option 93. (See RFC 4578)
#dhcp-match=peecees, option:client-arch, 0 #x86-32
#dhcp-match=itanics, option:client-arch, 2 #IA64
#dhcp-match=hammers, option:client-arch, 6 #x86-64
#dhcp-match=mactels, option:client-arch, 7 #EFI x86-64
# Do real PXE, rather than just booting a single file, this is an
# alternative to dhcp-boot.
#pxe-prompt="What system shall I netboot?"
# or with timeout before first available action is taken:
#pxe-prompt="Press F8 for menu.", 60
# Available boot services. for PXE.
#pxe-service=x86PC, "Boot from local disk"
# Loads <tftp-root>/pxelinux.0 from dnsmasq TFTP server.
#pxe-service=x86PC, "Install Linux", pxelinux
# Loads <tftp-root>/pxelinux.0 from TFTP server at 1.2.3.4.
# Beware this fails on old PXE ROMS.
#pxe-service=x86PC, "Install Linux", pxelinux, 1.2.3.4
# Use bootserver on network, found my multicast or broadcast.
#pxe-service=x86PC, "Install windows from RIS server", 1
# Use bootserver at a known IP address.
#pxe-service=x86PC, "Install windows from RIS server", 1, 1.2.3.4
# If you have multicast-FTP available,
# information for that can be passed in a similar way using options 1
# to 5. See page 19 of
# http://download.intel.com/design/archives/wfm/downloads/pxespec.pdf
# Enable dnsmasq's built-in TFTP server
#enable-tftp
# Set the root directory for files available via FTP.
#tftp-root=/var/ftpd
# Do not abort if the tftp-root is unavailable
#tftp-no-fail
# Make the TFTP server more secure: with this set, only files owned by
# the user dnsmasq is running as will be send over the net.
#tftp-secure
# This option stops dnsmasq from negotiating a larger blocksize for TFTP
# transfers. It will slow things down, but may rescue some broken TFTP
# clients.
#tftp-no-blocksize
# Set the boot file name only when the "red" tag is set.
#dhcp-boot=tag:red,pxelinux.red-net
# An example of dhcp-boot with an external TFTP server: the name and IP
# address of the server are given after the filename.
# Can fail with old PXE ROMS. Overridden by --pxe-service.
#dhcp-boot=/var/ftpd/pxelinux.0,boothost,192.168.0.3
# If there are multiple external tftp servers having a same name
# (using /etc/hosts) then that name can be specified as the
# tftp_servername (the third option to dhcp-boot) and in that
# case dnsmasq resolves this name and returns the resultant IP
# addresses in round robin fashion. This facility can be used to
# load balance the tftp load among a set of servers.
#dhcp-boot=/var/ftpd/pxelinux.0,boothost,tftp_server_name
# Set the limit on DHCP leases, the default is 150
#dhcp-lease-max=150
# The DHCP server needs somewhere on disk to keep its lease database.
# This defaults to a sane location, but if you want to change it, use
# the line below.
#dhcp-leasefile=/var/lib/misc/dnsmasq.leases
# Set the DHCP server to authoritative mode. In this mode it will barge in
# and take over the lease for any client which broadcasts on the network,
# whether it has a record of the lease or not. This avoids long timeouts
# when a machine wakes up on a new network. DO NOT enable this if there's
# the slightest chance that you might end up accidentally configuring a DHCP
# server for your campus/company accidentally. The ISC server uses
# the same option, and this URL provides more information:
# http://www.isc.org/files/auth.html
dhcp-authoritative
# Run an executable when a DHCP lease is created or destroyed.
# The arguments sent to the script are "add" or "del",
# then the MAC address, the IP address and finally the hostname
# if there is one.
#dhcp-script=/bin/echo
# Set the cachesize here.
#cache-size=150
# If you want to disable negative caching, uncomment this.
#no-negcache
# Normally responses which come from /etc/hosts and the DHCP lease
# file have Time-To-Live set as zero, which conventionally means
# do not cache further. If you are happy to trade lower load on the
# server for potentially stale date, you can set a time-to-live (in
# seconds) here.
#local-ttl=
# If you want dnsmasq to detect attempts by Verisign to send queries
# to unregistered .com and .net hosts to its sitefinder service and
# have dnsmasq instead return the correct NXDOMAIN response, uncomment
# this line. You can add similar lines to do the same for other
# registries which have implemented wildcard A records.
#bogus-nxdomain=64.94.110.11
# If you want to fix up DNS results from upstream servers, use the
# alias option. This only works for IPv4.
# This alias makes a result of 1.2.3.4 appear as 5.6.7.8
#alias=1.2.3.4,5.6.7.8
# and this maps 1.2.3.x to 5.6.7.x
#alias=1.2.3.0,5.6.7.0,255.255.255.0
# and this maps 192.168.0.10->192.168.0.40 to 10.0.0.10->10.0.0.40
#alias=192.168.0.10-192.168.0.40,10.0.0.0,255.255.255.0
# Change these lines if you want dnsmasq to serve MX records.
# Return an MX record named "maildomain.com" with target
# servermachine.com and preference 50
#mx-host=maildomain.com,servermachine.com,50
# Set the default target for MX records created using the localmx option.
#mx-target=servermachine.com
# Return an MX record pointing to the mx-target for all local
# machines.
#localmx
# Return an MX record pointing to itself for all local machines.
#selfmx
# Change the following lines if you want dnsmasq to serve SRV
# records. These are useful if you want to serve ldap requests for
# Active Directory and other windows-originated DNS requests.
# See RFC 2782.
# You may add multiple srv-host lines.
# The fields are <name>,<target>,<port>,<priority>,<weight>
# If the domain part if missing from the name (so that is just has the
# service and protocol sections) then the domain given by the domain=
# config option is used. (Note that expand-hosts does not need to be
# set for this to work.)
# A SRV record sending LDAP for the example.com domain to
# ldapserver.example.com port 389
#srv-host=_ldap._tcp.example.com,ldapserver.example.com,389
# A SRV record sending LDAP for the example.com domain to
# ldapserver.example.com port 389 (using domain=)
#srv-host=_ldap._tcp,ldapserver.example.com,389
# Two SRV records for LDAP, each with different priorities
#srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,1
#srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,2
# A SRV record indicating that there is no LDAP server for the domain
# example.com
#srv-host=_ldap._tcp.example.com
# The following line shows how to make dnsmasq serve an arbitrary PTR
# record. This is useful for DNS-SD. (Note that the
# domain-name expansion done for SRV records _does_not
# occur for PTR records.)
#ptr-record=_http._tcp.dns-sd-services,"New Employee Page._http._tcp.dns-sd-services"
# Change the following lines to enable dnsmasq to serve TXT records.
# These are used for things like SPF and zeroconf. (Note that the
# domain-name expansion done for SRV records _does_not
# occur for TXT records.)
#Example SPF.
#txt-record=example.com,"v=spf1 a -all"
#Example zeroconf
#txt-record=_http._tcp.example.com,name=value,paper=A4
# Provide an alias for a "local" DNS name. Note that this _only_ works
# for targets which are names from DHCP or /etc/hosts. Give host
# "bert" another name, bertrand
#cname=bertand,bert
# For debugging purposes, log each DNS query as it passes through
# dnsmasq.
#log-queries
# Log lots of extra information about DHCP transactions.
#log-dhcp
# Include another lot of configuration options.
#conf-file=/etc/dnsmasq.more.conf
#conf-dir=/etc/dnsmasq.d
# Include all the files in a directory except those ending in .bak
#conf-dir=/etc/dnsmasq.d,.bak
# Include all files in a directory which end in .conf
#conf-dir=/etc/dnsmasq.d/,*.conf

View file

@ -0,0 +1,4 @@
proc /proc proc defaults 0 0
/dev/mmcblk0p1 /boot vfat defaults 0 2
/dev/mmcblk0p2 / ext4 defaults,noatime 0 1
# a swapfile is not a swap partition, so no using swapon|off from here on, use dphys-swapfile swap[on|off] for that

View file

@ -0,0 +1,3 @@
interface=wlan0
ssid=IoTBox
channel=1

View file

@ -0,0 +1,11 @@
server {
# By listening specifically on the access point IP, this server block will only match
# requests when the access point is enabled. We mark it as the 'default_server' so that
# it takes priority over the regular config.
listen 10.11.12.1:80 default_server;
location / {
# Redirect any URL to the homepage (triggers the 'sign-in' popup upon connecting to the IoT box)
return 301 $scheme://10.11.12.1:8069;
}
}

View file

@ -0,0 +1,39 @@
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/ssl/certs/nginx-cert.crt;
ssl_certificate_key /etc/ssl/private/nginx-cert.key;
# Increase the allowed body size from 1MB to 10MB, this ensures
# large actions such as printing a PDF will always work.
client_max_body_size 10M;
location / {
# Increase the request timeout from 1 minute to 10 minutes,
# this is required for the self-flashing feature.
proxy_read_timeout 600s;
proxy_pass http://127.0.0.1:8069;
}
# If the iot_drivers module fails to start, the user will be sent to the
# database selector. Instead of this we will show them the 502 error page
# (IoT box is down) as it is more appropriate.
location /web/database/selector {
return 301 /502.html;
}
error_page 502 /502.html;
location /502.html {
root /var/www/html;
}
# Expose the /var/log/ folder directly via nginx, so that
# it remains accessible even if Odoo is failing to start.
location /odoo-logs {
alias /var/log/;
autoindex on;
}
}

View file

@ -0,0 +1,46 @@
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
exec 1>/var/log/rc.local.log 2>&1 # send stdout and stderr from rc.local to a log file
set -x # display commands before execution
# Ensure Wi-Fi radio is enabled
nmcli radio wifi on
# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
mkdir -p /var/run/odoo
chown odoo:odoo /var/run/odoo
# For compatibility between new IoT Box images and old Odoo code on it (it will not start Odoo on boot without this)
start_wifi=/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/wireless_ap.sh
if [ -f $start_wifi ]; then
$start_wifi &
fi
# Update current branch
cd /home/pi/odoo
localbranch=$(sudo -u odoo git symbolic-ref -q --short HEAD)
localremote=$(sudo -u odoo git config branch.$localbranch.remote)
sudo -u odoo rm -f /home/pi/odoo/.git/shallow.lock
sudo -u odoo git remote set-url "${localremote}" "https://github.com/odoo/odoo.git"
sudo -u odoo GIT_SSL_NO_VERIFY=1 git fetch "${localremote}" "${localbranch}" --depth=1
sudo -u odoo git reset --hard FETCH_HEAD
exit 0

View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID4TCCAsmgAwIBAgIJAMCws64aK3IlMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYD
VQQGEwJCRTEXMBUGA1UECAwOQnJhYmFudC1XYWxsb24xGTAXBgNVBAcMEEdyYW5k
LVJvc2nDg8KocmUxEDAOBgNVBAoMB09kb29Jb1QxDDAKBgNVBAsMA0lvVDEiMCAG
A1UEAwwZT2Rvb1RlbXBJb1RCb3hDZXJ0aWZpY2F0ZTAgFw0xODA5MjgxNjIzNDNa
GA8yMTE4MDkwNDE2MjM0M1owgYUxCzAJBgNVBAYTAkJFMRcwFQYDVQQIDA5CcmFi
YW50LVdhbGxvbjEZMBcGA1UEBwwQR3JhbmQtUm9zacODwqhyZTEQMA4GA1UECgwH
T2Rvb0lvVDEMMAoGA1UECwwDSW9UMSIwIAYDVQQDDBlPZG9vVGVtcElvVEJveENl
cnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq8O2dZJO
Pj9QJ2bIDthDNfDK4gm4jthIpwFpJFQmrWfZyQy2IiECnGwHlb8T2pZAw6LhyMbK
EPeaiyNAxztkJQavyW/tyRzWJiVI5/gPkWn3PFSWiJc7tpN2dgYlOzhEa209SJvC
qCS/ncraUt8o/KjW8F155mqYZ9qwD52tJyKjbtzIwG3KO5+ErcWGiMs77pGkimb1
f9gNQ+JclGyVJ2WUhXeFU6C8hdz1JlsDqYabaZzS0ESvXhGhstcRUU/KzdNYe/7d
Xw4vmxyQvzbyJaj+T5ILCMkgU6OPeEfswEF1qXyEIdIXlD4pSMlGzQ3MDku564e1
ebR51BwkfHwtewIDAQABo1AwTjAdBgNVHQ4EFgQU5Zyb7DuZFqb96okkP1yfDV2Q
Gv8wHwYDVR0jBBgwFoAU5Zyb7DuZFqb96okkP1yfDV2QGv8wDAYDVR0TBAUwAwEB
/zANBgkqhkiG9w0BAQsFAAOCAQEALkon2ZHMBW9t+8oig/C5I+edCniSgs+2Loh9
ufIG5G7KD8MuKjg55a9cCH1Ra5GSVZTj4krBPab21lN+8rb2mAeIEbIwyivx6dlP
2x9Xf3ifvdB4Lav7zSjX3TNB+1OxLCYtxlCLDPdIHgSX5bz00KRsRPQn2o/hSBtK
4BzZckiz7ZzUFZUQb1lzqccAPLMM28JCEgWFJPHRXQHIq6cMLNm/z6JlkGzNwl6m
vdVieTlZ9dwwwGvgMk3lmGUYUO8NUyEi5n2sY72xAs3+2Tep2T4VHn0i9CYOsA2A
k5/8BMbjf0ghkzhf2MkLBhIwHuaI6TKClvRtoRdTwceJhnkyPQ==
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrw7Z1kk4+P1An
ZsgO2EM18MriCbiO2EinAWkkVCatZ9nJDLYiIQKcbAeVvxPalkDDouHIxsoQ95qL
I0DHO2QlBq/Jb+3JHNYmJUjn+A+Rafc8VJaIlzu2k3Z2BiU7OERrbT1Im8KoJL+d
ytpS3yj8qNbwXXnmaphn2rAPna0nIqNu3MjAbco7n4StxYaIyzvukaSKZvV/2A1D
4lyUbJUnZZSFd4VToLyF3PUmWwOphptpnNLQRK9eEaGy1xFRT8rN01h7/t1fDi+b
HJC/NvIlqP5PkgsIySBTo494R+zAQXWpfIQh0heUPilIyUbNDcwOS7nrh7V5tHnU
HCR8fC17AgMBAAECggEAeYkN/br8Kgdai8aqH/bd97jdlXsTX9+h6KmS3+W7SE+H
Rj78UMHSuyOlaku9nJlcUhFaeVpPeBn6/CCBoXdgsOI+V+Ye9oK09GDFaX2YZmf4
THP938BCvDkzROesSG7T2r988XdlENyPyPLT8Hd+5OgCzikWK/eYx0Nx+Fq1Pk6W
U7TSap7YFlXlP7vAxogOng1eIXQfWLWnS4ZYQALLWYlJbSwvnA2caEqM68df9M1z
X974ZAVKYKftzc506s6Mrjaw0RBM/fXKUGAUsip5Aw/4QCzICqU71taolnwVl3dL
tHFib7HX55ge5N0IBYIKzjj496ceUegnLNSaYmnPQQKBgQDj+5qjsBXkPuS0FABS
yEHLf1xIrr2pVRnctlCmN7s/PIAL/depRBcRST2ZRQeek4LIgbDZ++nBSCPc2fMj
TrVYq/b7VK7aCs0uDuvJT2ScVm6QHkoc780XNoAo5QgwARksW53N7ysbXkG/YaSj
xDlcQ2dmjZNq1frEJBr3bXkMqQKBgQDA33dfm2khNJZFXOJIHxaxeilNBndowknF
lHMm1hWXvjh66dwS3hxhj03pqF+33xxP6BA6y32sn30dFBduw9kvN/2gCsxTMDNE
8/EncC31i1eEQH1vK/fe02nvR8SbI9NcyD53emsOWsZi/FevrHK+LudgopA/ddMk
fbPN0rn7gwKBgGz9pQEqNl0G4Eli4oCw8ht6SMEKoOtqHtIQat/79s2Ve9W/xjFK
twhxjjxO7wSVmsmGjui3cRoRBewYWg+AGlxI4etnoavlzA9/3KNCDGRdQcAuatoI
nnDBgmWKkO56J/G33upLs70Cw8XdxVrxfUaphq5Vcqt5nsfURvwQ3vT5AoGAJ4qo
8pTuDLy3Qik0ywx0npYo+X2l5XhPn447vW6Oprl84tYnJEcdEnNKyeiXFx9Ksqcl
DKjDbyyTfe6sjyzfzepwuOr90OBE4pIQksFQ6tJScu61yKD/BFPbmA7io9vIbXEw
PVZ/tEWv/oM1hvKX453CGfG6GQiS7RxITJ4zOvkCgYEAgaMK2j02vmWHuDWvCsJC
XS8PMuVsc3cqwyhqyal4s9XGMHqsbeura3OI/LObg7inoSlSyD3Eax0MdlbwywmE
Qq8FzCgw9gNu3BQPZgfKSfc/zvdAjlaVtLhW6ztoK1kWgmcnRXr5YNorNK99HHTL
a4T40G2WVNTtlTOarEvYrP0=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,2 @@
swaybg -i ~/.config/labwc/iot-bg.png -m fit >/dev/null 2>&1 &
wtype -M alt -M logo h -m alt -m logo

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<labwc_config>
<touch deviceName="" mapToOutput="HDMI-A-1" mouseEmulation="no"/>
<keyboard>
<keybind key="A-W-h">
<action name="HideCursor" />
<action name="WarpCursor" x="-1" y="-1" />
</keybind>
</keyboard>
</labwc_config>

View file

@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
width: 100vw;
height: 100vh;
font-family: system-ui;
background-color: #f1f1f1;
color: #212529;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
.header a {
color: white;
background-color: #714b67;
max-width: 100px;
padding: 8px 16px;
border-radius: 4px;
text-decoration: none;
font-weight: 500;
}
.header a:hover {
background-color: #52374B;
color: white;
}
h1 {
margin-right: auto;
}
h2 {
color: #6c757d;
font-weight: normal;
margin-top: 0;
}
a {
font-weight: bold;
color: #714b67;
}
a:hover {
color: blue;
}
.container {
max-width: 600px;
padding: 10px 20px;
background: #ffffff;
border-radius: 8px;
}
.footer {
display: flex;
gap: 1rem;
margin-top: 12px;
}
</style>
<title>IoT Box is down</title>
</head>
<body>
<div class="container">
<div class="header">
<h1>IoT Box is down</h1>
<a href="/odoo-logs/odoo/odoo-server.log">Download Odoo logs</a>
<a href="/odoo-logs/nginx/access.log">Download Nginx logs</a>
</div>
<h2>(502 Bad Gateway)</h2>
<p>The IoT Box received the request but was not able to handle it. You can try to refresh the page to see if the request can now be handled.</p>
<p>If the error persists for more than 5 minutes:</p>
<ol>
<li>Force restart the IoT Box by unplugging the IoT power supply then plugging it in again</li>
<li>Re-flash the SD card of the IoT Box, see:
<a href="https://www.odoo.com/documentation/latest/applications/general/iot/iot_advanced/updating_iot.html#iot-updating-iot-image-code" target="_blank">documentation</a>
</li>
</ol>
</div>
<div class="footer">
<a target="_blank" href="https://www.odoo.com/help">Help</a>
<a target="_blank" href="https://www.odoo.com/documentation/latest/applications/productivity/iot.html">Documentation</a>
</div>
</body>
</html>

View file

@ -0,0 +1,10 @@
# KEYBOARD CONFIGURATION FILE
# Consult the keyboard(5) manual page.
XKBMODEL="pc105"
XKBLAYOUT="us"
XKBVARIANT=""
XKBOPTIONS=""
BACKSPACE="guess"

View file

@ -0,0 +1,3 @@
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=en_US.UTF-8

View file

@ -0,0 +1,231 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
# Recommends: antiword, graphviz, ghostscript, python-gevent, poppler-utils
export DEBIAN_FRONTEND=noninteractive
# single-user mode, appropriate for chroot environment
# explicitly setting the runlevel prevents warnings after installing packages
export RUNLEVEL=1
# Unset lang variables to prevent locale settings leaking from host
unset "${!LC_@}"
unset "${!LANG@}"
# set locale to en_US
echo "set locale to en_US"
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
dpkg-reconfigure locales
# Aliases
echo "alias ll='ls -al'" | tee -a ~/.bashrc /home/pi/.bashrc
echo "alias odoo='sudo systemctl stop odoo; sudo -u odoo /usr/bin/python3 /home/pi/odoo/odoo-bin --config /home/pi/odoo.conf'" | tee -a ~/.bashrc /home/pi/.bashrc
echo "alias odoo_logs='less -R +F /var/log/odoo/odoo-server.log'" | tee -a ~/.bashrc /home/pi/.bashrc
echo "alias odoo_conf='cat /home/pi/odoo.conf'" | tee -a ~/.bashrc /home/pi/.bashrc
echo "alias install='sudo chroot /root_bypass_ramdisks/'" | tee -a ~/.bashrc /home/pi/.bashrc
echo "alias blackbox='ls /dev/serial/by-path/'" | tee -a ~/.bashrc /home/pi/.bashrc
echo "alias nano='sudo -u odoo nano -l'" | tee -a /home/pi/.bashrc
echo "alias vim='sudo -u odoo vim -u /home/pi/.vimrc'" | tee -a /home/pi/.bashrc
echo "alias odoo_luxe='printf \" ______\n< Luxe >\n ------\n \\ ^__^\n \\ (oo)\\_______\n (__)\\ )\\/\\ \n ||----w |\n || ||\n\"'" | tee -a ~/.bashrc /home/pi/.bashrc
echo "alias odoo_start='sudo systemctl start odoo'" >> /home/pi/.bashrc
echo "alias odoo_stop='sudo systemctl stop odoo'" >> /home/pi/.bashrc
echo "alias odoo_restart='sudo systemctl restart odoo'" >> /home/pi/.bashrc
echo "
odoo_help() {
echo '-------------------------------'
echo ' Welcome to Odoo IoT Box tools'
echo '-------------------------------'
echo ''
echo 'odoo Starts/Restarts Odoo server manually (not through odoo.service)'
echo 'odoo_logs Displays Odoo server logs in real time'
echo 'odoo_conf Displays Odoo configuration file content'
echo 'install Bypasses ramdisks to allow package installation'
echo 'blackbox Lists all serial connected devices'
echo 'odoo_start Starts Odoo service'
echo 'odoo_stop Stops Odoo service'
echo 'odoo_restart Restarts Odoo service'
echo 'odoo_dev <branch> Resets Odoo on the specified branch from odoo-dev repository'
echo 'odoo_origin <branch> Resets Odoo on the specified branch from the odoo repository'
echo 'devtools Enables/Disables specific functions for development (more help with devtools help)'
echo ''
echo 'Odoo IoT online help: <https://www.odoo.com/documentation/latest/applications/general/iot.html>'
}
odoo_dev() {
if [ -z \"\$1\" ]; then
odoo_help
return
fi
pwd=\$(pwd)
cd /home/pi/odoo
sudo -u odoo git remote add dev https://github.com/odoo-dev/odoo.git
sudo -u odoo git fetch dev \$1 --depth=1 --prune
sudo -u odoo git reset --hard FETCH_HEAD
sudo -u odoo git branch -m \$1
sudo chroot /root_bypass_ramdisks /bin/bash -c \"export DEBIAN_FRONTEND=noninteractive && xargs apt-get -y -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" install < /home/pi/odoo/addons/iot_box_image/configuration/packages.txt\"
sudo -u odoo pip3 install -r /home/pi/odoo/addons/iot_box_image/configuration/requirements.txt --break-system-package
cd \$pwd
}
odoo_origin() {
if [ -z \"\$1\" ]; then
odoo_help
return
fi
pwd=\$(pwd)
cd /home/pi/odoo
sudo -u odoo git remote set-url origin https://github.com/odoo/odoo.git # ensure odoo repository
sudo -u odoo git fetch origin \$1 --depth=1 --prune
sudo -u odoo git reset --hard FETCH_HEAD
sudo -u odoo git branch -m \$1
sudo chroot /root_bypass_ramdisks /bin/bash -c \"export DEBIAN_FRONTEND=noninteractive && xargs apt-get -y -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" install < /home/pi/odoo/addons/iot_box_image/configuration/packages.txt\"
sudo -u odoo pip3 install -r /home/pi/odoo/addons/iot_box_image/configuration/requirements.txt --break-system-package
cd \$pwd
}
pip() {
if [[ -z \"\$1\" || -z \"\$2\" ]]; then
odoo_help
return 1
fi
additional_arg=\"\"
if [ \"\$1\" == \"install\" ]; then
additional_arg=\"--user\"
fi
pip3 \"\$1\" \"\$2\" --break-system-package \$additional_arg
}
devtools() {
help_message() {
echo 'Usage: devtools <enable/disable> <general/actions> [action name]'
echo ''
echo 'Only provide an action name if you want to enable/disable a specific device action.'
echo 'If no action name is provided, all actions will be enabled/disabled.'
echo 'To enable/disable multiple actions, enclose them in quotes separated by commas.'
}
case \"\$1\" in
enable|disable)
case \"\$2\" in
general|actions|longpolling)
if ! grep -q '^\[devtools\]' /home/pi/odoo.conf; then
sudo -u odoo bash -c \"printf '\n[devtools]\n' >> /home/pi/odoo.conf\"
fi
if [ \"\$1\" == \"disable\" ]; then
value=\"\${3:-*}\" # Default to '*' if no action name is provided
devtools enable \"\$2\" # Remove action/general/longpolling from conf to avoid duplicate keys
sudo sed -i \"/^\[devtools\]/a\\\\\$2 = \$value\" /home/pi/odoo.conf
elif [ \"\$1\" == \"enable\" ]; then
sudo sed -i \"/\[devtools\]/,/\[/{/\$2 =/d}\" /home/pi/odoo.conf
fi
;;
*)
help_message
return 1
;;
esac
;;
*)
help_message
return 1
;;
esac
}
" | tee -a ~/.bashrc /home/pi/.bashrc
# Change default hostname from 'raspberrypi' to 'iotbox'
echo iotbox | tee /etc/hostname
sed -i 's/\braspberrypi/iotbox/g' /etc/hosts
apt-get update
# At the first start it is necessary to configure a password
# This will be modified by a unique password on the first start of Odoo
password="$(openssl rand -base64 12)"
echo "pi:${password}" | chpasswd
echo TrustedUserCAKeys /etc/ssh/ca.pub >> /etc/ssh/sshd_config
# Prevent Wi-Fi blocking
apt-get -y remove rfkill
echo "Acquire::Retries "16";" > /etc/apt/apt.conf.d/99acquire-retries
# KEEP OWN CONFIG FILES DURING PACKAGE CONFIGURATION
# http://serverfault.com/questions/259226/automatically-keep-current-version-of-config-files-when-apt-get-install
xargs apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install < /home/pi/odoo/addons/iot_box_image/configuration/packages.txt
apt-get -y autoremove
apt-get clean
localepurge
rm -rfv /usr/share/doc
# Remove the default nginx website, we have our own config in /etc/nginx/conf.d/
rm /etc/nginx/sites-enabled/default
pip3 install -r /home/pi/odoo/addons/iot_box_image/configuration/requirements.txt --break-system-package
# Create Odoo user for odoo service and disable password login
adduser --disabled-password --gecos "" --shell /usr/sbin/nologin odoo
# odoo user doesn't need to type its password to run sudo commands
cp /etc/sudoers.d/010_pi-nopasswd /etc/sudoers.d/010_odoo-nopasswd
sed -i 's/pi/odoo/g' /etc/sudoers.d/010_odoo-nopasswd
# copy the odoo.conf file to the overwrite directory
mv -v "/home/pi/odoo/addons/iot_box_image/configuration/odoo.conf" "/home/pi/"
chown odoo:odoo "/home/pi/odoo.conf"
groupadd usbusers
usermod -a -G usbusers odoo
usermod -a -G video odoo
usermod -a -G render odoo
usermod -a -G lp odoo
usermod -a -G input odoo
usermod -a -G dialout odoo
usermod -a -G pi odoo
mkdir -v /var/log/odoo
chown odoo:odoo /var/log/odoo
chown odoo:odoo -R /home/pi/odoo/
# logrotate is very picky when it comes to file permissions
chown -R root:root /etc/logrotate.d/
chmod -R 644 /etc/logrotate.d/
chown root:root /etc/logrotate.conf
chmod 644 /etc/logrotate.conf
update-rc.d -f hostapd remove
update-rc.d -f nginx remove
update-rc.d -f dnsmasq remove
systemctl enable ramdisks.service
systemctl disable dphys-swapfile.service
systemctl enable ssh
systemctl set-default graphical.target
systemctl disable getty@tty1.service
systemctl disable systemd-timesyncd.service
systemctl unmask hostapd.service
systemctl disable hostapd.service
systemctl disable cups-browsed.service
systemctl enable labwc.service
systemctl enable odoo.service
systemctl enable odoo-led-manager.service
systemctl enable odoo-ngrok.service
# create dirs for ramdisks
create_ramdisk_dir () {
mkdir -v "${1}_ram"
}
create_ramdisk_dir "/var"
create_ramdisk_dir "/etc"
create_ramdisk_dir "/tmp"
mkdir -v /root_bypass_ramdisks
echo ""
echo "--- DEFAULT PASSWORD: ${password} ---"
echo ""

View file

@ -0,0 +1,17 @@
# see "man logrotate" for details
# rotate log files daily
daily
# keep 3 days worth of backlogs
rotate 3
# create new (empty) log files after rotating old ones
create
# uncomment this if you want your log files compressed
#compress
# packages drop log rotation information into this directory
include /etc/logrotate.d
# system-specific logs may be configured here

View file

@ -0,0 +1,6 @@
/var/log/odoo/*.log {
size 100M
copytruncate
missingok
notifempty
}

View file

@ -0,0 +1,4 @@
nameserver 1.1.1.1
nameserver 1.0.0.1
nameserver 8.8.8.8
nameserver 8.8.4.4

View file

@ -0,0 +1,16 @@
[Unit]
Description=labwc wayland compositor
Documentation=man:labwc(1)
After=graphical.target systemd-user-sessions.service
[Service]
User=odoo
ExecStart=/usr/bin/labwc
Environment="XDG_RUNTIME_DIR=/run/odoo"
Environment="XDG_CACHE_HOME=/run/odoo"
Environment="WLR_LIBINPUT_NO_DEVICES=1"
Restart=always
Type=simple
[Install]
WantedBy=graphical.target

View file

@ -0,0 +1,12 @@
[Unit]
Description=Odoo LED Status Indicator: makes LEDs blink based on Odoo status
[Service]
Type=simple
User=root
ExecStart=/home/pi/odoo/addons/iot_box_image/configuration/led_manager.sh
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,14 @@
[Unit]
Description=Odoo Service to ensure ngrok is running
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
User=root
ExecStart=/usr/bin/ngrok tcp 22 --config /home/pi/ngrok.yml
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,30 @@
[Unit]
Description=Odoo IoT Box service
After=cups.socket network-online.target NetworkManager.service rc-local.service
Wants=network-online.target
StartLimitIntervalSec=0
[Service]
User=odoo
Group=odoo
Environment="LIBCAMERA_LOG_LEVELS=3"
Environment="XDG_RUNTIME_DIR=/run/odoo"
Environment="XDG_CACHE_HOME=/run/odoo"
Environment="ODOO_PY_COLORS=True"
ExecStartPre=sudo /bin/mkdir -p /run/odoo
ExecStartPre=sudo /bin/chown odoo:odoo /run/odoo
ExecStartPre=sudo timedatectl set-ntp true
ExecStart=/usr/bin/python3 /home/pi/odoo/odoo-bin --config /home/pi/odoo.conf
Restart=on-failure
RestartSec=10s
StandardOutput=null
StandardError=append:/var/log/odoo/odoo-server.log
[Install]
WantedBy=multi-user.target
# Tip: don't forget to 'systemctl disable' then re 'enable' service if you update the 'WantedBy' line
# reason is that 'enable' creates a symlink in /etc/systemd/system/multi-user.target.wants/ pointing to this file which does
# not get updated if you only 'systemctl daemon-reload'
# Documentation: https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html

View file

@ -0,0 +1,12 @@
[Unit]
Description=ramdisks
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=basic.target
[Service]
Type=oneshot
ExecStart=/home/pi/odoo/addons/iot_box_image/configuration/setup_ramdisks.sh
[Install]
WantedBy=basic.target

View file

@ -0,0 +1,2 @@
SUBSYSTEM=="usb", GROUP="usbusers", MODE="0660"
SUBSYSTEMS=="usb", GROUP="usbusers", MODE="0660"

View file

@ -0,0 +1,2 @@
SUBSYSTEM=="input", GROUP="input", MODE="0660"
KERNEL=="tty[0-9]*", GROUP="tty", MODE="0660"

View file

@ -0,0 +1,31 @@
filetype on
set all&
set autoindent
set backspace=2
set nobackup
set nocompatible
set noerrorbells
set noexpandtab
set expandtab
set number
set hidden
set history=500
set hlsearch
set ignorecase
set laststatus=2
set modeline
set mouse=a
set ruler
set shiftwidth=4
set scrolloff=5
set showcmd
set showmode
set tabstop=4
set textwidth=0
set visualbell
set t_vb=
set wrap
set list
set listchars=tab:~.,trail:.,extends:>,precedes:<
set viminfo="NONE"
syntax on

View file

@ -0,0 +1,43 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from functools import wraps
import requests
import logging
from . import server_logger
from . import connection_manager
from . import controllers
from . import driver
from . import event_manager
from . import exception_logger
from . import http
from . import interface
from . import main
from . import tools
from . import websocket_client
from . import webrtc_client
_logger = logging.getLogger(__name__)
_logger.warning("==== Starting Odoo ====")
_get = requests.get
_post = requests.post
def set_default_options(func):
@wraps(func)
def wrapper(*args, **kwargs):
headers = kwargs.pop('headers', None) or {}
verify = kwargs.pop('verify', False)
headers['User-Agent'] = 'OdooIoTBox/1.0'
server_url = tools.helpers.get_odoo_server_url()
db_name = tools.helpers.get_conf('db_name')
if server_url and db_name and args[0].startswith(server_url) and '/web/login?db=' not in args[0]:
headers['X-Odoo-Database'] = db_name
return func(*args, headers=headers, verify=verify, **kwargs)
return wrapper
requests.get = set_default_options(_get)
requests.post = set_default_options(_post)

View file

@ -0,0 +1,27 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Hardware Proxy',
'category': 'Hidden',
'sequence': 6,
'summary': 'Connect the Web Client to Hardware Peripherals',
'website': 'https://www.odoo.com/app/iot',
'description': """
Hardware Poxy
=============
This module allows you to remotely use peripherals connected to this server.
This modules only contains the enabling framework. The actual devices drivers
are found in other modules that must be installed separately.
""",
'assets': {
'iot_drivers.assets': [ # dummy asset name to make sure it does not load outside of IoT homepage
'iot_drivers/static/**/*',
],
},
'installable': False,
'author': 'Odoo S.A.',
'license': 'LGPL-3',
}

View file

@ -0,0 +1,128 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import subprocess
from enum import Enum
from odoo.addons.iot_drivers.tools import helpers
_logger = logging.getLogger(__name__)
CHROMIUM_ARGS = [
'--incognito',
'--disable-infobars',
'--noerrdialogs',
'--no-first-run',
'--bwsi', # Use chromium without signing in
'--disable-extensions', # Disable extensions as they fill up /tmp
'--disk-cache-dir=/dev/null', # Disable disk cache
'--disk-cache-size=1', # Set disk cache size to 1 byte
'--log-level=3', # Reduce amount of logs
]
class BrowserState(Enum):
"""Enum to represent the state of the browser"""
NORMAL = 'normal'
KIOSK = 'kiosk'
FULLSCREEN = 'fullscreen'
class Browser:
"""Methods to interact with a browser"""
def __init__(self, url, _x_screen, env):
"""
:param url: URL to open in the browser
:param _x_screen: X screen number
:param env: Environment variables (e.g. os.environ.copy())
:param kiosk: Whether the browser should be in kiosk mode
"""
self.url = url
self.browser = 'chromium-browser'
self.browser_process_name = 'chromium'
self.state = BrowserState.NORMAL
self._x_screen = _x_screen
self._set_environment(env)
self.open_browser()
def _set_environment(self, env):
"""
Set the environment variables for the browser
:param env: Environment variables (os.environ.copy())
"""
self.env = env
self.env['DISPLAY'] = f':0.{self._x_screen}'
self.env['XAUTHORITY'] = '/run/lightdm/pi/xauthority'
for key in ['HOME', 'XDG_RUNTIME_DIR', 'XDG_CACHE_HOME']:
self.env[key] = '/tmp/' + self._x_screen
def open_browser(self, url=None, state=BrowserState.FULLSCREEN):
"""
open the browser with the given URL, or reopen it if it is already open
:param url: URL to open in the browser
:param state: State of the browser (normal, kiosk, fullscreen)
"""
self.url = url or self.url
self.state = state
# Reopen to take new url or additional args into account
self.close_browser()
browser_args = list(CHROMIUM_ARGS)
if state == BrowserState.KIOSK:
browser_args.extend(["--kiosk", "--touch-events"])
elif state == BrowserState.FULLSCREEN:
browser_args.append("--start-fullscreen")
subprocess.Popen(
[
self.browser,
self.url,
*browser_args,
],
env=self.env,
)
helpers.save_browser_state(url=self.url)
def close_browser(self):
"""close the browser"""
# Kill browser instance (can't `instance.pkill()` as we can't keep the instance after Odoo service restarts)
# We need to terminate it because Odoo will create a new instance each time it is restarted.
subprocess.run(['pkill', self.browser_process_name], check=False)
def xdotool_keystroke(self, keystroke):
"""
Execute a keystroke using xdotool
:param keystroke: Keystroke to execute
"""
subprocess.run([
'xdotool', 'search',
'--sync', '--onlyvisible',
'--screen', self._x_screen,
'--class', self.browser_process_name,
'key', keystroke,
], check=False)
def xdotool_type(self, text):
"""
Type text using xdotool
:param text: Text to type
"""
subprocess.run([
'xdotool', 'search',
'--sync', '--onlyvisible',
'--screen', self._x_screen,
'--class', self.browser_process_name,
'type', text,
], check=False)
def refresh(self):
"""Refresh the current tab"""
self.xdotool_keystroke('ctrl+r')
def disable_kiosk_mode(self):
"""Removes arguments to chromium-browser cli to open it without kiosk mode"""
if self.state == BrowserState.KIOSK:
self.open_browser(state=BrowserState.FULLSCREEN)

View file

@ -0,0 +1,28 @@
import secrets
import sys
import textwrap
from passlib.hash import pbkdf2_sha512
from odoo.cli import Command
from odoo.tools import config
class GenProxyToken(Command):
""" Generate and (re)set proxy access token in config file """
def generate_token(self, length=16):
token = secrets.token_hex(int(length / 2))
split_size = int(length / 4)
return '-'.join(textwrap.wrap(token, split_size))
def run(self, cmdargs):
self.parser.add_argument('-c', '--config', type=str, help="Specify an alternate config file")
self.parser.add_argument('--token-length', type=int, help="Token Length", default=16)
args, _ = self.parser.parse_known_args()
if args.config:
config.rcfile = args.config
token = self.generate_token(length=args.token_length)
config['proxy_access_token'] = pbkdf2_sha512.hash(token)
config.save()
sys.stdout.write(f'{token}\n')

View file

@ -0,0 +1,115 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import requests
from threading import Thread
import time
from odoo.addons.iot_drivers.main import iot_devices, manager
from odoo.addons.iot_drivers.tools import helpers, upgrade, wifi
from odoo.addons.iot_drivers.tools.system import IS_RPI, IS_TEST
_logger = logging.getLogger(__name__)
class ConnectionManager(Thread):
daemon = True
def __init__(self):
super().__init__()
self.pairing_code = False
self.pairing_uuid = False
self.pairing_code_expired = False
self.new_database_url = False
self.iot_box_registered = False
self.n_times_polled = -1
requests.packages.urllib3.disable_warnings()
def _register_iot_box(self):
""" This method is called to register the IoT Box on odoo.com and get a pairing code"""
req = self._call_iot_proxy()
if all(key in req for key in ['pairing_code', 'pairing_uuid']):
self.pairing_code = req['pairing_code']
self.pairing_uuid = req['pairing_uuid']
if IS_RPI:
self._try_print_pairing_code()
self.iot_box_registered = True
def _get_next_polling_interval(self):
# To avoid spamming odoo.com with requests we gradually space out the requests
# e.g If the pairing code is valid for 2 hours this would lead to max 329 requests
# Starting with 15 seconds and ending with 40s interval, staying under 20s for 50 min
self.n_times_polled += 1
return 14 + 1.01 ** self.n_times_polled
def run(self):
# Double loop is needed in case the IoT Box isn't initially connected to the internet
while True:
while self._should_poll_to_connect_database():
if not self.iot_box_registered:
self._register_iot_box()
self._poll_pairing_result()
time.sleep(self._get_next_polling_interval())
time.sleep(5)
def _should_poll_to_connect_database(self):
return (
not helpers.get_odoo_server_url() and
helpers.get_ip() and
not (IS_RPI and wifi.is_access_point()) and
not self.pairing_code_expired
)
def _call_iot_proxy(self):
data = {
'params': {
'pairing_code': self.pairing_code,
'pairing_uuid': self.pairing_uuid,
'serial_number': helpers.get_identifier(),
}
}
try:
req = requests.post(
'https://iot-proxy.odoo.com/odoo-enterprise/iot/connect-box',
json=data,
timeout=5,
)
req.raise_for_status()
if req.json().get('error') == 'expired':
self.pairing_code_expired = True
self.pairing_code = False
self.pairing_uuid = False
return req.json().get('result', {})
except Exception:
_logger.exception('Could not reach iot-proxy.odoo.com')
return {}
def _poll_pairing_result(self):
result = self._call_iot_proxy()
if all(key in result for key in ['url', 'token', 'db_uuid', 'enterprise_code']):
self._connect_to_server(result['url'], result['token'], result['db_uuid'], result['enterprise_code'])
def _connect_to_server(self, url, token, db_uuid, enterprise_code):
self.new_database_url = url
# Save DB URL and token
helpers.save_conf_server(url, token, db_uuid, enterprise_code)
# Send already detected devices and IoT Box info to the database
manager._send_all_devices()
# Switch git branch before restarting, this avoids restarting twice
upgrade.check_git_branch()
# Restart to get a certificate, load the IoT handlers...
helpers.odoo_restart(2)
def _try_print_pairing_code(self):
printers = [device for device in iot_devices.values() if device.device_type == 'printer' and device.connected_by_usb and device.device_subtype in ['receipt_printer', 'label_printer']]
for printer in printers:
printer.print_status()
connection_manager = ConnectionManager()
if not IS_TEST:
connection_manager.start()

View file

@ -0,0 +1,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import driver
from . import proxy
from . import homepage

Some files were not shown because too many files have changed in this diff Show more