diff --git a/addons/hw_drivers/__init__.py b/addons/hw_drivers/__init__.py
new file mode 100644
index 00000000000..a03bfd097a6
--- /dev/null
+++ b/addons/hw_drivers/__init__.py
@@ -0,0 +1 @@
+from . import controllers
\ No newline at end of file
diff --git a/addons/hw_drivers/__manifest__.py b/addons/hw_drivers/__manifest__.py
new file mode 100644
index 00000000000..c1955ee6d74
--- /dev/null
+++ b/addons/hw_drivers/__manifest__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'Hardware Proxy',
+ 'category': 'IOT',
+ 'sequence': 6,
+ 'summary': 'Connect the Web Client to Hardware Peripherals',
+ 'website': 'https://www.odoo.com/page/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.
+
+""",
+ 'installable': False,
+}
diff --git a/addons/hw_drivers/controllers/__init__.py b/addons/hw_drivers/controllers/__init__.py
new file mode 100644
index 00000000000..3bd726d7111
--- /dev/null
+++ b/addons/hw_drivers/controllers/__init__.py
@@ -0,0 +1,2 @@
+from . import driver
+from . import loading
\ No newline at end of file
diff --git a/addons/hw_drivers/controllers/driver.py b/addons/hw_drivers/controllers/driver.py
new file mode 100755
index 00000000000..7e2a775e4b7
--- /dev/null
+++ b/addons/hw_drivers/controllers/driver.py
@@ -0,0 +1,422 @@
+#!/usr/bin/python3
+import logging
+import time
+from threading import Thread
+import usb
+import gatt
+import subprocess
+import netifaces as ni
+import json
+import re
+from odoo import http
+import urllib3
+from odoo.http import request as httprequest
+import datetime
+
+logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s: %(message)s')
+_logger = logging.getLogger('dispatcher')
+
+owner_dict = {}
+last_ping = {}
+
+class StatusController(http.Controller):
+
+ @http.route('/hw_drivers/owner/check', type='json', auth='none', cors='*', csrf=False)
+ def check_cantakeowner(self): #, devices, tab
+ data = httprequest.jsonrequest
+ for device in data['devices']:
+ if owner_dict.get(device) and owner_dict[device] != data['tab']:
+ before_date = datetime.datetime.now() - datetime.timedelta(seconds=10)
+ if last_ping.get(owner_dict[device]) and last_ping.get(owner_dict[device]) > before_date:
+ return 'no'
+ else:
+ old_tab = owner_dict[device]
+ for dev2 in owner_dict:
+ if owner_dict[dev2] == old_tab:
+ owner_dict[dev2] = ''
+ return 'yes'
+
+ @http.route('/hw_drivers/owner/take', type='json', auth='none', cors='*', csrf=False)
+ def take_ownership(self): #, devices, tab
+ data = httprequest.jsonrequest
+ for device in data['devices']:
+ owner_dict[device] = data['tab']
+ last_ping[data['tab']] = datetime.datetime.now()
+ return data['tab']
+
+ @http.route('/hw_drivers/owner/ping', type='json', auth='none', cors='*', csrf=False)
+ def ping_trigger(self): #, tab
+ data = httprequest.jsonrequest
+ ping_dict = {}
+ last_ping[data['tab']] = datetime.datetime.now()
+ for dev in data['devices']:
+ if owner_dict.get(dev) and owner_dict[dev] == data['tab']:
+ for driver_path in drivers:
+ if driver_path.find(dev) == 0 and drivers[driver_path].ping_value:
+ ping_dict[dev] = drivers[driver_path].ping_value
+ drivers[driver_path].ping_value = '' # or set it to nothing
+ else:
+ ping_dict[dev] = 'STOP'
+ return ping_dict
+
+ @http.route('/hw_drivers/box/connect', type='json', auth='none', cors='*', csrf=False)
+ def connect_box(self):
+ data = httprequest.jsonrequest
+ server = "" # read from file
+ try:
+ f = open('/home/pi/odoo-remote-server.conf', 'r')
+ for line in f:
+ server += line
+ f.close()
+ server = server.split('\n')[0]
+ except:
+ server = ''
+ if server:
+ return 'This IoTBox has already been connected'
+ else:
+ iotname = ''
+ token = data['token'].split('|')[1]
+ url = data['token'].split('|')[0]
+ reboot = 'noreboot'
+ subprocess.call(['/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/connect_to_server.sh', url, iotname, token, reboot])
+ send_iot_box_device(False)
+ return 'IoTBox connected'
+
+ @http.route('/hw_drivers/drivers/status', type='http', auth='none', cors='*')
+ def status(self):
+ result = "
List of drivers and values:
"
+ for path in drivers:
+ result += "- " + path + ":" + str(drivers[path].value) + "
"
+ result += "
"
+ result +=" "
+ return result
+
+ @http.route('/hw_drivers/driverdetails/', type='http', auth='none', cors='*')
+ def statusdetail(self, identifier):
+ for device in drivers:
+ if device.find(identifier) != -1:
+ return str(drivers[device].value)
+ return ''
+
+ @http.route('/hw_drivers/driveraction/', type='json', auth='none', cors='*', csrf=False)
+ def driveraction(self, identifier):
+ data = httprequest.jsonrequest
+ result = 'device not found'
+ if data.get('action') == 'print':
+ with open('/tmp/toprinter', 'w') as file:
+ file.write(data['data'])
+ subprocess.call("cat /tmp/toprinter | base64 -d | lp -d " + identifier, shell=True)
+ result = 'ok'
+ if data.get('action') == 'camera':
+ cameras = subprocess.check_output("v4l2-ctl --list-devices", shell=True).decode('utf-8').split('\n\n')
+ adrress = '/dev/video0'
+ for camera in cameras:
+ if camera:
+ camera = camera.split('\n\t')
+ serial = re.sub('[^a-zA-Z0-9 ]+', '', camera[0].split(': ')[0]).replace(' ','_')
+ if serial == data.get('identifier'):
+ adrress = camera[1]
+ picture = subprocess.check_output("v4l2-ctl --list-formats-ext|grep 'Size'|awk '{print $3}'|sort -rn|awk NR==1", shell=True).decode('utf-8')
+ subprocess.call("fswebcam -d " + adrress + " /tmp/testimage -r " + picture, shell=True)
+ image_bytes = subprocess.check_output('cat /tmp/testimage | base64',shell=True)
+ result = {'image': image_bytes}
+ return result
+
+ @http.route('/hw_drivers/send_iot_box', type='http', auth='none', cors='*')
+ def send_iot_box(self):
+ send_iot_box_device(False)
+ return 'ok'
+
+#----------------------------------------------------------
+# Driver common interface
+#----------------------------------------------------------
+class Driver(Thread):
+ ping_value = ""
+
+ def supported(self):
+ pass
+
+ def value(self):
+ pass
+
+ def get_name(self):
+ pass
+
+ def get_connection(self):
+ pass
+
+ def action(self, action):
+ pass
+
+#----------------------------------------------------------
+# Usb drivers
+#----------------------------------------------------------
+usbdrivers = []
+drivers = {}
+
+class UsbMetaClass(type):
+ def __new__(cls, clsname, bases, attrs):
+ newclass = super(UsbMetaClass, cls).__new__(cls, clsname, bases, attrs)
+ usbdrivers.append(newclass)
+ return newclass
+
+class USBDriver(Driver,metaclass=UsbMetaClass):
+ def __init__(self, dev):
+ super(USBDriver, self).__init__()
+ self.dev = dev
+ self.value = ""
+
+ def get_name(self):
+ lsusb = str(subprocess.check_output('lsusb')).split("\\n")
+ for usbpath in lsusb: # Should filter on usb devices or inverse loops?
+ device = self.dev
+ if "%04x:%04x" % (device.idVendor, device.idProduct) in usbpath:
+ return usbpath.split("%04x:%04x" % (device.idVendor, device.idProduct))[1]
+ return str(device.idVendor) + ":" + str(device.idProduct)
+
+ def get_connection(self):
+ return 'direct'
+
+ def value(self):
+ return self.value
+
+
+#----------------------------------------------------------
+# Bluetooth
+#----------------------------------------------------------
+class GattBtManager(gatt.DeviceManager):
+
+ def device_discovered(self, device):
+ # TODO: need some kind of updated_devices mechanism or not?
+ path = "bt_%s" % (device.mac_address,)
+ if path not in drivers:
+ for driverclass in btdrivers:
+ d = driverclass(device = device, manager=self)
+ if d.supported():
+ drivers[path] = d
+ d.connect()
+ send_iot_box_device(False)
+
+
+class BtManager(Thread):
+ gatt_manager = False
+
+ def run(self):
+ dm = GattBtManager(adapter_name='hci0')
+ self.gatt_manager = dm
+ dm.start_discovery()
+ dm.run()
+
+#----------------------------------------------------------
+# Bluetooth drivers
+#----------------------------------------------------------
+btdrivers = []
+
+class BtMetaClass(type):
+ def __new__(cls, clsname, bases, attrs):
+ newclass = super(BtMetaClass, cls).__new__(cls, clsname, bases, attrs)
+ btdrivers.append(newclass)
+ return newclass
+
+
+class BtDriver(Driver, metaclass=BtMetaClass):
+
+
+ def __init__(self, device, manager):
+ super(BtDriver, self).__init__()
+ self.dev = device
+ self.manager = manager
+ self.value = ''
+ self.gatt_device = False
+
+ def disconnect(self):
+ path = "bt_%s" % (self.dev.mac_address,)
+ del drivers[path]
+
+ def get_name(self):
+ return self.dev.alias()
+
+ def value(self):
+ return self.value
+
+ def action(self, action):
+ pass
+
+ def get_connection(self):
+ return 'bluetooth'
+
+ def connect(self):
+ pass
+
+
+
+
+
+
+class USBDeviceManager(Thread):
+ devices = {}
+ def run(self):
+ first_time = True
+ send_iot_box_device(False)
+ while 1:
+ sendJSON = False
+ devs = usb.core.find(find_all=True)
+ updated_devices = {}
+ for dev in devs:
+ path = "usb_%04x:%04x_%03d_%03d_" % (dev.idVendor, dev.idProduct, dev.bus, dev.address)
+ updated_devices[path] = self.devices.get(path, dev)
+ added = updated_devices.keys() - self.devices.keys()
+ removed = self.devices.keys() - updated_devices.keys()
+ self.devices = updated_devices
+ if (removed):
+ for path in list(drivers):
+ if (path in removed):
+ del drivers[path]
+ sendJSON = True
+ for path in added:
+ dev = updated_devices[path]
+ for driverclass in usbdrivers:
+ d = driverclass(updated_devices[path])
+ if d.supported():
+ _logger.info('For device %s will be driven', path)
+ drivers[path] = d
+ # launch thread
+ d.daemon = True
+ d.start()
+ sendJSON = True
+ if sendJSON or first_time:
+ send_iot_box_device(send_printer = first_time)
+ first_time = False
+ time.sleep(3)
+
+
+
+
+
+def send_iot_box_device(send_printer):
+ maciotbox = subprocess.check_output("/sbin/ifconfig eth0 |grep -Eo ..\(\:..\){5}", shell=True).decode('utf-8').split('\n')[0]
+ server = "" # read from file
+ try:
+ f = open('/home/pi/odoo-remote-server.conf', 'r')
+ for line in f:
+ server += line
+ f.close()
+ except: #In case the file does not exist
+ server=''
+ server = server.split('\n')[0]
+ if server:
+ url = server + "/iot/setup"
+ interfaces = ni.interfaces()
+ for iface_id in interfaces:
+ iface_obj = ni.ifaddresses(iface_id)
+ ifconfigs = iface_obj.get(ni.AF_INET, [])
+ for conf in ifconfigs:
+ if conf.get('addr') and conf.get('addr') != '127.0.0.1':
+ ips = conf.get('addr')
+ break
+
+ # Build device JSON
+ devicesList = {}
+ for path in drivers:
+ device_name = drivers[path].get_name()
+ device_connection = drivers[path].get_connection()
+ identifier = path.split('_')[0] + '_' + path.split('_')[1]
+ devicesList[identifier] = {'name': device_name,
+ 'type': 'device',
+ 'connection': device_connection}
+
+ # Build camera JSON
+ try:
+ cameras = subprocess.check_output("v4l2-ctl --list-devices", shell=True).decode('utf-8').split('\n\n')
+ for camera in cameras:
+ if camera:
+ camera = camera.split('\n\t')
+ serial = re.sub('[^a-zA-Z0-9 ]+', '', camera[0].split(': ')[0]).replace(' ','_')
+ devicesList[serial] = {
+ 'name': camera[0].split(': ')[0],
+ 'connection': 'direct',
+ 'type': 'camera'
+ }
+ except:
+ pass
+
+ # Build printer JSON
+ printerList = {}
+ if send_printer:
+ printers = subprocess.check_output("sudo lpinfo -lv", shell=True).decode('utf-8').split('Device')
+ for printer in printers:
+ printerTab = printer.split('\n')
+ if printer and printerTab[4].split('=')[1] != ' ':
+ device_connection = printerTab[1].split('= ')[1]
+ model = ''
+ for device_id in printerTab[4].split('= ')[1].split(';'):
+ if any(x in device_id for x in ['MDL','MODEL']):
+ model = device_id.split(':')[1]
+ name = printerTab[2].split('= ')[1]
+ serial = re.sub('[^a-zA-Z0-9 ]+', '', model).replace(' ','_')
+ identifier = ''
+ if device_connection == 'direct':
+ identifier = serial + '_' + maciotbox #name + macIOTBOX
+ elif device_connection == 'network' and 'socket' in printerTab[0]:
+ socketIP = printerTab[0].split('://')[1]
+ macprinter = subprocess.check_output("arp -a " + socketIP + " |awk NR==1'{print $4}'", shell=True).decode('utf-8').split('\n')[0]
+ identifier = macprinter # macPRINTER
+ elif device_connection == 'network' and 'dnssd' in printerTab[0]:
+ hostname_printer = subprocess.check_output("ippfind -n \"" + model + "\" | awk \'{split($0,a,\"/\"); print a[3]}\' | awk \'{split($0,b,\":\"); print b[1]}\'", shell=True).decode('utf-8').split('\n')[0]
+ if hostname_printer:
+ macprinter = subprocess.check_output("arp -a " + hostname_printer + " |awk NR==1'{print $4}'", shell=True).decode('utf-8').split('\n')[0]
+ identifier = macprinter # macprinter
+
+ identifier = identifier.replace(':','_')
+ if identifier and identifier not in printerList:
+ printerList[identifier] = {
+ 'name': model,
+ 'connection': device_connection,
+ 'type': 'printer'
+ }
+
+ # install these printers
+ try:
+ ppd = subprocess.check_output("sudo lpinfo -m |grep '" + model + "'", shell=True).decode('utf-8').split('\n')
+ if len(ppd) > 2:
+ subprocess.call("sudo lpadmin -p '" + identifier + "' -E -v '" + printerTab[0].split('= ')[1] + "'", shell=True)
+ else:
+ subprocess.call("sudo lpadmin -p '" + identifier + "' -E -v '" + printerTab[0].split('= ')[1] + "' -m '" + ppd[0].split(' ')[0] + "'", shell=True)
+ except:
+ subprocess.call("sudo lpadmin -p '" + identifier + "' -E -v '" + printerTab[0].split('= ')[1] + "'", shell=True)
+ subprocess.call('> /tmp/printers', shell=True)
+ for printer in printerList:
+ subprocess.call('echo "' + printerList[printer]['name'] + '" >> /tmp/printers', shell=True)
+
+ if devicesList:
+ subprocess.call('> /tmp/devices', shell=True)
+ for device in devicesList:
+ subprocess.call('echo "' + str(device) + '|' + devicesList[device]['name'] + '" >> /tmp/devices', shell=True)
+
+ #build JSON with all devices
+ hostname = subprocess.check_output('hostname').decode('utf-8').split('\n')[0]
+ token = "" # read from file
+ try:
+ f = open('/home/pi/token', 'r')
+ for line in f:
+ token += line
+ f.close()
+ except: #In case the file does not exist
+ token=''
+ token = token.split('\n')[0]
+ data = {'name': hostname,'identifier': maciotbox, 'ip': ips, 'token': token}
+ devicesList.update(printerList)
+ data['devices'] = devicesList
+ data_json = json.dumps(data).encode('utf8')
+ headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
+ http = urllib3.PoolManager()
+ req = False
+ try:
+ req = http.request('POST',
+ url,
+ body=data_json,
+ headers=headers)
+ except:
+ _logger.warning('Could not reach configured server')
+
+
diff --git a/addons/hw_drivers/controllers/loading.py b/addons/hw_drivers/controllers/loading.py
new file mode 100644
index 00000000000..cd02d3dd82f
--- /dev/null
+++ b/addons/hw_drivers/controllers/loading.py
@@ -0,0 +1,34 @@
+import importlib.util
+import os
+from .driver import BtManager
+from .driver import USBDeviceManager
+from odoo import modules
+
+driversList = os.listdir("/home/pi/odoo/addons/hw_drivers/drivers")
+for driver in driversList:
+ path = "/home/pi/odoo/addons/hw_drivers/drivers/" + driver
+ spec = importlib.util.spec_from_file_location(driver, path)
+ if spec:
+ foo = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(foo)
+
+
+if not getattr(modules, '_iot_daemon_started', False):
+ # ----------------------------------------------------------
+ # Bluetooth start
+ # ----------------------------------------------------------
+ bm = BtManager()
+ bm.daemon = True
+ bm.start()
+
+ #----------------------------------------------------------
+ #USB start
+ #----------------------------------------------------------
+
+ udm = USBDeviceManager()
+ udm.daemon = True
+ udm.start()
+
+ # Did this because of the
+ modules._iot_daemon_started = True
+