[IMP] hw_drivers: Easier detection of new devices

Until now, to detect new types of devices on the IoT Box, we had to
modify driver.py. If partners wanted to connect a new type of device,
they had to modify directly the files on the Box, meaning that after
each new build they had to re-apply all their modifications.

We remove the loops that detected the connected devices and replace
them by `Interfaces`. All interfaces will be loaded from the connected
Odoo instance with the corresponding drivers.

TaskID: 2082282
This commit is contained in:
Antoine Prieels
2020-05-13 13:26:05 +00:00
parent bea1613f11
commit e1c52c936d
15 changed files with 240 additions and 282 deletions
+82 -245
View File
@@ -1,27 +1,18 @@
#!/usr/bin/python3
import logging
import time
from threading import Thread, Event, Lock
from usb import core
from gatt import DeviceManager as Gatt_DeviceManager
import subprocess
from base64 import b64decode
from dbus.mainloop.glib import DBusGMainLoop
from importlib import util
import json
from re import sub, finditer
import urllib3
import logging
import os
import socket
import subprocess
import sys
from importlib import util
import v4l2
from fcntl import ioctl
from cups import Connection as cups_connection
from glob import glob
from base64 import b64decode
from pathlib import Path
import socket
import ctypes
from threading import Thread, Event
import time
import urllib3
from odoo import http, tools, _
from odoo import http, tools
from odoo.http import send_file
from odoo.modules.module import get_resource_path
from odoo.addons.hw_drivers.tools import helpers
@@ -91,7 +82,7 @@ class StatusController(http.Controller):
subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_server.sh'), url, '', token, 'noreboot'])
helpers.check_certificate()
m.send_alldevices()
m.load_drivers()
m.load_iot_handlers()
image = get_resource_path('hw_drivers', 'static/img', 'True.jpg')
except subprocess.CalledProcessError as e:
_logger.error('A error encountered : %s ' % e.output)
@@ -135,10 +126,56 @@ sys.stderr = ExceptionLogger()
#----------------------------------------------------------
drivers = []
bt_devices = {}
socket_devices = {}
interfaces = {}
iot_devices = {}
class InterfaceMetaClass(type):
def __new__(cls, clsname, bases, attrs):
if clsname in interfaces:
return interfaces[clsname]
new_interface = super(InterfaceMetaClass, cls).__new__(cls, clsname, bases, attrs)
interfaces[clsname] = new_interface
return new_interface
class Interface(Thread, metaclass=InterfaceMetaClass):
_loop_delay = 3 # Delay (in seconds) between calls to get_devices or 0 if it should be called only once
_detected_devices = {}
connection_type = ''
def __init__(self):
super(Interface, self).__init__()
self.drivers = [d for d in drivers if d.connection_type == self.connection_type]
def run(self):
while self.connection_type and self.drivers:
self.update_iot_devices(self.get_devices())
if not self._loop_delay:
break
time.sleep(self._loop_delay)
def update_iot_devices(self, devices={}):
added = devices.keys() - self._detected_devices
removed = self._detected_devices - devices.keys()
self._detected_devices = devices.keys()
for path in removed:
if path in iot_devices:
iot_devices[path].disconnect()
_logger.info('Device %s is now disconnected', path)
for path in added:
for driver in self.drivers:
if driver.supported(device=devices[path]):
_logger.info('Device %s is now connected', path)
d = driver(device=devices[path])
d.daemon = True
d.start()
iot_devices[path] = d
break
def get_devices(self):
raise NotImplementedError()
class DriverMetaClass(type):
def __new__(cls, clsname, bases, attrs):
newclass = super(DriverMetaClass, cls).__new__(cls, clsname, bases, attrs)
@@ -160,7 +197,6 @@ class Driver(Thread, metaclass=DriverMetaClass):
super(Driver, self).__init__()
self.dev = device
self.data = {'value': ''}
self.gatt_device = False
self._device_manufacturer = ''
@property
@@ -253,12 +289,6 @@ class EventManager(object):
self.sessions[session]['event'].set()
class IoTDevice(object):
def __init__(self, dev, connection_type):
self.dev = dev
self.connection_type = connection_type
event_manager = EventManager()
@@ -268,21 +298,22 @@ event_manager = EventManager()
class Manager(Thread):
def load_drivers(self):
def load_iot_handlers(self):
"""
This method loads local files: 'odoo/addons/hw_drivers/drivers'
And execute these python drivers
This method loads local files: 'odoo/addons/hw_drivers/iot_handlers/drivers' and
'odoo/addons/hw_drivers/iot_handlers/interfaces'
And execute these python drivers and interfaces
"""
helpers.download_drivers()
path = get_resource_path('hw_drivers', 'drivers')
driversList = os.listdir(path)
self.devices = {}
for driver in driversList:
path_file = os.path.join(path, driver)
spec = util.spec_from_file_location(driver, path_file)
if spec:
module = util.module_from_spec(spec)
spec.loader.exec_module(module)
helpers.download_iot_handlers()
for directory in ['interfaces', 'drivers']:
path = get_resource_path('hw_drivers', 'iot_handlers', directory)
filesList = os.listdir(path)
for file in filesList:
path_file = os.path.join(path, file)
spec = util.spec_from_file_location(file, path_file)
if spec:
module = util.module_from_spec(spec)
spec.loader.exec_module(module)
http.addons_manifest = {}
http.root = http.Root()
@@ -335,222 +366,28 @@ class Manager(Thread):
else:
_logger.warning('Odoo server not set')
def get_connected_displays(self):
display_devices = {}
displays = subprocess.check_output(['tvservice', '-l']).decode()
x_screen = 0
for match in finditer('Display Number (\d), type HDMI (\d)', displays):
display_id, hdmi_id = match.groups()
tvservice_output = subprocess.check_output(['tvservice', '-nv', display_id]).decode().rstrip()
if tvservice_output:
display_name = tvservice_output.split('=')[1]
display_identifier = sub('[^a-zA-Z0-9 ]+', '', display_name).replace(' ', '_') + "_" + str(hdmi_id)
iot_device = IoTDevice({
'identifier': display_identifier,
'name': display_name,
'x_screen': str(x_screen),
}, 'display')
display_devices[display_identifier] = iot_device
x_screen += 1
if not len(display_devices):
# No display connected, create "fake" device to be accessed from another computer
display_devices['distant_display'] = IoTDevice({
'identifier': "distant_display",
'name': "Distant Display",
}, 'display')
return display_devices
def serial_loop(self):
serial_devices = {}
for identifier in glob('/dev/serial/by-path/*'):
iot_device = IoTDevice({'identifier': identifier, }, 'serial')
serial_devices[identifier] = iot_device
return serial_devices
def usb_loop(self):
"""
Loops over the connected usb devices, assign them an identifier, instantiate
an `IoTDevice` for them.
USB devices are identified by a combination of their `idVendor` and
`idProduct`. We can't be sure this combination in unique per equipment.
To still allow connecting multiple similar equipments, we complete the
identifier by a counter. The drawbacks are we can't be sure the equipments
will get the same identifiers after a reboot or a disconnect/reconnect.
:return: a dict of the `IoTDevices` instances indexed by their identifier.
"""
usb_devices = {}
devs = core.find(find_all=True)
cpt = 2
for dev in devs:
dev.identifier = "usb_%04x:%04x" % (dev.idVendor, dev.idProduct)
if dev.identifier in usb_devices:
dev.identifier += '_%s' % cpt
cpt += 1
iot_device = IoTDevice(dev, 'usb')
usb_devices[dev.identifier] = iot_device
return usb_devices
def video_loop(self):
camera_devices = {}
videos = glob('/dev/video*')
for video in videos:
with open(video, 'w') as path:
dev = v4l2.v4l2_capability()
ioctl(path, v4l2.VIDIOC_QUERYCAP, dev)
dev.interface = video
dev.identifier = dev.bus_info.decode('utf-8')
iot_device = IoTDevice(dev, 'video')
camera_devices[dev.identifier] = iot_device
return camera_devices
def printer_loop(self):
printer_devices = {}
with cups_lock:
devices = conn.getDevices()
for path in devices:
if 'uuid=' in path:
serial = sub('[^a-zA-Z0-9 ]+', '', path.split('uuid=')[1])
elif 'serial=' in path:
serial = sub('[^a-zA-Z0-9 ]+', '', path.split('serial=')[1])
else:
serial = sub('[^a-zA-Z0-9 ]+', '', path)
devices[path]['identifier'] = serial
devices[path]['url'] = path
iot_device = IoTDevice(devices[path], 'printer')
printer_devices[serial] = iot_device
return printer_devices
def run(self):
"""
Thread that will check connected/disconnected device, load drivers if needed and contact the odoo server with the updates
"""
helpers.check_git_branch()
helpers.check_certificate()
updated_devices = {}
self.send_alldevices()
self.load_drivers()
# The list of devices doesn't change after the Raspberry has booted
display_devices = self.get_connected_displays()
cpt = 0
self.load_iot_handlers()
self.previous_iot_devices = []
for interface in interfaces.values():
i = interface()
i.daemon = True
i.start()
while 1:
updated_devices = self.usb_loop()
updated_devices.update(self.video_loop())
updated_devices.update(mpdm.devices)
updated_devices.update(display_devices)
updated_devices.update(bt_devices)
updated_devices.update(socket_devices)
updated_devices.update(self.serial_loop())
if cpt % 40 == 0:
printer_devices = self.printer_loop()
cpt = 0
updated_devices.update(printer_devices)
cpt += 1
added = updated_devices.keys() - self.devices.keys()
removed = self.devices.keys() - updated_devices.keys()
self.devices = updated_devices
send_devices = False
for path in [device_rm for device_rm in removed if device_rm in iot_devices]:
iot_devices[path].disconnect()
_logger.info('Device %s is now disconnected', path)
send_devices = True
for path in [device_add for device_add in added if device_add not in iot_devices]:
for driverclass in [d for d in drivers if d.connection_type == self.devices[path].connection_type]:
if driverclass.supported(device = updated_devices[path].dev):
_logger.info('Device %s is now connected', path)
d = driverclass(device = updated_devices[path].dev)
d.daemon = True
d.start()
iot_devices[path] = d
send_devices = True
break
if send_devices:
if iot_devices != self.previous_iot_devices:
self.send_alldevices()
self.previous_iot_devices = iot_devices.copy()
time.sleep(3)
class GattBtManager(Gatt_DeviceManager):
def device_discovered(self, device):
path = "bt_%s" % (device.mac_address,)
if path not in bt_devices:
device.manager = self
device.identifier = path
iot_device = IoTDevice(device, 'bluetooth')
bt_devices[path] = iot_device
class BtManager(Thread):
def run(self):
dm = GattBtManager(adapter_name='hci0')
for device in [device_con for device_con in dm.devices() if device_con.is_connected()]:
device.disconnect()
dm.start_discovery()
dm.run()
class SocketManager(Thread):
def run(self):
while True:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 9000))
sock.listen(1)
dev, addr = sock.accept()
if addr and addr[0] not in socket_devices:
iot_device = IoTDevice(type('', (), {'dev': dev}), 'socket')
socket_devices[addr[0]] = iot_device
except OSError as e:
_logger.error(_('Error in SocketManager: %s') % (e.strerror))
class MPDManager(Thread):
def __init__(self):
super(MPDManager, self).__init__()
self.devices = {}
self.mpd_session = ctypes.c_void_p()
def run(self):
eftapi.EFT_CreateSession(ctypes.byref(self.mpd_session))
while True:
if self.terminal_connected(terminal_id):
self.devices[terminal_id] = IoTDevice(terminal_id, 'mpd')
elif terminal_id in self.devices:
self.devices = {}
time.sleep(20)
def terminal_connected(self, terminal_id):
eftapi.EFT_QueryStatus(self.mpd_session)
device_status = ctypes.c_long()
eftapi.EFT_GetDeviceStatusCode(self.mpd_session, ctypes.byref(device_status))
return device_status.value in [0, 1]
conn = cups_connection()
PPDs = conn.getPPDs()
printers = conn.getPrinters()
cups_lock = Lock() # We can only make one call to Cups at a time
mpdm = MPDManager()
terminal_id = helpers.read_file_first_line('odoo-six-payment-terminal.conf')
subprocess.call(["pkill", "-9", "eftdvs"]) # Kill any running MPD server
if terminal_id:
subprocess.Popen(["eftdvs", "/ConfigDir", "/usr/share/eftdvs/", "/TID", terminal_id]) # Start MPD server
eftapi = ctypes.CDLL("eftapi.so") # Library given by Six
mpdm.daemon = True
mpdm.start()
# Must be started from main thread
DBusGMainLoop(set_as_default=True)
m = Manager()
m.daemon = True
m.start()
bm = BtManager()
bm.daemon = True
bm.start()
sm = SocketManager()
sm.daemon = True
sm.start()
@@ -13,7 +13,7 @@ from odoo import http
from odoo.addons.hw_drivers.tools import helpers
from odoo.addons.hw_drivers.controllers.driver import Driver, event_manager, iot_devices
path = os.path.realpath(os.path.join(os.path.dirname(__file__), '../views'))
path = os.path.realpath(os.path.join(os.path.dirname(__file__), '../../views'))
loader = jinja2.FileSystemLoader(path)
jinja_env = jinja2.Environment(loader=loader, autoescape=True)
@@ -188,7 +188,7 @@ class DisplayController(http.Controller):
cust_js = None
interfaces = ni.interfaces()
with open(os.path.join(os.path.dirname(__file__), "../static/src/js/worker.js")) as js:
with open(os.path.join(os.path.dirname(__file__), "../../static/src/js/worker.js")) as js:
cust_js = js.read()
display_ifaces = []
@@ -15,7 +15,8 @@ import tempfile
from PIL import Image, ImageOps
from odoo import http, _
from odoo.addons.hw_drivers.controllers.driver import event_manager, Driver, PPDs, conn, printers, cups_lock, iot_devices
from odoo.addons.hw_drivers.controllers.driver import event_manager, Driver, iot_devices
from odoo.addons.hw_drivers.iot_handlers.interfaces.PrinterInterface import PPDs, conn, printers, cups_lock
from odoo.addons.hw_drivers.tools import helpers
from odoo.addons.hw_proxy.controllers.main import drivers as old_drivers
@@ -13,7 +13,7 @@ import serial
from odoo import http
from odoo.addons.hw_proxy.controllers.main import drivers as old_drivers
from odoo.addons.hw_drivers.controllers.driver import event_manager
from odoo.addons.hw_drivers.drivers.SerialBaseDriver import SerialDriver, SerialProtocol, serial_connection
from odoo.addons.hw_drivers.iot_handlers.drivers.SerialBaseDriver import SerialDriver, SerialProtocol, serial_connection
_logger = logging.getLogger(__name__)
@@ -0,0 +1,36 @@
from re import sub, finditer
import subprocess
from odoo.addons.hw_drivers.controllers.driver import Interface
class DisplayInterface(Interface):
_loop_delay = 0
connection_type = 'display'
def get_devices(self):
display_devices = {}
displays = subprocess.check_output(['tvservice', '-l']).decode()
x_screen = 0
for match in finditer('Display Number (\d), type HDMI (\d)', displays):
display_id, hdmi_id = match.groups()
tvservice_output = subprocess.check_output(['tvservice', '-nv', display_id]).decode().strip()
if tvservice_output:
display_name = tvservice_output.split('=')[1]
display_identifier = sub('[^a-zA-Z0-9 ]+', '', display_name).replace(' ', '_') + "_" + str(hdmi_id)
iot_device = {
'identifier': display_identifier,
'name': display_name,
'x_screen': str(x_screen),
}
display_devices[display_identifier] = iot_device
x_screen += 1
if not len(display_devices):
# No display connected, create "fake" device to be accessed from another computer
display_devices['distant_display'] = {
'identifier': "distant_display",
'name': "Distant Display",
}
return display_devices
@@ -0,0 +1,30 @@
from cups import Connection as cups_connection
from re import sub
from threading import Lock
from odoo.addons.hw_drivers.controllers.driver import Interface
conn = cups_connection()
PPDs = conn.getPPDs()
printers = conn.getPrinters()
cups_lock = Lock() # We can only make one call to Cups at a time
class PrinterInterface(Interface):
_loop_delay = 120
connection_type = 'printer'
def get_devices(self):
printer_devices = {}
with cups_lock:
devices = conn.getDevices()
for path in devices:
if 'uuid=' in path:
identifier = sub('[^a-zA-Z0-9 ]+', '', path.split('uuid=')[1])
elif 'serial=' in path:
identifier = sub('[^a-zA-Z0-9 ]+', '', path.split('serial=')[1])
else:
identifier = sub('[^a-zA-Z0-9 ]+', '', path)
devices[path]['identifier'] = identifier
devices[path]['url'] = path
printer_devices[identifier] = devices[path]
return printer_devices
@@ -0,0 +1,15 @@
from glob import glob
from odoo.addons.hw_drivers.controllers.driver import Interface
class SerialInterface(Interface):
connection_type = 'serial'
def get_devices(self):
serial_devices = {}
for identifier in glob('/dev/serial/by-path/*'):
serial_devices[identifier] = {
'identifier': identifier
}
return serial_devices
@@ -0,0 +1,26 @@
from usb import core
from odoo.addons.hw_drivers.controllers.driver import Interface
class USBInterface(Interface):
connection_type = 'usb'
def get_devices(self):
"""
USB devices are identified by a combination of their `idVendor` and
`idProduct`. We can't be sure this combination in unique per equipment.
To still allow connecting multiple similar equipments, we complete the
identifier by a counter. The drawbacks are we can't be sure the equipments
will get the same identifiers after a reboot or a disconnect/reconnect.
"""
usb_devices = {}
devs = core.find(find_all=True)
cpt = 2
for dev in devs:
dev.identifier = "usb_%04x:%04x" % (dev.idVendor, dev.idProduct)
if dev.identifier in usb_devices:
dev.identifier += '_%s' % cpt
cpt += 1
usb_devices[dev.identifier] = dev
return usb_devices
+5 -4
View File
@@ -79,7 +79,8 @@ def check_git_branch():
if db_branch != local_branch:
subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"])
subprocess.check_call(["rm", "/home/pi/odoo/addons/hw_drivers/drivers/*"])
subprocess.check_call(["rm", "/home/pi/odoo/addons/hw_drivers/iot_handlers/drivers/*"])
subprocess.check_call(["rm", "/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')
@@ -195,7 +196,7 @@ def load_certificate():
subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/etc/cups"])
subprocess.check_call(["sudo", "service", "nginx", "restart"])
def download_drivers(auto=True):
def download_iot_handlers(auto=True):
"""
Get the drivers from the configured Odoo server
"""
@@ -203,13 +204,13 @@ def download_drivers(auto=True):
if server:
urllib3.disable_warnings()
pm = urllib3.PoolManager(cert_reqs='CERT_NONE')
server = server + '/iot/get_drivers'
server = server + '/iot/get_handlers'
try:
resp = pm.request('POST', server, fields={'mac': get_mac_address(), 'auto': auto})
if resp.data:
subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"])
zip_file = zipfile.ZipFile(io.BytesIO(resp.data))
zip_file.extractall(get_resource_path('hw_drivers', 'drivers'))
zip_file.extractall(get_resource_path('hw_drivers', 'iot_handlers'))
subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/"])
subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/etc/cups"])
except Exception as e:
+23 -17
View File
@@ -42,7 +42,7 @@ jinja_env.filters["json"] = json.dumps
homepage_template = jinja_env.get_template('homepage.html')
server_config_template = jinja_env.get_template('server_config.html')
wifi_config_template = jinja_env.get_template('wifi_config.html')
driver_list_template = jinja_env.get_template('driver_list.html')
handler_list_template = jinja_env.get_template('handler_list.html')
remote_connect_template = jinja_env.get_template('remote_connect.html')
configure_wizard_template = jinja_env.get_template('configure_wizard.html')
six_payment_terminal_template = jinja_env.get_template('six_payment_terminal.html')
@@ -103,24 +103,29 @@ class IoTboxHomepage(web.Home):
else:
return homepage_template.render(self.get_homepage_data())
@http.route('/list_drivers', type='http', auth='none', website=True)
def list_drivers(self):
@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', 'drivers')):
for driver in os.listdir(get_resource_path('hw_drivers', 'iot_handlers/drivers')):
if driver != '__pycache__':
drivers_list.append(driver)
return driver_list_template.render({
'title': "Odoo's IoT Box - Drivers list",
'breadcrumb': 'Drivers list',
interfaces_list = []
for interface in os.listdir(get_resource_path('hw_drivers', 'iot_handlers/interfaces')):
if interface != '__pycache__':
interfaces_list.append(interface)
return handler_list_template.render({
'title': "Odoo's IoT Box - Handlers list",
'breadcrumb': 'Handlers list',
'drivers_list': drivers_list,
'interfaces_list': interfaces_list,
'server': helpers.get_odoo_server_url()
})
@http.route('/load_drivers', type='http', auth='none', website=True)
def load_drivers(self):
helpers.download_drivers(False)
@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"])
return "<meta http-equiv='refresh' content='20; url=http://" + helpers.get_ip() + ":8069/list_drivers'>"
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)
def list_credential(self):
@@ -188,12 +193,13 @@ class IoTboxHomepage(web.Home):
helpers.unlink_file('odoo-remote-server.conf')
return "<meta http-equiv='refresh' content='0; url=http://" + helpers.get_ip() + ":8069'>"
@http.route('/drivers_clear', type='http', auth='none', cors='*', csrf=False)
def clear_drivers_list(self):
for driver in os.listdir(get_resource_path('hw_drivers', 'drivers')):
if driver != '__pycache__':
helpers.unlink_file(get_resource_path('hw_drivers', 'drivers', driver))
return "<meta http-equiv='refresh' content='0; url=http://" + helpers.get_ip() + ":8069/list_drivers'>"
@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))
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)
def connect_to_server(self, token, iotname):
@@ -3,42 +3,48 @@
{% block head %}
<script>
$(document).ready(function () {
$('#load_driver_btn').on('click', function(e){
$('#load_handler_btn').on('click', function(e){
e.preventDefault();
$('.loading-block').removeClass('o_hide');
$.ajax({
url: '/load_drivers',
url: '/load_iot_handlers',
}).done(function () {
$('.message-status').html('Driver loaded successfully <br> Refreshing page');
$('.message-status').html('Handlers loaded successfully <br> Refreshing page');
setTimeout(function () {
location.reload(true);
}, 20000);
}, 25000);
}).fail(function () {
setTimeout(function () {
location.reload(true);
}, 20000);
}, 25000);
});
});
});
</script>
{% endblock %}
{% block content %}
<h2 class="text-center text-green">Interfaces list</h2>
<table align="center" width="50%" cellpadding="3">
{% for interfaces in interfaces_list -%}
<tr><td>{{ interfaces }}</td></tr>
{%- endfor %}
</table>
<h2 class="text-center text-green">Drivers list</h2>
<table align="center" cellpadding="3">
<table align="center" width="50%" cellpadding="3">
{% for driver in drivers_list -%}
<tr><td>{{ driver }}</td></tr>
{%- endfor %}
</table>
{% if server %}
<div style="margin-top: 20px;" class="text-center">
<a id="load_driver_btn" class="btn" href='/load_drivers'>Load drivers</a>
<a id="load_handler_btn" class="btn" href='/load_iot_handlers'>Load handlers</a>
</div>
<div class="text-center font-small" style="margin: 10px auto;">
You can clear the drivers configuration
<form style="display: inline-block;margin-left: 4px;" action='/drivers_clear'>
You can clear the handlers configuration
<form style="display: inline-block;margin-left: 4px;" action='/handlers_clear'>
<input class="btn btn-sm" type="submit" value="Clear"/>
</form>
</div>
{% endif %}
{{ loading_block_ui('Loading Drivers') }}
{{ loading_block_ui('Loading Handlers') }}
{% endblock %}
@@ -137,7 +137,7 @@
</div>
{% endfor %}
</div>
<br><center><a class="btn btn-sm" href='/list_drivers'>drivers list</a></center>
<br><center><a class="btn btn-sm" href='/list_handlers'>handlers list</a></center>
</td>
</tr>
</table>
@@ -168,7 +168,7 @@ echo "disable_overscan=1" >> /boot/config.txt
sed -i '/dtoverlay/d' /boot/config.txt
# exclude /drivers folder from git info to be able to load specific drivers
echo "addons/hw_drivers/drivers/" > /home/pi/odoo/.git/info/exclude
echo "addons/hw_drivers/iot_devices/" > /home/pi/odoo/.git/info/exclude
# create dirs for ramdisks
create_ramdisk_dir () {