[IMP] hw_*: Add possibility to run IoT in Windows

Currently it is only possible to run the modules
for the IoT Box on Raspios.
The modifications brought by this commit brings the possibility
that the various hw_* modules can be executed whatever the OS
of the hardware (Linux or Windows).
If the modules should be versioned according to the OS,
the file will be renamed so that the end of
the file includes *_L (for linux) or *_W (for windows)

closes odoo/odoo#107350

X-original-commit: b7b8809ea8b7fe4ac8b24f43fe3f3a5e38a66696
Related: odoo/enterprise#34713
Signed-off-by: Quentin Lejeune (qle) <qle@odoo.com>
This commit is contained in:
lejeune quentin
2022-12-07 10:02:23 +01:00
parent 355b004775
commit b49e7e48f5
27 changed files with 469 additions and 225 deletions
+2 -5
View File
@@ -54,15 +54,12 @@ class ConnectionManager(Thread):
_logger.error('A error encountered : %s ' % e)
def _connect_to_server(self, url, token, db_uuid, enterprise_code):
if db_uuid and enterprise_code:
helpers.add_credential(db_uuid, enterprise_code)
# Save DB URL and token
subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_server.sh'), url, '', token, 'noreboot'])
helpers.save_conf_server(url, token, db_uuid, enterprise_code)
# Notify the DB, so that the kanban view already shows the IoT Box
manager.send_alldevices()
# Restart to checkout the git branch, get a certificate, load the IoT handlers...
subprocess.check_call(["sudo", "service", "odoo", "restart"])
helpers.odoo_restart(2)
def _refresh_displays(self):
"""Refresh all displays to hide the pairing code"""
-30
View File
@@ -65,36 +65,6 @@ class DriverController(http.Controller):
req['result']['session_id'] = req['session_id']
return req['result']
@http.route('/hw_drivers/box/connect', type='http', auth='none', cors='*', csrf=False, save_session=False)
def connect_box(self, token):
"""
This route is called when we want that a IoT Box will be connected to a Odoo DB
token is a base 64 encoded string and have 2 argument separate by |
1 - url of odoo DB
2 - token. This token will be compared to the token of Odoo. He have 1 hour lifetime
"""
server = helpers.get_odoo_server_url()
image = get_resource_path('hw_drivers', 'static/img', 'False.jpg')
if not server:
credential = b64decode(token).decode('utf-8').split('|')
url = credential[0]
token = credential[1]
if len(credential) > 2:
# IoT Box send token with db_uuid and enterprise_code only since V13
db_uuid = credential[2]
enterprise_code = credential[3]
helpers.add_credential(db_uuid, enterprise_code)
try:
subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_server.sh'), url, '', token, 'noreboot'])
manager.send_alldevices()
image = get_resource_path('hw_drivers', 'static/img', 'True.jpg')
helpers.odoo_restart(3)
except subprocess.CalledProcessError as e:
_logger.error('A error encountered : %s ' % e.output)
if os.path.isfile(image):
with open(image, 'rb') as f:
return f.read()
@http.route('/hw_drivers/download_logs', type='http', auth='none', cors='*', csrf=False, save_session=False)
def download_logs(self):
"""
@@ -19,7 +19,7 @@ from odoo.addons.hw_drivers.connection_manager import connection_manager
from odoo.addons.hw_drivers.controllers.proxy import proxy_drivers
from odoo.addons.hw_drivers.driver import Driver
from odoo.addons.hw_drivers.event_manager import event_manager
from odoo.addons.hw_drivers.iot_handlers.interfaces.PrinterInterface import PPDs, conn, cups_lock
from odoo.addons.hw_drivers.iot_handlers.interfaces.PrinterInterface_L import PPDs, conn, cups_lock
from odoo.addons.hw_drivers.main import iot_devices
from odoo.addons.hw_drivers.tools import helpers
@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from PIL import Image, ImageOps
import logging
from base64 import b64decode
import io
import win32print
import ghostscript
from odoo.addons.hw_drivers.controllers.proxy import proxy_drivers
from odoo.addons.hw_drivers.driver import Driver
from odoo.addons.hw_drivers.event_manager import event_manager
from odoo.addons.hw_drivers.main import iot_devices
from odoo.addons.hw_drivers.tools import helpers
_logger = logging.getLogger(__name__)
RECEIPT_PRINTER_COMMANDS = {
'star': {
'center': b'\x1b\x1d\x61\x01', # ESC GS a n
'cut': b'\x1b\x64\x02', # ESC d n
'title': b'\x1b\x69\x01\x01%s\x1b\x69\x00\x00', # ESC i n1 n2
'drawers': [b'\x07', b'\x1a'] # BEL & SUB
},
'escpos': {
'center': b'\x1b\x61\x01', # ESC a n
'cut': b'\x1d\x56\x41\n', # GS V m
'title': b'\x1b\x21\x30%s\x1b\x21\x00', # ESC ! n
'drawers': [b'\x1b\x3d\x01', b'\x1b\x70\x00\x19\x19', b'\x1b\x70\x01\x19\x19'] # ESC = n then ESC p m t1 t2
}
}
class PrinterDriver(Driver):
connection_type = 'printer'
def __init__(self, identifier, device):
super().__init__(identifier, device)
self.device_type = 'printer'
self.device_connection = 'network'
self.device_name = device.get('identifier')
self.printer_handle = device.get('printer_handle')
self.state = {
'status': 'connecting',
'message': 'Connecting to printer',
'reason': None,
}
self.send_status()
self._actions.update({
'cashbox': self.open_cashbox,
'print_receipt': self.print_receipt,
'': self._action_default,
})
self.receipt_protocol = 'escpos'
@classmethod
def supported(cls, device):
return True
@classmethod
def get_status(cls):
status = 'connected' if any(iot_devices[d].device_type == "printer" and iot_devices[d].device_connection == 'direct' for d in iot_devices) else 'disconnected'
return {'status': status, 'messages': ''}
def disconnect(self):
self.update_status('disconnected', 'Printer was disconnected')
super(PrinterDriver, self).disconnect()
def update_status(self, status, message, reason=None):
"""Updates the state of the current printer.
Args:
status (str): The new value of the status
message (str): A comprehensive message describing the status
reason (str): The reason fo the current status
"""
if self.state['status'] != status or self.state['reason'] != reason:
self.state = {
'status': status,
'message': message,
'reason': reason,
}
self.send_status()
def send_status(self):
""" Sends the current status of the printer to the connected Odoo instance.
"""
self.data = {
'value': '',
'state': self.state,
}
event_manager.device_changed(self)
def print_raw(self, data):
win32print.StartDocPrinter(self.printer_handle, 1, ('', None, "RAW"))
win32print.StartPagePrinter(self.printer_handle)
win32print.WritePrinter(self.printer_handle, data)
win32print.EndPagePrinter(self.printer_handle)
win32print.EndDocPrinter(self.printer_handle)
def print_report(self, data):
helpers.write_file('document.pdf', data, 'wb')
file_name = helpers.path_file('document.pdf')
printer = self.device_name
args = [
"-dPrinted", "-dBATCH", "-dNOSAFER", "-dNOPAUSE", "-dNOPROMPT"
"-q",
"-sDEVICE#mswinpr2",
f'-sOutputFile#%printer%{printer}',
f'{file_name}'
]
ghostscript.Ghostscript(*args)
def print_receipt(self, data):
receipt = b64decode(data['receipt'])
im = Image.open(io.BytesIO(receipt))
# Convert to greyscale then to black and white
im = im.convert("L")
im = ImageOps.invert(im)
im = im.convert("1")
print_command = getattr(self, 'format_%s' % self.receipt_protocol)(im)
self.print_raw(print_command)
def format_escpos(self, im):
width = int((im.width + 7) / 8)
raster_send = b'\x1d\x76\x30\x00'
max_slice_height = 255
raster_data = b''
dots = im.tobytes()
while dots:
im_slice = dots[:width*max_slice_height]
slice_height = int(len(im_slice) / width)
raster_data += raster_send + width.to_bytes(2, 'little') + slice_height.to_bytes(2, 'little') + im_slice
dots = dots[width*max_slice_height:]
return raster_data + RECEIPT_PRINTER_COMMANDS['escpos']['cut']
def open_cashbox(self, data):
"""Sends a signal to the current printer to open the connected cashbox."""
commands = RECEIPT_PRINTER_COMMANDS[self.receipt_protocol]
for drawer in commands['drawers']:
self.print_raw(drawer)
def _action_default(self, data):
self.print_report(b64decode(data['document']))
proxy_drivers['printer'] = PrinterDriver
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import win32print
from odoo.addons.hw_drivers.interface import Interface
class PrinterInterface(Interface):
_loop_delay = 30
connection_type = 'printer'
def get_devices(self):
printer_devices = {}
printers = win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL)
for printer in printers:
identifier = printer[2]
handle_printer = win32print.OpenPrinter(identifier)
win32print.GetPrinter(handle_printer, 2)
printer_devices[identifier] = {
'identifier': identifier,
'printer_handle': handle_printer,
}
return printer_devices
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from glob import glob
import serial.tools.list_ports
from odoo.addons.hw_drivers.interface import Interface
@@ -11,8 +11,8 @@ class SerialInterface(Interface):
def get_devices(self):
serial_devices = {}
for identifier in glob('/dev/serial/by-path/*'):
serial_devices[identifier] = {
'identifier': identifier
for port in serial.tools.list_ports.comports():
serial_devices[port.device] = {
'identifier': port.device
}
return serial_devices
+13 -6
View File
@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from traceback import format_exc
from dbus.mainloop.glib import DBusGMainLoop
import json
import platform
import logging
import socket
from threading import Thread
@@ -14,6 +13,12 @@ from odoo.addons.hw_drivers.tools import helpers
_logger = logging.getLogger(__name__)
try:
from dbus.mainloop.glib import DBusGMainLoop
except ImportError:
DBusGMainLoop = None
_logger.error('Could not import library dbus')
drivers = []
interfaces = {}
iot_devices = {}
@@ -72,7 +77,9 @@ class Manager(Thread):
Thread that will load interfaces and drivers and contact the odoo server with the updates
"""
helpers.check_git_branch()
helpers.start_nginx_server()
if platform.system() == 'Linux':
helpers.check_git_branch()
helpers.check_certificate()
# We first add the IoT Box to the connected DB because IoT handlers cannot be downloaded if
@@ -93,16 +100,16 @@ class Manager(Thread):
while 1:
try:
if iot_devices != self.previous_iot_devices:
self.send_alldevices()
self.previous_iot_devices = iot_devices.copy()
self.send_alldevices()
time.sleep(3)
except Exception:
# No matter what goes wrong, the Manager loop needs to keep running
_logger.error(format_exc())
# Must be started from main thread
DBusGMainLoop(set_as_default=True)
if DBusGMainLoop:
DBusGMainLoop(set_as_default=True)
manager = Manager()
manager.daemon = True
Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 B

+113 -78
View File
@@ -3,6 +3,7 @@
import datetime
from importlib import util
import platform
import io
import json
import logging
@@ -15,8 +16,9 @@ import urllib3
import zipfile
from threading import Thread
import time
import contextlib
from odoo import _, http
from odoo import _, http, service
from odoo.tools.func import lazy_property
from odoo.modules.module import get_resource_path
@@ -36,14 +38,36 @@ class IoTRestart(Thread):
def run(self):
time.sleep(self.delay)
subprocess.check_call(["sudo", "service", "odoo", "restart"])
service.server.restart()
if platform.system() == 'Windows':
writable = contextlib.nullcontext
elif platform.system() == 'Linux':
@contextlib.contextmanager
def writable():
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/"])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/"])
try:
yield
finally:
subprocess.call(["sudo", "mount", "-o", "remount,ro", "/"])
subprocess.call(["sudo", "mount", "-o", "remount,ro", "/root_bypass_ramdisks/"])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/etc/cups"])
def access_point():
return get_ip() == '10.11.12.1'
def add_credential(db_uuid, enterprise_code):
write_file('odoo-db-uuid.conf', db_uuid)
write_file('odoo-enterprise-code.conf', enterprise_code)
def start_nginx_server():
if platform.system() == 'Windows':
path_nginx = get_path_nginx()
if path_nginx:
os.chdir(path_nginx)
_logger.info('Start Nginx server: %s\\nginx.exe', path_nginx)
os.popen('nginx.exe')
os.chdir('..\\server')
elif platform.system() == 'Linux':
subprocess.check_call(["sudo", "service", "nginx", "restart"])
def check_certificate():
"""
@@ -51,7 +75,10 @@ def check_certificate():
"""
server = get_odoo_server_url()
if server:
path = Path('/etc/ssl/certs/nginx-cert.crt')
if platform.system() == 'Windows':
path = Path(get_path_nginx()).joinpath('conf/nginx-cert.crt')
elif platform.system() == 'Linux':
path = Path('/etc/ssl/certs/nginx-cert.crt')
if path.exists():
with path.open('r') as f:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
@@ -94,14 +121,12 @@ def check_git_branch():
local_branch = subprocess.check_output(git + ['symbolic-ref', '-q', '--short', 'HEAD']).decode('utf-8').rstrip()
if db_branch != local_branch:
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/"])
subprocess.check_call(["rm", "-rf", "/home/pi/odoo/addons/hw_drivers/iot_handlers/drivers/*"])
subprocess.check_call(["rm", "-rf", "/home/pi/odoo/addons/hw_drivers/iot_handlers/interfaces/*"])
subprocess.check_call(git + ['branch', '-m', db_branch])
subprocess.check_call(git + ['remote', 'set-branches', 'origin', db_branch])
os.system('/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/posbox_update.sh')
subprocess.call(["sudo", "mount", "-o", "remount,ro", "/"])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/etc/cups"])
with writable():
subprocess.check_call(["rm", "-rf", "/home/pi/odoo/addons/hw_drivers/iot_handlers/drivers/*"])
subprocess.check_call(["rm", "-rf", "/home/pi/odoo/addons/hw_drivers/iot_handlers/interfaces/*"])
subprocess.check_call(git + ['branch', '-m', db_branch])
subprocess.check_call(git + ['remote', 'set-branches', 'origin', db_branch])
os.system('/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/posbox_update.sh')
except Exception as e:
_logger.error('Could not reach configured server')
@@ -130,39 +155,37 @@ def check_image():
version = checkFile.get(valueLastest, 'Error').replace('iotboxv', '').replace('.zip', '').split('_')
return {'major': version[0], 'minor': version[1]}
def save_conf_server(url, token, db_uuid, enterprise_code):
"""
Save config to connect IoT to the server
"""
write_file('odoo-remote-server.conf', url)
write_file('token', token)
write_file('odoo-db-uuid.conf', db_uuid or '')
write_file('odoo-enterprise-code.conf', enterprise_code or '')
def get_img_name():
major, minor = get_version().split('.')
return 'iotboxv%s_%s.zip' % (major, minor)
def get_ip():
while True:
try:
return netifaces.ifaddresses('eth0')[netifaces.AF_INET][0]['addr']
except KeyError:
pass
try:
return netifaces.ifaddresses('wlan0')[netifaces.AF_INET][0]['addr']
except KeyError:
pass
_logger.warning("Couldn't get IP, sleeping and retrying.")
time.sleep(5)
interfaces = netifaces.interfaces()
for interface in interfaces:
if netifaces.ifaddresses(interface).get(netifaces.AF_INET):
addr = netifaces.ifaddresses(interface).get(netifaces.AF_INET)[0]['addr']
if addr != '127.0.0.1':
return addr
def get_mac_address():
while True:
try:
return netifaces.ifaddresses('eth0')[netifaces.AF_LINK][0]['addr']
except KeyError:
pass
interfaces = netifaces.interfaces()
for interface in interfaces:
if netifaces.ifaddresses(interface).get(netifaces.AF_INET):
addr = netifaces.ifaddresses(interface).get(netifaces.AF_LINK)[0]['addr']
if addr != '00:00:00:00:00:00':
return addr
try:
return netifaces.ifaddresses('wlan0')[netifaces.AF_LINK][0]['addr']
except KeyError:
pass
_logger.warning("Couldn't get MAC address, sleeping and retrying.")
time.sleep(5)
def get_path_nginx():
return str(list(Path().absolute().parent.glob('*nginx*'))[0])
def get_ssid():
ap = subprocess.call(['systemctl', 'is-active', '--quiet', 'hostapd']) # if service is active return 0 else inactive
@@ -173,16 +196,20 @@ def get_ssid():
return subprocess.check_output(['sed', 's/.*"\\(.*\\)"/\\1/'], stdin=process_grep.stdout).decode('utf-8').rstrip()
def get_odoo_server_url():
ap = subprocess.call(['systemctl', 'is-active', '--quiet', 'hostapd']) # if service is active return 0 else inactive
if not ap:
return False
if platform.system() == 'Linux':
ap = subprocess.call(['systemctl', 'is-active', '--quiet', 'hostapd']) # if service is active return 0 else inactive
if not ap:
return False
return read_file_first_line('odoo-remote-server.conf')
def get_token():
return read_file_first_line('token')
def get_version():
return subprocess.check_output(['cat', '/var/odoo/iotbox_version']).decode().rstrip()
if platform.system() == 'Linux':
return read_file_first_line('/var/odoo/iotbox_version')
elif platform.system() == 'Windows':
return 'W22_11'
def get_wifi_essid():
wifi_options = []
@@ -219,16 +246,17 @@ def load_certificate():
result = json.loads(response.data.decode('utf8'))['result']
if result:
write_file('odoo-subject.conf', result['subject_cn'])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/"])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/"])
Path('/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem'])
Path('/root_bypass_ramdisks/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem'])
Path('/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem'])
Path('/root_bypass_ramdisks/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem'])
subprocess.call(["sudo", "mount", "-o", "remount,ro", "/"])
subprocess.call(["sudo", "mount", "-o", "remount,ro", "/root_bypass_ramdisks/"])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/etc/cups"])
subprocess.check_call(["sudo", "service", "nginx", "restart"])
if platform.system() == 'Linux':
with writable():
Path('/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem'])
Path('/root_bypass_ramdisks/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem'])
Path('/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem'])
Path('/root_bypass_ramdisks/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem'])
elif platform.system() == 'Windows':
Path(get_path_nginx()).joinpath('conf/nginx-cert.crt').write_text(result['x509_pem'])
Path(get_path_nginx()).joinpath('conf/nginx-cert.key').write_text(result['private_key_pem'])
time.sleep(3)
start_nginx_server()
def download_iot_handlers(auto=True):
"""
@@ -240,14 +268,13 @@ def download_iot_handlers(auto=True):
pm = urllib3.PoolManager(cert_reqs='CERT_NONE')
server = server + '/iot/get_handlers'
try:
resp = pm.request('POST', server, fields={'mac': get_mac_address(), 'auto': auto})
resp = pm.request('POST', server, fields={'mac': get_mac_address(), 'auto': auto}, timeout=8)
if resp.data:
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/"])
drivers_path = Path.home() / 'odoo/addons/hw_drivers/iot_handlers'
zip_file = zipfile.ZipFile(io.BytesIO(resp.data))
zip_file.extractall(drivers_path)
subprocess.call(["sudo", "mount", "-o", "remount,ro", "/"])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/etc/cups"])
with writable():
drivers_path = ['odoo', 'addons', 'hw_drivers', 'iot_handlers']
path = path_file(str(Path().joinpath(*drivers_path)))
zip_file = zipfile.ZipFile(io.BytesIO(resp.data))
zip_file.extractall(path)
except Exception as e:
_logger.error('Could not reach configured server')
_logger.error('A error encountered : %s ' % e)
@@ -260,38 +287,46 @@ def load_iot_handlers():
"""
for directory in ['interfaces', 'drivers']:
path = get_resource_path('hw_drivers', 'iot_handlers', directory)
filesList = os.listdir(path)
filesList = list_file_by_os(path)
for file in filesList:
path_file = os.path.join(path, file)
spec = util.spec_from_file_location(file, path_file)
spec = util.spec_from_file_location(file, str(Path(path).joinpath(file)))
if spec:
module = util.module_from_spec(spec)
spec.loader.exec_module(module)
lazy_property.reset_all(http.root)
def list_file_by_os(file_list):
platform_os = platform.system()
if platform_os == 'Linux':
return [x.name for x in Path(file_list).glob('*[!W].*')]
elif platform_os == 'Windows':
return [x.name for x in Path(file_list).glob('*[!L].*')]
def odoo_restart(delay):
IR = IoTRestart(delay)
IR.start()
def path_file(filename):
platform_os = platform.system()
if platform_os == 'Linux':
return Path().absolute().parent.joinpath(filename)
elif platform_os == 'Windows':
return Path().absolute().parent.joinpath('server/' + filename)
def read_file_first_line(filename):
path = Path.home() / filename
path = Path('/home/pi/' + filename)
path = path_file(filename)
if path.exists():
with path.open('r') as f:
return f.readline().strip('\n')
return ''
def unlink_file(filename):
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/"])
path = Path.home() / filename
if path.exists():
path.unlink()
subprocess.call(["sudo", "mount", "-o", "remount,ro", "/"])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/etc/cups"])
with writable():
path = path_file(filename)
if path.exists():
path.unlink()
def write_file(filename, text):
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/"])
path = Path.home() / filename
path.write_text(text)
subprocess.call(["sudo", "mount", "-o", "remount,ro", "/"])
subprocess.call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/etc/cups"])
def write_file(filename, text, mode='w'):
with writable():
path = path_file(filename)
with open(path, mode) as f:
f.write(text)
-1
View File
@@ -1 +0,0 @@
from . import controllers
-32
View File
@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Egypt ETA Hardware Driver',
'category': 'Accounting/Accounting',
'website': 'https://www.odoo.com',
'summary': 'Egypt ETA Hardware Driver',
'description': """
Egypt ETA Hardware Driver
=======================
This module allows Odoo to digitally sign invoices using an USB key approved by the egyptian government
Special thanks to Plementus <info@plementus.com> for their help in developing this module.
Requirements per system
-----------------------
Windows:
- eps2003csp11.dll
Linux/macOS:
- OpenSC
""",
'external_dependencies': {
'python': ['PyKCS11'],
},
'installable': False,
'license': 'LGPL-3',
}
@@ -1 +0,0 @@
from . import main
+30 -28
View File
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
import jinja2
import platform
import logging
import os
from pathlib import Path
@@ -10,10 +12,10 @@ import subprocess
import sys
import threading
from odoo import http
from odoo import http, service
from odoo.http import Response
from odoo.modules.module import get_resource_path
from odoo.addons.hw_drivers.connection_manager import connection_manager
from odoo.addons.hw_drivers.main import iot_devices
from odoo.addons.hw_drivers.tools import helpers
from odoo.addons.web.controllers.home import Home
@@ -59,8 +61,11 @@ class IoTboxHomepage(Home):
def get_homepage_data(self):
hostname = str(socket.gethostname())
ssid = helpers.get_ssid()
wired = subprocess.check_output(['cat', '/sys/class/net/eth0/operstate']).decode('utf-8').strip('\n')
if platform.system() == 'Linux':
ssid = helpers.get_ssid()
wired = helpers.read_file_first_line('/sys/class/net/eth0/operstate')
else:
wired = 'up'
if wired == 'up':
network = 'Ethernet'
elif ssid:
@@ -85,9 +90,11 @@ class IoTboxHomepage(Home):
'mac': helpers.get_mac_address(),
'iot_device_status': iot_device,
'server_status': helpers.get_odoo_server_url() or 'Not Configured',
'pairing_code': connection_manager.pairing_code,
'six_terminal': self.get_six_terminal(),
'network_status': network,
'version': helpers.get_version(),
'system': platform.system(),
}
@http.route('/', type='http', auth='none')
@@ -101,14 +108,8 @@ class IoTboxHomepage(Home):
@http.route('/list_handlers', type='http', auth='none', website=True)
def list_handlers(self):
drivers_list = []
for driver in os.listdir(get_resource_path('hw_drivers', 'iot_handlers/drivers')):
if driver != '__pycache__':
drivers_list.append(driver)
interfaces_list = []
for interface in os.listdir(get_resource_path('hw_drivers', 'iot_handlers/interfaces')):
if interface != '__pycache__':
interfaces_list.append(interface)
drivers_list = helpers.list_file_by_os(get_resource_path('hw_drivers', 'iot_handlers', 'drivers'))
interfaces_list = helpers.list_file_by_os(get_resource_path('hw_drivers', 'iot_handlers', 'interfaces'))
return handler_list_template.render({
'title': "Odoo's IoT Box - Handlers list",
'breadcrumb': 'Handlers list',
@@ -120,7 +121,7 @@ class IoTboxHomepage(Home):
@http.route('/load_iot_handlers', type='http', auth='none', website=True)
def load_iot_handlers(self):
helpers.download_iot_handlers(False)
subprocess.check_call(["sudo", "service", "odoo", "restart"])
helpers.odoo_restart(0)
return "<meta http-equiv='refresh' content='20; url=http://" + helpers.get_ip() + ":8069/list_handlers'>"
@http.route('/list_credential', type='http', auth='none', website=True)
@@ -134,15 +135,16 @@ class IoTboxHomepage(Home):
@http.route('/save_credential', type='http', auth='none', cors='*', csrf=False)
def save_credential(self, db_uuid, enterprise_code):
helpers.add_credential(db_uuid, enterprise_code)
subprocess.check_call(["sudo", "service", "odoo", "restart"])
helpers.write_file('odoo-db-uuid.conf', db_uuid)
helpers.write_file('odoo-enterprise-code.conf', enterprise_code)
helpers.odoo_restart(0)
return "<meta http-equiv='refresh' content='20; url=http://" + helpers.get_ip() + ":8069'>"
@http.route('/clear_credential', type='http', auth='none', cors='*', csrf=False)
def clear_credential(self):
helpers.unlink_file('odoo-db-uuid.conf')
helpers.unlink_file('odoo-enterprise-code.conf')
subprocess.check_call(["sudo", "service", "odoo", "restart"])
helpers.odoo_restart(0)
return "<meta http-equiv='refresh' content='20; url=http://" + helpers.get_ip() + ":8069'>"
@http.route('/wifi', type='http', auth='none', website=True)
@@ -192,9 +194,9 @@ class IoTboxHomepage(Home):
@http.route('/handlers_clear', type='http', auth='none', cors='*', csrf=False)
def clear_handlers_list(self):
for directory in ['drivers', 'interfaces']:
for file in os.listdir(get_resource_path('hw_drivers', 'iot_handlers', directory)):
if file != '__pycache__':
helpers.unlink_file(get_resource_path('hw_drivers', 'iot_handlers', directory, file))
for file in list(Path(get_resource_path('hw_drivers', 'iot_handlers', directory)).glob('*')):
if file.name != '__pycache__':
helpers.unlink_file(str(file.relative_to(*file.parts[:3])))
return "<meta http-equiv='refresh' content='0; url=http://" + helpers.get_ip() + ":8069/list_handlers'>"
@http.route('/server_connect', type='http', auth='none', cors='*', csrf=False)
@@ -203,16 +205,16 @@ class IoTboxHomepage(Home):
credential = token.split('|')
url = credential[0]
token = credential[1]
if len(credential) > 2:
# IoT Box send token with db_uuid and enterprise_code only since V13
db_uuid = credential[2]
enterprise_code = credential[3]
helpers.add_credential(db_uuid, enterprise_code)
db_uuid = credential[2]
enterprise_code = credential[3]
helpers.save_conf_server(url, token, db_uuid, enterprise_code)
else:
url = helpers.get_odoo_server_url()
token = helpers.get_token()
reboot = 'reboot'
subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_server.sh'), url, iotname, token, reboot])
if iotname:
subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/rename_iot.sh'), iotname])
else:
helpers.odoo_restart(3)
return 'http://' + helpers.get_ip() + ':8069'
@http.route('/steps', type='http', auth='none', cors='*', csrf=False)
@@ -280,13 +282,13 @@ class IoTboxHomepage(Home):
@http.route('/six_payment_terminal_add', type='http', auth='none', cors='*', csrf=False)
def add_six_payment_terminal(self, terminal_id):
helpers.write_file('odoo-six-payment-terminal.conf', terminal_id)
subprocess.check_call(["sudo", "service", "odoo", "restart"])
service.server.restart()
return 'http://' + helpers.get_ip() + ':8069'
@http.route('/six_payment_terminal_clear', type='http', auth='none', cors='*', csrf=False)
def clear_six_payment_terminal(self):
helpers.unlink_file('odoo-six-payment-terminal.conf')
subprocess.check_call(["sudo", "service", "odoo", "restart"])
service.server.restart()
return "<meta http-equiv='refresh' content='0; url=http://" + helpers.get_ip() + ":8069'>"
@http.route('/hw_proxy/upgrade', type='http', auth='none', )
+16 -6
View File
@@ -155,7 +155,9 @@
<div class="title arrow-down">Restart</div>
<div class="content">
<div class="device-status">
<button class="btn btn-sm btn-sm-restart" onclick="restart_odoo_or_reboot('reboot_iot_box')">Reboot the IoT Box</button>
{% if system == "Linux" %}
<button class="btn btn-sm btn-sm-restart" onclick="restart_odoo_or_reboot('reboot_iot_box')">Reboot the IoT Box</button>
{% endif %}
<button class="btn btn-sm btn-sm-restart" onclick="restart_odoo_or_reboot('restart_odoo')">Restart Odoo service</button>
</div>
</div>
@@ -165,11 +167,11 @@
<table align="center" cellpadding="3">
<tr>
<td class="heading">Name</td>
<td> {{ hostname }} <a class="btn btn-sm float-end" href='/server'>configure</a></td>
<td> {{ hostname }} {% if system == "Linux" %}<a class="btn btn-sm float-right" href='/server'>configure</a>{% endif %}</td>
</tr>
<tr>
<td class="heading">Version</td>
<td> {{ version }} <a class="btn btn-sm float-end" href='/hw_proxy/upgrade/'>update</a></td>
<td> {{ version }} {% if system == "Linux" %}<a class="btn btn-sm float-right" href='/hw_proxy/upgrade/'>update</a>{% endif %}</td>
</tr>
<tr>
<td class="heading">IP Address</td>
@@ -181,7 +183,7 @@
</tr>
<tr>
<td class="heading">Network</td>
<td>{{ network_status }} <a class="btn btn-sm float-end" href='/wifi'>configure wifi</a></td>
<td>{{ network_status }} {% if system == "Linux" %}<a class="btn btn-sm float-right" href='/wifi'>configure wifi</a>{% endif %}</td>
</tr>
<tr>
<td class="heading">Server</td>
@@ -193,6 +195,12 @@
<td>{{ six_terminal }} <a class="btn btn-sm float-end" href='/six_payment_terminal'>configure</a></td>
</tr>
{% endif %}
{% if pairing_code %}
<tr>
<td class="heading">Pairing code</td>
<td>{{ pairing_code }}</td>
</tr>
{% endif %}
<tr>
<td class="heading">IOT Device</td>
<td>
@@ -220,8 +228,10 @@
</table>
<div style="margin: 20px auto 10px auto;" class="text-center">
<a class="btn" href='/point_of_sale/display'>POS Display</a>
<a class="btn" style="margin-left: 10px;" href='/remote_connect'>Remote Debug</a>
<a target="_blank" class="btn" style="margin-left: 10px;" href="http://{{ ip }}:631">Printers server</a>
{% if system == "Linux" %}
<a class="btn" style="margin-left: 10px;" href='/remote_connect'>Remote Debug</a>
<a target="_blank" class="btn" style="margin-left: 10px;" href="http://{{ ip }}:631">Printers server</a>
{% endif %}
{% if server_status != "Not Configured" %}
<a class="btn" style="margin-left: 10px;" href='/list_credential'>Credential</a>
{% endif %}
@@ -2,15 +2,10 @@
# Write the server configuration
function connect () {
SERVER="${1}"
CURRENT_SERVER_FILE=/home/pi/odoo-remote-server.conf
TOKEN_FILE=/home/pi/token
TOKEN="${3}"
REBOOT="${4}"
HOSTS=/root_bypass_ramdisks/etc/hosts
HOST_FILE=/root_bypass_ramdisks/etc/hostname
HOSTNAME="$(hostname)"
IOT_NAME="${2}"
IOT_NAME="${1}"
IOT_NAME="${IOT_NAME//[^[:ascii:]]/}"
IOT_NAME="${IOT_NAME//[^a-zA-Z0-9-]/}"
if [ -z "$IOT_NAME" ]
@@ -19,11 +14,6 @@ function connect () {
fi
sudo mount -o remount,rw /
sudo mount -o remount,rw /root_bypass_ramdisks
if [ ! -z "${1}" ]
then
echo "${SERVER}" > ${CURRENT_SERVER_FILE}
echo "${TOKEN}" > ${TOKEN_FILE}
fi
if [ "${IOT_NAME}" != "${HOSTNAME}" ]
then
sudo sed -i "s/${HOSTNAME}/${IOT_NAME}/g" ${HOSTS}
@@ -39,10 +29,6 @@ function connect () {
fi
sudo mount -o remount,ro /
sudo mount -o remount,ro /root_bypass_ramdisks
if [ "$REBOOT" == 'reboot' ]
then
sudo service odoo restart
fi
}
connect "${1}" "${2}" "${3}" "${4}"
connect "${1}"
@@ -28,8 +28,8 @@ __base="$(basename ${__file} .sh)"
MOUNT_POINT="${__dir}/root_mount"
OVERWRITE_FILES_BEFORE_INIT_DIR="${__dir}/overwrite_before_init"
OVERWRITE_FILES_AFTER_INIT_DIR="${__dir}/overwrite_after_init"
VERSION=15.0
VERSION_IOTBOX=21.10
VERSION=16.0
VERSION_IOTBOX=22.11
REPO=https://github.com/odoo/odoo.git
if ! file_exists *raspios*.img ; then
+7 -4
View File
@@ -26,15 +26,18 @@ server_clean:
rm -rf $(SERVER_DIRECTORY)/.cyg*
allinone: server_clean
# need to install requirements-local-proxy.txt for local proxy install
cp $(SERVER_DIRECTORY)/requirements.txt $(WINPY32_DIR)/
cp requirements-local-proxy.txt $(WINPY32_DIR)/
-(cd $(WINPY32_DIR) && ./python.exe -m pip install --upgrade pip)
-(cd $(WINPY32_DIR) && cat requirements.txt | while read PAC ; do Scripts/pip3.exe install "$${PAC%%#*}" ; done)
-(cd $(WINPY32_DIR) && Scripts/pip3.exe freeze)
-(cd $(WINPY32_DIR) && cat requirements*.txt | while read PAC ; do Scripts/pip3.exe install "$${PAC%%#*}" ; done)
-(cd $(WINPY32_DIR) && Scripts/pip3.exe list)
rm $(WINPY32_DIR)/requirements.txt
cp $(SERVER_DIRECTORY)/requirements.txt $(WINPY64_DIR)/
cp requirements-local-proxy.txt $(WINPY64_DIR)/
-(cd $(WINPY64_DIR) && ./python.exe -m pip install --upgrade pip)
-(cd $(WINPY64_DIR) && cat requirements.txt | while read PAC ; do Scripts/pip3.exe install "$${PAC%%#*}" ; done)
-(cd $(WINPY64_DIR) && Scripts/pip3.exe freeze)
-(cd $(WINPY64_DIR) && cat requirements*.txt | while read PAC ; do Scripts/pip3.exe install "$${PAC%%#*}" ; done)
-(cd $(WINPY64_DIR) && Scripts/pip3.exe list)
rm $(WINPY64_DIR)/requirements.txt
(cd $(SERVER_DIRECTORY)/setup/win32 && $(LAUNCH_MAKENSIS))
(cd $(SERVER_DIRECTORY)/setup/win32 && mkdir -p $(FILES_DIRECTORY))
+19
View File
@@ -0,0 +1,19 @@
events {
worker_connections 1024;
}
http {
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name localhost;
ssl_certificate nginx-cert.crt;
ssl_certificate_key nginx-cert.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_read_timeout 600s;
proxy_pass http://127.0.0.1:8069;
}
}
}
+3
View File
@@ -0,0 +1,3 @@
netifaces==0.11.0
PyKCS11==1.5.10
ghostscript==0.7
+78 -11
View File
@@ -174,10 +174,12 @@ LangString DESC_PostgreSQL_Username ${LANG_ENGLISH} "Username"
LangString DESC_PostgreSQL_Password ${LANG_ENGLISH} "Password"
LangString Profile_AllInOne ${LANG_ENGLISH} "Odoo Server And PostgreSQL Server"
LangString Profile_Server ${LANG_ENGLISH} "Odoo Server Only"
LangString Profile_LocalProxyMode ${LANG_ENGLISH} "Local Proxy Mode"
LangString Profile_IOT ${LANG_ENGLISH} "Odoo IoT"
LangString TITLE_Odoo_Server ${LANG_ENGLISH} "Odoo Server"
LangString TITLE_PostgreSQL ${LANG_ENGLISH} "PostgreSQL Database"
LangString TITLE_LocalProxyMode ${LANG_ENGLISH} "Local Proxy Mode"
LangString TITLE_IOT ${LANG_ENGLISH} "Odoo IoT"
LangString TITLE_Nginx ${LANG_ENGLISH} "Nginx WebServer"
LangString TITLE_Ghostscript ${LANG_ENGLISH} "Ghostscript interpreter"
LangString DESC_FinishPageText ${LANG_ENGLISH} "Start Odoo"
; French
@@ -197,16 +199,18 @@ LangString DESC_PostgreSQL_Username ${LANG_FRENCH} "Utilisateur"
LangString DESC_PostgreSQL_Password ${LANG_FRENCH} "Mot de passe"
LangString Profile_AllInOne ${LANG_FRENCH} "Serveur Odoo Et Serveur PostgreSQL"
LangString Profile_Server ${LANG_FRENCH} "Seulement Le Serveur Odoo"
LangString Profile_LocalProxyMode ${LANG_FRENCH} "Mode Proxy Local"
LangString Profile_IOT ${LANG_FRENCH} "Odoo IoT"
LangString TITLE_Odoo_Server ${LANG_FRENCH} "Serveur Odoo"
LangString TITLE_PostgreSQL ${LANG_FRENCH} "Installation du serveur de base de données PostgreSQL"
LangString TITLE_LocalProxyMode ${LANG_FRENCH} "Mode Proxy Local"
LangString TITLE_IOT ${LANG_FRENCH} "Odoo IoT"
LangString TITLE_Nginx ${LANG_FRENCH} "Installation du serveur web Nginx"
LangString TITLE_Ghostscript ${LANG_FRENCH} "Installation de l'interpréteur Ghostscript"
LangString DESC_FinishPageText ${LANG_FRENCH} "Démarrer Odoo"
InstType /NOCUSTOM
InstType $(Profile_AllInOne)
InstType $(Profile_Server)
InstType $(Profile_LocalProxyMode)
InstType $(Profile_IOT)
Section $(TITLE_Odoo_Server) SectionOdoo_Server
SectionIn 1 2 3
@@ -247,6 +251,8 @@ Section $(TITLE_Odoo_Server) SectionOdoo_Server
# Fix the addons path
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "addons_path" "$INSTDIR\server\odoo\addons"
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "bin_path" "$INSTDIR\thirdparty"
# Set data_dir
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "data_dir" "$INSTDIR\sessions"
# if we're going to install postgresql force it's path,
# otherwise we consider it's always done and/or correctly tune by users
@@ -256,17 +262,18 @@ Section $(TITLE_Odoo_Server) SectionOdoo_Server
# Productivity Apps
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "default_productivity_apps" "True"
DetailPrint "Installing Windows service"
nsExec::ExecTOLog '"$INSTDIR\python\python.exe" "$INSTDIR\server\odoo-bin" --stop-after-init --logfile "$INSTDIR\server\odoo.log" -s'
${If} ${RunningX64}
nsExec::ExecToLog '"$INSTDIR\nssm\win64\nssm.exe" install ${SERVICENAME} "$INSTDIR\python\python.exe"'
nsExec::ExecToLog '"$INSTDIR\nssm\win64\nssm.exe" set ${SERVICENAME} AppDirectory "$\"$INSTDIR\python$\""'
nsExec::ExecToLog '"$INSTDIR\nssm\win64\nssm.exe" set ${SERVICENAME} AppParameters "\"$INSTDIR\server\odoo-bin\" -c "\"$INSTDIR\server\odoo.conf\"'
nsExec::ExecToLog '"$INSTDIR\nssm\win64\nssm.exe" set ${SERVICENAME} ObjectName "SERVICE LOCAL" ""'
${Else}
nsExec::ExecToLog '"$INSTDIR\nssm\win32\nssm.exe" install ${SERVICENAME} "$INSTDIR\python\python.exe" '
nsExec::ExecToLog '"$INSTDIR\nssm\win32\nssm.exe" set ${SERVICENAME} AppDirectory "$\"$INSTDIR\python$\""'
nsExec::ExecToLog '"$INSTDIR\nssm\win32\nssm.exe" set ${SERVICENAME} AppParameters "\"$INSTDIR\server\odoo-bin\" -c "\"$INSTDIR\server\odoo.conf\"'
nsExec::ExecToLog '"$INSTDIR\nssm\win32\nssm.exe" set ${SERVICENAME} ObjectName "SERVICE LOCAL" ""'
${EndIf}
Call RestartOdooService
@@ -309,18 +316,73 @@ Section $(TITLE_PostgreSQL) SectionPostgreSQL
--serverport $TextPostgreSQLPort'
SectionEnd
Section $(TITLE_LocalProxyMode) LocalProxy
Section $(TITLE_IOT) IOT
SectionIn 3
DetailPrint "Configuring Local Proxy Mode"
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "server_wide_modules" "base,web,hw_l10n_eg_eta"
DetailPrint "Configuring TITLE_IOT"
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "server_wide_modules" "web,hw_posbox_homepage,hw_drivers"
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "list_db" "False"
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "max_cron_thread" "0"
WriteIniStr "$INSTDIR\server\odoo.conf" "options" "max_cron_threads" "0"
nsExec::ExecToStack '"$INSTDIR\python\python.exe" "$INSTDIR\server\odoo-bin" genproxytoken'
pop $0
pop $ProxyTokenPwd
Call RestartOdooService
SectionEnd
Section $(TITLE_Nginx) Nginx
SectionIn 3
SetOutPath '$TEMP'
VAR /GLOBAL nginx_zip_filename
VAR /GLOBAL nginx_url
# need unzip plugin:
# https://nsis.sourceforge.io/mediawiki/images/5/5a/NSISunzU.zip
StrCpy $nginx_zip_filename "nginx-1.22.0.zip"
StrCpy $nginx_url "https://nginx.org/download/$nginx_zip_filename"
DetailPrint "Downloading Nginx"
inetc::get "$nginx_url" "$TEMP\$nginx_zip_filename" /POPUP
DetailPrint "Temp dir: $TEMP\$nginx_zip_filename"
DetailPrint "Unzip Nginx"
nsisunz::UnzipToLog "$TEMP\$nginx_zip_filename" "$INSTDIR"
Pop $0
StrCmp $0 "success" ok
DetailPrint "$0" ;print error message to log
ok:
FindFirst $0 $1 "$INSTDIR\nginx*"
DetailPrint "Setting up nginx"
SetOutPath "$INSTDIR\$1\conf"
CreateDirectory $INSTDIR\$1\temp
CreateDirectory $INSTDIR\$1\logs
FindClose $0
File "conf\nginx\nginx.conf"
# Temporary certs for the first start
File "..\..\odoo\addons\point_of_sale\tools\posbox\overwrite_after_init\etc\ssl\certs\nginx-cert.crt"
File "..\..\odoo\addons\point_of_sale\tools\posbox\overwrite_after_init\etc\ssl\private\nginx-cert.key"
SectionEnd
Section $(TITLE_Ghostscript) SectionGhostscript
SectionIn 3
SetOutPath '$TEMP'
VAR /GLOBAL ghostscript_exe_filename
VAR /GLOBAL ghostscript_url
StrCpy $ghostscript_exe_filename "gs1000w64.exe"
StrCpy $ghostscript_url "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs1000/$ghostscript_exe_filename"
DetailPrint "Downloading Ghostscript"
inetc::get "$ghostscript_url" "$TEMP\$ghostscript_exe_filename" /POPUP
DetailPrint "Temp dir: $TEMP\$ghostscript_exe_filename"
Rmdir /r "INSTDIR\Ghostscript"
DetailPrint "Installing Ghostscript"
ExecWait '"$TEMP\$ghostscript_exe_filename" \
/S \
/D=$INSTDIR\Ghostscript'
SectionEnd
Section -Post
WriteRegExpandStr HKLM "${UNINSTALL_REGISTRY_KEY}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteRegExpandStr HKLM "${UNINSTALL_REGISTRY_KEY}" "InstallLocation" "$INSTDIR"
@@ -347,15 +409,20 @@ Section "Uninstall"
Pop $R0
ReadRegStr $0 HKLM "${UNINSTALL_REGISTRY_KEY_SERVER}" "UninstallString"
ExecWait '"$0" /S'
ExecWait '"$INSTDIR\Ghostscript\uninstgs.exe" /S'
nsExec::Exec "net stop ${SERVICENAME}"
nsExec::Exec "sc delete ${SERVICENAME}"
sleep 2
Rmdir /r "$INSTDIR\server"
Rmdir /r "$INSTDIR\sessions"
Rmdir /r "$INSTDIR\thirdparty"
Rmdir /r "$INSTDIR\python"
Rmdir /r "$INSTDIR\nssm"
FindFirst $0 $1 "$INSTDIR\nginx*"
Rmdir /R "$INSTDIR\$1"
FindClose $0
DeleteRegKey HKLM "${UNINSTALL_REGISTRY_KEY}"
SectionEnd
@@ -503,7 +570,7 @@ Function ShowProxyTokenDialogPage
Abort
${EndIf}
${NSD_CreateLabel} 0 0 100% 25% "Here is your access token for the Odoo Local Proxy, please write it down in a safe place, you will need it to configure the proxy"
${NSD_CreateLabel} 0 0 100% 25% "Here is your access token for the Odoo IOT, please write it down in a safe place, you will need it to configure the IOT"
Pop $ProxyTokenLabel
${NSD_CreateText} 0 30% 100% 13u $ProxyTokenPwd