diff options
| author | Antoine Prieels <anp@odoo.com> | 2020-05-14 07:45:07 +0000 |
|---|---|---|
| committer | Antoine Prieels <anp@odoo.com> | 2020-08-25 06:19:38 +0000 |
| commit | 841c016913a87133bf7257c62ae5f6bf7d99e06d (patch) | |
| tree | b0ed83f8b440e9920ec32c4dd48818847c4725d6 /addons | |
| parent | 6bc4b4178c8033f19f1ed3d7ba22f777ac9b0788 (diff) | |
[IMP] hw_*,point_of_sale: Cleaning IoT Box
- Split driver.py to multiple files
- Remove hw_proxy module as most of its routes were not used and
move remaingin routes to a controller in hw_drivers
- Add missing coding and license lines at the beginning of files
- Reorder imports and remove unused ones
- Rename device_id to device_identifier for clarity
- Rename screen to display for consistency
- Other minors improvements
TaskID: 1973956
Diffstat (limited to 'addons')
32 files changed, 589 insertions, 801 deletions
diff --git a/addons/hw_drivers/__init__.py b/addons/hw_drivers/__init__.py index a03bfd097a6..b36b894717b 100644 --- a/addons/hw_drivers/__init__.py +++ b/addons/hw_drivers/__init__.py @@ -1 +1,10 @@ -from . import controllers
\ No newline at end of file +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import connection_manager +from . import controllers +from . import driver +from . import event_manager +from . import exception_logger +from . import interface +from . import main diff --git a/addons/hw_drivers/connection_manager.py b/addons/hw_drivers/connection_manager.py new file mode 100644 index 00000000000..ec8ba076489 --- /dev/null +++ b/addons/hw_drivers/connection_manager.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from datetime import datetime, timedelta +import logging +import subprocess +import requests +from threading import Thread +import time +import urllib3 + +from odoo.modules.module import get_resource_path +from odoo.addons.hw_drivers.main import iot_devices, manager +from odoo.addons.hw_drivers.tools import helpers + +_logger = logging.getLogger(__name__) + +class ConnectionManager(Thread): + def __init__(self): + super(ConnectionManager, self).__init__() + self.pairing_code = False + self.pairing_uuid = False + + def run(self): + if not helpers.get_odoo_server_url(): + end_time = datetime.now() + timedelta(minutes=5) + while (datetime.now() < end_time): + self._connect_box() + time.sleep(10) + self.pairing_code = False + self.pairing_uuid = False + self._refresh_displays() + + def _connect_box(self): + data = { + 'jsonrpc': 2.0, + 'params': { + 'pairing_code': self.pairing_code, + 'pairing_uuid': self.pairing_uuid, + } + } + + urllib3.disable_warnings() + req = requests.post('https://iot-proxy.odoo.com/odoo-enterprise/iot/connect-box', json=data, verify=False) + result = req.json().get('result', {}) + + if all(key in result for key in ['pairing_code', 'pairing_uuid']): + self.pairing_code = result['pairing_code'] + self.pairing_uuid = result['pairing_uuid'] + elif 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): + 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']) + # 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"]) + + def _refresh_displays(self): + """Refresh all displays to hide the pairing code""" + for d in iot_devices: + if iot_devices[d].device_type == 'display': + iot_devices[d].action({ + 'action': 'display_refresh' + }) + +connection_manager = ConnectionManager() +connection_manager.daemon = True +connection_manager.start() diff --git a/addons/hw_drivers/controllers/__init__.py b/addons/hw_drivers/controllers/__init__.py index 8a59703b3c1..ad287d4d087 100644 --- a/addons/hw_drivers/controllers/__init__.py +++ b/addons/hw_drivers/controllers/__init__.py @@ -1 +1,5 @@ -from . import driver
\ No newline at end of file +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import driver +from . import proxy diff --git a/addons/hw_drivers/controllers/driver.py b/addons/hw_drivers/controllers/driver.py index 46605790bf2..39ac2408947 100755 --- a/addons/hw_drivers/controllers/driver.py +++ b/addons/hw_drivers/controllers/driver.py @@ -1,40 +1,32 @@ -#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + from base64 import b64decode -from datetime import datetime, timedelta -from dbus.mainloop.glib import DBusGMainLoop -from importlib import util import json import logging import os -import requests -import socket import subprocess -import sys -from threading import Thread, Event -import time -import urllib3 from odoo import http, tools from odoo.http import send_file from odoo.modules.module import get_resource_path + +from odoo.addons.hw_drivers.event_manager import event_manager +from odoo.addons.hw_drivers.main import iot_devices, manager from odoo.addons.hw_drivers.tools import helpers _logger = logging.getLogger(__name__) -#---------------------------------------------------------- -# Controllers -#---------------------------------------------------------- - -class StatusController(http.Controller): +class DriverController(http.Controller): @http.route('/hw_drivers/action', type='json', auth='none', cors='*', csrf=False, save_session=False) - def action(self, session_id, device_id, data): + def action(self, session_id, device_identifier, data): """ This route is called when we want to make a action with device (take picture, printing,...) We specify in data from which session_id that action is called And call the action of specific device """ - iot_device = iot_devices.get(device_id) + iot_device = iot_devices.get(device_identifier) if iot_device: iot_device.data['owner'] = session_id data = json.loads(data) @@ -53,7 +45,7 @@ class StatusController(http.Controller): @http.route('/hw_drivers/event', type='json', auth='none', cors='*', csrf=False, save_session=False) def event(self, listener): """ - listener is a dict in witch there are a sessions_id and a dict of device_id to listen + listener is a dict in witch there are a sessions_id and a dict of device_identifier to listen """ req = event_manager.add_request(listener) if req['event'].wait(50): @@ -82,7 +74,7 @@ class StatusController(http.Controller): 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']) - m.send_alldevices() + manager.send_alldevices() image = get_resource_path('hw_drivers', 'static/img', 'True.jpg') helpers.odoo_restart(3) except subprocess.CalledProcessError as e: @@ -100,355 +92,3 @@ class StatusController(http.Controller): res = send_file(tools.config['logfile'], mimetype="text/plain", as_attachment=True) res.headers['Cache-Control'] = 'no-cache' return res - -#---------------------------------------------------------- -# Log Exceptions -#---------------------------------------------------------- - -class ExceptionLogger: - """ - Redirect Exceptions to the logger to keep track of them in the log file. - """ - - def __init__(self): - self.logger = logging.getLogger() - - def write(self, message): - if message != '\n': - self.logger.error(message) - - def flush(self): - pass - -sys.stderr = ExceptionLogger() - -#---------------------------------------------------------- -# Drivers -#---------------------------------------------------------- - -drivers = [] -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 = sorted([d for d in drivers if d.connection_type == self.connection_type], key=lambda d: d.priority, reverse=True) - - 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) - if hasattr(newclass, 'priority'): - newclass.priority += 1 - else: - newclass.priority = 0 - drivers.append(newclass) - return newclass - -class Driver(Thread, metaclass=DriverMetaClass): - """ - Hook to register the driver into the drivers list - """ - connection_type = "" - - def __init__(self, device): - super(Driver, self).__init__() - self.dev = device - self.data = {'value': ''} - self._device_manufacturer = '' - - @property - def device_name(self): - return self._device_name - - @property - def device_identifier(self): - return self.dev.identifier - - @property - def device_manufacturer(self): - return self._device_manufacturer - - @property - def device_connection(self): - """ - On specific driver override this method to give connection type of device - return string - possible value : direct - network - bluetooth - serial - hdmi - """ - return self._device_connection - - @property - def device_type(self): - """ - On specific driver override this method to give type of device - return string - possible value : printer - camera - keyboard - scanner - display - device - """ - return self._device_type - - @classmethod - def supported(cls, device): - """ - On specific driver override this method to check if device is supported or not - return True or False - """ - pass - - def get_message(self): - return '' - - def action(self, data): - """ - On specific driver override this method to make a action with device (take picture, printing,...) - """ - raise NotImplementedError() - - def disconnect(self): - del iot_devices[self.device_identifier] - - -#---------------------------------------------------------- -# Device manager -#---------------------------------------------------------- - -class EventManager(object): - def __init__(self): - self.sessions = {} - - def _delete_expired_sessions(self, max_time=70): - ''' - Clears sessions that are no longer called. - - :param max_time: time a session can stay unused before being deleted - ''' - now = time.time() - expired_sessions = [session for session in self.sessions if now - self.sessions[session]['time_request'] > max_time] - for session in expired_sessions: - del self.sessions[session] - - def add_request(self, listener): - self.session = { - 'session_id': listener['session_id'], - 'devices': listener['devices'], - 'event': Event(), - 'result': {}, - 'time_request': time.time(), - } - self._delete_expired_sessions() - self.sessions[listener['session_id']] = self.session - return self.sessions[listener['session_id']] - - def device_changed(self, device): - for session in self.sessions: - if device.device_identifier in self.sessions[session]['devices']: - self.sessions[session]['result'] = device.data - self.sessions[session]['result']['device_id'] = device.device_identifier - self.sessions[session]['event'].set() - - -event_manager = EventManager() - -#---------------------------------------------------------- -# ConnectionManager -#---------------------------------------------------------- - -class ConnectionManager(Thread): - def __init__(self): - super(ConnectionManager, self).__init__() - self.pairing_code = False - self.pairing_uuid = False - - def run(self): - if not helpers.get_odoo_server_url(): - end_time = datetime.now() + timedelta(minutes=5) - while (datetime.now() < end_time): - self._connect_box() - time.sleep(10) - self.pairing_code = False - self.pairing_uuid = False - self._refresh_displays() - - def _connect_box(self): - data = { - 'jsonrpc': 2.0, - 'params': { - 'pairing_code': self.pairing_code, - 'pairing_uuid': self.pairing_uuid, - } - } - - urllib3.disable_warnings() - req = requests.post('https://iot-proxy.odoo.com/odoo-enterprise/iot/connect-box', json=data, verify=False) - result = req.json().get('result', {}) - - if all(key in result for key in ['pairing_code', 'pairing_uuid']): - self.pairing_code = result['pairing_code'] - self.pairing_uuid = result['pairing_uuid'] - elif 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): - 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']) - # Notify the DB, so that the kanban view already shows the IoT Box - m.send_alldevices() - # Restart to checkout the git branch, get a certificate, load the IoT handlers... - subprocess.check_call(["sudo", "service", "odoo", "restart"]) - - def _refresh_displays(self): - """Refresh all displays to hide the pairing code""" - for d in iot_devices: - if iot_devices[d].device_type == 'display': - iot_devices[d].action({ - 'action': 'display_refresh' - }) - -#---------------------------------------------------------- -# Manager -#---------------------------------------------------------- - -class Manager(Thread): - - def load_iot_handlers(self): - """ - 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_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() - - def send_alldevices(self): - """ - This method send IoT Box and devices informations to Odoo database - """ - server = helpers.get_odoo_server_url() - if server: - subject = helpers.read_file_first_line('odoo-subject.conf') - if subject: - domain = helpers.get_ip().replace('.', '-') + subject.strip('*') - else: - domain = helpers.get_ip() - iot_box = { - 'name': socket.gethostname(), - 'identifier': helpers.get_mac_address(), - 'ip': domain, - 'token': helpers.get_token(), - 'version': helpers.get_version() - } - devices_list = {} - for device in iot_devices: - identifier = iot_devices[device].device_identifier - devices_list[identifier] = { - 'name': iot_devices[device].device_name, - 'type': iot_devices[device].device_type, - 'manufacturer': iot_devices[device].device_manufacturer, - 'connection': iot_devices[device].device_connection, - } - data = { - 'params': { - 'iot_box' : iot_box, - 'devices' : devices_list, - } - } - # disable certifiacte verification - urllib3.disable_warnings() - http = urllib3.PoolManager(cert_reqs='CERT_NONE') - try: - http.request( - 'POST', - server + "/iot/setup", - body = json.dumps(data).encode('utf8'), - headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} - ) - except Exception as e: - _logger.error('Could not reach configured server') - _logger.error('A error encountered : %s ' % e) - else: - _logger.warning('Odoo server not set') - - 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() - self.send_alldevices() - self.load_iot_handlers() - self.previous_iot_devices = [] - for interface in interfaces.values(): - i = interface() - i.daemon = True - i.start() - while 1: - if iot_devices != self.previous_iot_devices: - self.send_alldevices() - self.previous_iot_devices = iot_devices.copy() - time.sleep(3) - -# Must be started from main thread -DBusGMainLoop(set_as_default=True) - -cm = ConnectionManager() -cm.daemon = True -cm.start() - -m = Manager() -m.daemon = True -m.start() diff --git a/addons/hw_drivers/controllers/proxy.py b/addons/hw_drivers/controllers/proxy.py new file mode 100644 index 00000000000..b5124772d2a --- /dev/null +++ b/addons/hw_drivers/controllers/proxy.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import http + +proxy_drivers = {} + +class ProxyController(http.Controller): + @http.route('/hw_proxy/hello', type='http', auth='none', cors='*') + def hello(self): + return "ping" + + @http.route('/hw_proxy/handshake', type='json', auth='none', cors='*') + def handshake(self): + return True + + @http.route('/hw_proxy/status_json', type='json', auth='none', cors='*') + def status_json(self): + statuses = {} + for driver in proxy_drivers: + statuses[driver] = proxy_drivers[driver].get_status() + return statuses diff --git a/addons/hw_drivers/driver.py b/addons/hw_drivers/driver.py new file mode 100644 index 00000000000..bfe60effc6b --- /dev/null +++ b/addons/hw_drivers/driver.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from threading import Thread + +from odoo.addons.hw_drivers.main import drivers, iot_devices + + +class DriverMetaClass(type): + def __new__(cls, clsname, bases, attrs): + newclass = super(DriverMetaClass, cls).__new__(cls, clsname, bases, attrs) + if hasattr(newclass, 'priority'): + newclass.priority += 1 + else: + newclass.priority = 0 + drivers.append(newclass) + return newclass + + +class Driver(Thread, metaclass=DriverMetaClass): + """ + Hook to register the driver into the drivers list + """ + connection_type = '' + + def __init__(self, identifier, device): + super(Driver, self).__init__() + self.dev = device + self.device_identifier = identifier + self.device_name = '' + self.device_connection = '' + self.device_type = '' + self.device_manufacturer = '' + self.data = {'value': ''} + + @classmethod + def supported(cls, device): + """ + On specific driver override this method to check if device is supported or not + return True or False + """ + return False + + def action(self, data): + """ + On specific driver override this method to make a action with device (take picture, printing,...) + """ + raise NotImplementedError() + + def disconnect(self): + del iot_devices[self.device_identifier] diff --git a/addons/hw_drivers/event_manager.py b/addons/hw_drivers/event_manager.py new file mode 100644 index 00000000000..59d30ea2318 --- /dev/null +++ b/addons/hw_drivers/event_manager.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from threading import Event +import time + + +class EventManager(object): + def __init__(self): + self.sessions = {} + + def _delete_expired_sessions(self, max_time=70): + ''' + Clears sessions that are no longer called. + + :param max_time: time a session can stay unused before being deleted + ''' + now = time.time() + expired_sessions = [ + session + for session in self.sessions + if now - self.sessions[session]['time_request'] > max_time + ] + for session in expired_sessions: + del self.sessions[session] + + def add_request(self, listener): + self.session = { + 'session_id': listener['session_id'], + 'devices': listener['devices'], + 'event': Event(), + 'result': {}, + 'time_request': time.time(), + } + self._delete_expired_sessions() + self.sessions[listener['session_id']] = self.session + return self.sessions[listener['session_id']] + + def device_changed(self, device): + for session in self.sessions: + if device.device_identifier in self.sessions[session]['devices']: + self.sessions[session]['result'] = device.data + self.sessions[session]['result']['device_identifier'] = device.device_identifier + self.sessions[session]['event'].set() + + +event_manager = EventManager() diff --git a/addons/hw_drivers/exception_logger.py b/addons/hw_drivers/exception_logger.py new file mode 100644 index 00000000000..0d1890b5f25 --- /dev/null +++ b/addons/hw_drivers/exception_logger.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +import sys + + +class ExceptionLogger: + """ + Redirect Exceptions to the logger to keep track of them in the log file. + """ + + def __init__(self): + self.logger = logging.getLogger() + + def write(self, message): + if message != '\n': + self.logger.error(message) + + def flush(self): + pass + +sys.stderr = ExceptionLogger() diff --git a/addons/hw_drivers/interface.py b/addons/hw_drivers/interface.py new file mode 100644 index 00000000000..28ad1a55e0a --- /dev/null +++ b/addons/hw_drivers/interface.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +from threading import Thread +import time + +from odoo.addons.hw_drivers.main import drivers, interfaces, iot_devices + +_logger = logging.getLogger(__name__) + + +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 = sorted([d for d in drivers if d.connection_type == self.connection_type], key=lambda d: d.priority, reverse=True) + + 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 identifier in removed: + if identifier in iot_devices: + iot_devices[identifier].disconnect() + _logger.info('Device %s is now disconnected', identifier) + + for identifier in added: + for driver in self.drivers: + if driver.supported(devices[identifier]): + _logger.info('Device %s is now connected', identifier) + d = driver(identifier, devices[identifier]) + d.daemon = True + d.start() + iot_devices[identifier] = d + break + + def get_devices(self): + raise NotImplementedError() diff --git a/addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py b/addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py index b2076a58109..98854b98885 100644 --- a/addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py +++ b/addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py @@ -1,17 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + import jinja2 import json import logging import netifaces as ni import os -from pathlib import Path import subprocess import threading import time import urllib3 from odoo import http +from odoo.addons.hw_drivers.connection_manager import connection_manager +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 -from odoo.addons.hw_drivers.controllers.driver import Driver, event_manager, iot_devices, cm path = os.path.realpath(os.path.join(os.path.dirname(__file__), '../../views')) loader = jinja2.FileSystemLoader(path) @@ -27,11 +32,11 @@ _logger = logging.getLogger(__name__) class DisplayDriver(Driver): connection_type = 'display' - def __init__(self, device): - super(DisplayDriver, self).__init__(device) - self._device_type = 'display' - self._device_connection = 'hdmi' - self._device_name = device['name'] + def __init__(self, identifier, device): + super(DisplayDriver, self).__init__(identifier, device) + self.device_type = 'display' + self.device_connection = 'hdmi' + self.device_name = device['name'] self.event_data = threading.Event() self.owner = False self.rendered_html = '' @@ -39,10 +44,6 @@ class DisplayDriver(Driver): self._x_screen = device.get('x_screen', '0') self.load_url() - @property - def device_identifier(self): - return self.dev['identifier'] - @classmethod def supported(cls, device): return True # All devices with connection_type == 'display' are supported @@ -93,7 +94,7 @@ class DisplayDriver(Driver): urllib3.disable_warnings() http = urllib3.PoolManager(cert_reqs='CERT_NONE') try: - response = http.request('GET', "%s/iot/box/%s/screen_url" % (helpers.get_odoo_server_url(), helpers.get_mac_address())) + response = http.request('GET', "%s/iot/box/%s/display_url" % (helpers.get_odoo_server_url(), helpers.get_mac_address())) if response.status == 200: data = json.loads(response.data.decode('utf8')) url = data[self.device_identifier] @@ -215,5 +216,5 @@ class DisplayController(http.Controller): 'cust_js': cust_js, 'display_ifaces': display_ifaces, 'display_identifier': display_identifier, - 'pairing_code': cm.pairing_code, + 'pairing_code': connection_manager.pairing_code, }) diff --git a/addons/hw_drivers/iot_handlers/drivers/KeyboardUSBDriver.py b/addons/hw_drivers/iot_handlers/drivers/KeyboardUSBDriver.py index 9425456ca9a..8c27f20b569 100644 --- a/addons/hw_drivers/iot_handlers/drivers/KeyboardUSBDriver.py +++ b/addons/hw_drivers/iot_handlers/drivers/KeyboardUSBDriver.py @@ -8,17 +8,19 @@ import logging from lxml import etree import os from pathlib import Path +from queue import Queue, Empty import subprocess -import time from threading import Lock -import usb +import time import urllib3 -from queue import Queue, Empty +from usb import util from odoo import http, _ -from odoo.addons.hw_proxy.controllers.main import drivers as old_drivers +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 -from odoo.addons.hw_drivers.controllers.driver import event_manager, Driver, iot_devices _logger = logging.getLogger(__name__) xlib = ctypes.cdll.LoadLibrary('libX11.so.6') @@ -29,14 +31,14 @@ class KeyboardUSBDriver(Driver): keyboard_layout_groups = [] available_layouts = [] - def __init__(self, device): + def __init__(self, identifier, device): if not hasattr(KeyboardUSBDriver, 'display'): os.environ['XAUTHORITY'] = "/run/lightdm/pi/xauthority" KeyboardUSBDriver.display = xlib.XOpenDisplay(bytes(":0.0", "utf-8")) - super(KeyboardUSBDriver, self).__init__(device) - self._device_connection = 'direct' - self._device_name = self._set_name() + super(KeyboardUSBDriver, self).__init__(identifier, device) + self.device_connection = 'direct' + self.device_name = self._set_name() # from https://github.com/xkbcommon/libxkbcommon/blob/master/test/evdev-scancodes.h self._scancode_to_modifier = { @@ -52,9 +54,9 @@ class KeyboardUSBDriver(Driver): KeyboardUSBDriver.load_layouts_list() KeyboardUSBDriver.send_layouts_list() - for device in [evdev.InputDevice(path) for path in evdev.list_devices()]: - if (self.dev.idVendor == device.info.vendor) and (self.dev.idProduct == device.info.product): - self.input_device = device + for evdev_device in [evdev.InputDevice(path) for path in evdev.list_devices()]: + if (device.idVendor == evdev_device.info.vendor) and (device.idProduct == evdev_device.info.product): + self.input_device = evdev_device self._set_device_type('scanner') if self._is_scanner() else self._set_device_type() @@ -108,12 +110,8 @@ class KeyboardUSBDriver(Driver): def _set_name(self): try: - if usb.__version__ == '1.0.0b1': - manufacturer = usb.util.get_string(self.dev, 256, self.dev.iManufacturer) - product = usb.util.get_string(self.dev, 256, self.dev.iProduct) - else: - manufacturer = usb.util.get_string(self.dev, self.dev.iManufacturer) - product = usb.util.get_string(self.dev, self.dev.iProduct) + manufacturer = util.get_string(self.dev, self.dev.iManufacturer) + product = util.get_string(self.dev, self.dev.iProduct) return ("%s - %s") % (manufacturer, product) except ValueError as e: _logger.warning(e) @@ -134,7 +132,6 @@ class KeyboardUSBDriver(Driver): self.data['value'] = '' event_manager.device_changed(self) - def run(self): try: for event in self.input_device.read_loop(): @@ -228,7 +225,7 @@ class KeyboardUSBDriver(Driver): """Read the device type from the saved filed and set it as current type. If no file or no device type is found we try to detect it automatically. """ - device_name = self._device_name.lower() + device_name = self.device_name.lower() scanner_name = ['barcode', 'scanner', 'reader'] is_scanner = any(x in device_name for x in scanner_name) or self.dev.interface_protocol == '0' @@ -276,14 +273,14 @@ class KeyboardUSBDriver(Driver): type (string): Type wanted to switch """ if device_type == 'scanner': - self._device_type = 'scanner' + self.device_type = 'scanner' self.key_input = self._barcode_scanner_input self._barcodes = Queue() self._current_barcode = '' self.input_device.grab() self.read_barcode_lock = Lock() else: - self._device_type = 'keyboard' + self.device_type = 'keyboard' self.key_input = self._keyboard_input self.load_layout() @@ -355,8 +352,8 @@ class KeyboardUSBDriver(Driver): except Empty: return '' +proxy_drivers['scanner'] = KeyboardUSBDriver -old_drivers['scanner'] = KeyboardUSBDriver class KeyboardUSBController(http.Controller): @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*') diff --git a/addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py b/addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py index 7fd387f8a62..01041920583 100644 --- a/addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py +++ b/addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py @@ -4,21 +4,24 @@ from base64 import b64decode from cups import IPPError, IPP_PRINTER_IDLE, IPP_PRINTER_PROCESSING, IPP_PRINTER_STOPPED import dbus +import io import logging import netifaces as ni import os -import io -import base64 +from PIL import Image, ImageOps import re import subprocess import tempfile -from PIL import Image, ImageOps +from uuid import getnode as get_mac -from odoo import http, _ -from odoo.addons.hw_drivers.controllers.driver import event_manager, Driver, iot_devices, cm +from odoo import http +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.main import iot_devices from odoo.addons.hw_drivers.tools import helpers -from odoo.addons.hw_proxy.controllers.main import drivers as old_drivers _logger = logging.getLogger(__name__) @@ -48,15 +51,15 @@ def print_star_error(deviceId): process = subprocess.Popen(["lp", "-d", deviceId], stdin=subprocess.PIPE) process.communicate(error_page.encode("utf-8")) -def cups_notification_handler(message, uri, device_id, state, reason, accepting_jobs): - if device_id in iot_devices: +def cups_notification_handler(message, uri, device_identifier, state, reason, accepting_jobs): + if device_identifier in iot_devices: reason = reason if reason != 'none' else None state_value = { IPP_PRINTER_IDLE: 'connected', IPP_PRINTER_PROCESSING: 'processing', IPP_PRINTER_STOPPED: 'stopped' } - iot_devices[device_id].update_status(state_value[state], message, reason) + iot_devices[device_identifier].update_status(state_value[state], message, reason) # Create a Cups subscription if it doesn't exist yet try: @@ -76,18 +79,18 @@ bus.add_signal_receiver(cups_notification_handler, signal_name="PrinterStateChan class PrinterDriver(Driver): connection_type = 'printer' - def __init__(self, device): - super(PrinterDriver, self).__init__(device) - self._device_type = 'printer' - self._device_connection = self.dev['device-class'].lower() - self._device_name = self.dev['device-make-and-model'] + def __init__(self, identifier, device): + super(PrinterDriver, self).__init__(identifier, device) + self.device_type = 'printer' + self.device_connection = device['device-class'].lower() + self.device_name = device['device-make-and-model'] self.state = { 'status': 'connecting', 'message': 'Connecting to printer', 'reason': None, } self.send_status() - if 'direct' in self._device_connection and 'CMD:ESC/POS;' in self.dev['device-id']: + if 'direct' in self.device_connection and 'CMD:ESC/POS;' in device['device-id']: self.print_status() @classmethod @@ -137,15 +140,11 @@ class PrinterDriver(Driver): 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': ''} - @property - def device_identifier(self): - return self.dev['identifier'] - def action(self, data): if data.get('action') == 'cashbox': self.open_cashbox() elif data.get('action') == 'print_receipt': - self.print_receipt(base64.b64decode(data['receipt'])) + self.print_receipt(b64decode(data['receipt'])) else: self.print_raw(b64decode(data['document'])) @@ -257,7 +256,7 @@ class PrinterDriver(Driver): mac = '\nMAC Address:\n%s\n' % helpers.get_mac_address() homepage = '\nHomepage:\nhttp://%s:8069\n\n' % main_ips - code = cm.pairing_code + code = connection_manager.pairing_code if code: pairing_code = '\nPairing Code:\n%s\n' % code @@ -286,4 +285,4 @@ class PrinterController(http.Controller): return True return False -old_drivers['printer'] = PrinterDriver +proxy_drivers['printer'] = PrinterDriver diff --git a/addons/hw_drivers/iot_handlers/drivers/SerialBaseDriver.py b/addons/hw_drivers/iot_handlers/drivers/SerialBaseDriver.py index 37a63f57548..6c3f1ceebb7 100644 --- a/addons/hw_drivers/iot_handlers/drivers/SerialBaseDriver.py +++ b/addons/hw_drivers/iot_handlers/drivers/SerialBaseDriver.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import logging -import time -import traceback from collections import namedtuple -from threading import Lock from contextlib import contextmanager - +import logging import serial +from threading import Lock +import time +import traceback -from odoo.tools.translate import _ -from odoo.addons.hw_drivers.controllers.driver import Driver, event_manager +from odoo import _ +from odoo.addons.hw_drivers.event_manager import event_manager +from odoo.addons.hw_drivers.driver import Driver _logger = logging.getLogger(__name__) @@ -58,26 +58,22 @@ class SerialDriver(Driver): STATUS_ERROR = 'error' STATUS_CONNECTING = 'connecting' - def __init__(self, device): + def __init__(self, identifier, device): """ Attributes initialization method for `SerialDriver`. :param device: path to the device :type device: str """ - super().__init__(device) + super(SerialDriver, self).__init__(identifier, device) self._actions = { 'get_status': self._push_status, } - self._device_connection = 'serial' + self.device_connection = 'serial' self._device_lock = Lock() self._status = {'status': self.STATUS_CONNECTING, 'message_title': '', 'message_body': ''} self._set_name() - @property - def device_identifier(self): - return self.dev['identifier'] - def _get_raw_response(connection): pass @@ -91,10 +87,10 @@ class SerialDriver(Driver): """Tries to build the device's name based on its type and protocol name but falls back on a default name if that doesn't work.""" try: - name = ('%s serial %s' % (self._protocol.name, self._device_type)).title() + name = ('%s serial %s' % (self._protocol.name, self.device_type)).title() except Exception: name = 'Unknown Serial Device' - self._device_name = name + self.device_name = name def _take_measure(self): pass @@ -126,7 +122,7 @@ class SerialDriver(Driver): if self._connection and self._connection.isOpen(): self._do_action(data) else: - with serial_connection(self.dev['identifier'], self._protocol) as connection: + with serial_connection(self.device_identifier, self._protocol) as connection: self._connection = connection self._do_action(data) @@ -134,7 +130,7 @@ class SerialDriver(Driver): """Continuously gets new measures from the device.""" try: - with serial_connection(self.dev['identifier'], self._protocol) as connection: + with serial_connection(self.device_identifier, self._protocol) as connection: self._connection = connection self._status['status'] = self.STATUS_CONNECTED self._push_status() diff --git a/addons/hw_drivers/iot_handlers/drivers/SerialScaleDriver.py b/addons/hw_drivers/iot_handlers/drivers/SerialScaleDriver.py index 116beac799f..cc1d5469cad 100644 --- a/addons/hw_drivers/iot_handlers/drivers/SerialScaleDriver.py +++ b/addons/hw_drivers/iot_handlers/drivers/SerialScaleDriver.py @@ -1,18 +1,16 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - -import threading +from collections import namedtuple import logging import re -import time -from collections import namedtuple - import serial +import threading +import time 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.controllers.proxy import proxy_drivers +from odoo.addons.hw_drivers.event_manager import event_manager from odoo.addons.hw_drivers.iot_handlers.drivers.SerialBaseDriver import SerialDriver, SerialProtocol, serial_connection @@ -95,9 +93,9 @@ class ScaleDriver(SerialDriver): """Abstract base class for scale drivers.""" last_sent_value = None - def __init__(self, device): - self._device_type = 'scale' - super().__init__(device) + def __init__(self, identifier, device): + super(ScaleDriver, self).__init__(identifier, device) + self.device_type = 'scale' self._set_actions() self._is_reading = True @@ -105,7 +103,7 @@ class ScaleDriver(SerialDriver): # Only the last scale connected is kept global ACTIVE_SCALE ACTIVE_SCALE = self - old_drivers['scale'] = ACTIVE_SCALE + proxy_drivers['scale'] = ACTIVE_SCALE # Ensures compatibility with older versions of Odoo # and allows using the `ProxyDevice` in the point of sale to retrieve the status @@ -212,9 +210,9 @@ class Toledo8217Driver(ScaleDriver): """Driver for the Toldedo 8217 serial scale.""" _protocol = Toledo8217Protocol - def __init__(self, device): - super().__init__(device) - self._device_manufacturer = 'Toledo' + def __init__(self, identifier, device): + super(Toledo8217Driver, self).__init__(identifier, device) + self.device_manufacturer = 'Toledo' @classmethod def supported(cls, device): @@ -249,11 +247,11 @@ class AdamEquipmentDriver(ScaleDriver): _protocol = ADAMEquipmentProtocol priority = 0 # Test the supported method of this driver last, after all other serial drivers - def __init__(self, device): - super().__init__(device) + def __init__(self, identifier, device): + super(AdamEquipmentDriver, self).__init__(identifier, device) self._is_reading = False self._last_weight_time = 0 - self._device_manufacturer = 'Adam' + self.device_manufacturer = 'Adam' def _check_last_weight_time(self): """The ADAM doesn't make the difference between a value of 0 and "the same value as last time": diff --git a/addons/hw_drivers/iot_handlers/interfaces/DisplayInterface.py b/addons/hw_drivers/iot_handlers/interfaces/DisplayInterface.py index 075da7a4e08..57e7a4afadc 100644 --- a/addons/hw_drivers/iot_handlers/interfaces/DisplayInterface.py +++ b/addons/hw_drivers/iot_handlers/interfaces/DisplayInterface.py @@ -1,7 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + from re import sub, finditer import subprocess -from odoo.addons.hw_drivers.controllers.driver import Interface +from odoo.addons.hw_drivers.interface import Interface class DisplayInterface(Interface): @@ -29,7 +32,6 @@ class DisplayInterface(Interface): 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", } diff --git a/addons/hw_drivers/iot_handlers/interfaces/PrinterInterface.py b/addons/hw_drivers/iot_handlers/interfaces/PrinterInterface.py index e5d7dd89010..ca601dbce04 100644 --- a/addons/hw_drivers/iot_handlers/interfaces/PrinterInterface.py +++ b/addons/hw_drivers/iot_handlers/interfaces/PrinterInterface.py @@ -1,8 +1,11 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + from cups import Connection as cups_connection from re import sub from threading import Lock -from odoo.addons.hw_drivers.controllers.driver import Interface +from odoo.addons.hw_drivers.interface import Interface conn = cups_connection() PPDs = conn.getPPDs() diff --git a/addons/hw_drivers/iot_handlers/interfaces/SerialInterface.py b/addons/hw_drivers/iot_handlers/interfaces/SerialInterface.py index 82be2731a06..ae63969782f 100644 --- a/addons/hw_drivers/iot_handlers/interfaces/SerialInterface.py +++ b/addons/hw_drivers/iot_handlers/interfaces/SerialInterface.py @@ -1,6 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + from glob import glob -from odoo.addons.hw_drivers.controllers.driver import Interface +from odoo.addons.hw_drivers.interface import Interface class SerialInterface(Interface): diff --git a/addons/hw_drivers/iot_handlers/interfaces/USBInterface.py b/addons/hw_drivers/iot_handlers/interfaces/USBInterface.py index a1608189335..a40e82d6421 100644 --- a/addons/hw_drivers/iot_handlers/interfaces/USBInterface.py +++ b/addons/hw_drivers/iot_handlers/interfaces/USBInterface.py @@ -1,6 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + from usb import core -from odoo.addons.hw_drivers.controllers.driver import Interface +from odoo.addons.hw_drivers.interface import Interface class USBInterface(Interface): @@ -18,9 +21,9 @@ class USBInterface(Interface): 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 + identifier = "usb_%04x:%04x" % (dev.idVendor, dev.idProduct) + if identifier in usb_devices: + identifier += '_%s' % cpt cpt += 1 - usb_devices[dev.identifier] = dev + usb_devices[identifier] = dev return usb_devices diff --git a/addons/hw_drivers/main.py b/addons/hw_drivers/main.py new file mode 100644 index 00000000000..ccbbed52e92 --- /dev/null +++ b/addons/hw_drivers/main.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from dbus.mainloop.glib import DBusGMainLoop +import json +import logging +import socket +from threading import Thread +import time +import urllib3 + +from odoo.addons.hw_drivers.tools import helpers + +_logger = logging.getLogger(__name__) + +drivers = [] +interfaces = {} +iot_devices = {} + + +class Manager(Thread): + def send_alldevices(self): + """ + This method send IoT Box and devices informations to Odoo database + """ + server = helpers.get_odoo_server_url() + if server: + subject = helpers.read_file_first_line('odoo-subject.conf') + if subject: + domain = helpers.get_ip().replace('.', '-') + subject.strip('*') + else: + domain = helpers.get_ip() + iot_box = { + 'name': socket.gethostname(), + 'identifier': helpers.get_mac_address(), + 'ip': domain, + 'token': helpers.get_token(), + 'version': helpers.get_version(), + } + devices_list = {} + for device in iot_devices: + identifier = iot_devices[device].device_identifier + devices_list[identifier] = { + 'name': iot_devices[device].device_name, + 'type': iot_devices[device].device_type, + 'manufacturer': iot_devices[device].device_manufacturer, + 'connection': iot_devices[device].device_connection, + } + data = {'params': {'iot_box': iot_box, 'devices': devices_list,}} + # disable certifiacte verification + urllib3.disable_warnings() + http = urllib3.PoolManager(cert_reqs='CERT_NONE') + try: + http.request( + 'POST', + server + "/iot/setup", + body=json.dumps(data).encode('utf8'), + headers={ + 'Content-type': 'application/json', + 'Accept': 'text/plain', + }, + ) + except Exception as e: + _logger.error('Could not reach configured server') + _logger.error('A error encountered : %s ' % e) + else: + _logger.warning('Odoo server not set') + + def run(self): + """ + Thread that will load interfaces and drivers and contact the odoo server with the updates + """ + + helpers.check_git_branch() + helpers.check_certificate() + + # We first add the IoT Box to the connected DB because IoT handlers cannot be downloaded if + # the identifier of the Box is not found in the DB. So add the Box to the DB. + self.send_alldevices() + helpers.download_iot_handlers() + helpers.load_iot_handlers() + + # Start the interfaces + for interface in interfaces.values(): + i = interface() + i.daemon = True + i.start() + + # Check every 3 secondes if the list of connected devices has changed and send the updated + # list to the connected DB. + self.previous_iot_devices = [] + while 1: + if iot_devices != self.previous_iot_devices: + self.send_alldevices() + self.previous_iot_devices = iot_devices.copy() + time.sleep(3) + + +# Must be started from main thread +DBusGMainLoop(set_as_default=True) + +manager = Manager() +manager.daemon = True +manager.start() diff --git a/addons/hw_drivers/tools/helpers.py b/addons/hw_drivers/tools/helpers.py index ad825e4e1cc..2c2a3b171a0 100644 --- a/addons/hw_drivers/tools/helpers.py +++ b/addons/hw_drivers/tools/helpers.py @@ -1,21 +1,22 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import netifaces -from pathlib import Path import datetime -from OpenSSL import crypto -import urllib3 +from importlib import util import io import json import logging +import netifaces +from OpenSSL import crypto import os +from pathlib import Path import subprocess +import urllib3 import zipfile from threading import Thread import time -from odoo import _ +from odoo import _, http from odoo.modules.module import get_resource_path _logger = logging.getLogger(__name__) @@ -232,6 +233,24 @@ def download_iot_handlers(auto=True): _logger.error('Could not reach configured server') _logger.error('A error encountered : %s ' % e) +def load_iot_handlers(): + """ + 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 + """ + 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() + def odoo_restart(delay): IR = IoTRestart(delay) IR.start() diff --git a/addons/hw_escpos/__manifest__.py b/addons/hw_escpos/__manifest__.py index dedb4b6bde9..de2e3d442d5 100644 --- a/addons/hw_escpos/__manifest__.py +++ b/addons/hw_escpos/__manifest__.py @@ -16,7 +16,6 @@ to open ESC/POS controlled cashdrawers in the point of sale and other modules that would need such functionality. """, - 'depends': ['hw_proxy'], 'external_dependencies': { 'python' : ['pyusb','pyserial','qrcode'], }, diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index 160cee39342..123a8a41cec 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -27,7 +27,7 @@ except ImportError: usb = None from odoo import http, _ -from odoo.addons.hw_proxy.controllers import main as hw_proxy +from odoo.addons.hw_drivers.controllers import proxy _logger = logging.getLogger(__name__) @@ -78,10 +78,7 @@ class EscposDriver(Thread): for printer in printers: try: - if usb.__version__ == '1.0.0b1': - description = usb.util.get_string(printer, 256, printer.iManufacturer) + " " + usb.util.get_string(printer, 256, printer.iProduct) - else: - description = usb.util.get_string(printer, printer.iManufacturer) + " " + usb.util.get_string(printer, printer.iProduct) + description = usb.util.get_string(printer, printer.iManufacturer) + " " + usb.util.get_string(printer, printer.iProduct) except Exception as e: _logger.error("Can not get printer description: %s" % e) description = 'Unknown printer' @@ -326,9 +323,10 @@ class EscposDriver(Thread): driver = EscposDriver() -hw_proxy.drivers['escpos'] = driver +proxy.proxy_drivers['escpos'] = driver + -class EscposProxy(hw_proxy.Proxy): +class EscposProxy(proxy.ProxyController): @http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*') def open_cashbox(self): diff --git a/addons/hw_posbox_homepage/__manifest__.py b/addons/hw_posbox_homepage/__manifest__.py index e3117a519fe..83c63262445 100644 --- a/addons/hw_posbox_homepage/__manifest__.py +++ b/addons/hw_posbox_homepage/__manifest__.py @@ -19,6 +19,5 @@ If you activate this module, you won't be able to access the regular Odoo interface anymore. """, - 'depends': ['hw_proxy'], 'installable': False, } diff --git a/addons/hw_posbox_homepage/controllers/main.py b/addons/hw_posbox_homepage/controllers/main.py index ed050364988..138d0190ded 100644 --- a/addons/hw_posbox_homepage/controllers/main.py +++ b/addons/hw_posbox_homepage/controllers/main.py @@ -1,26 +1,23 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import logging import json import jinja2 -import subprocess -import socket -import sys -import netifaces -import odoo -from odoo import http +import logging import os -from odoo.tools import misc from pathlib import Path +import socket +import subprocess +import sys import threading -from odoo.addons.hw_proxy.controllers import main as hw_proxy -from odoo.addons.web.controllers import main as web +from odoo import http +from odoo.http import Response from odoo.modules.module import get_resource_path + +from odoo.addons.hw_drivers.main import iot_devices from odoo.addons.hw_drivers.tools import helpers -from odoo.addons.hw_drivers.controllers.driver import iot_devices -from odoo.http import Response +from odoo.addons.web.controllers import main as web _logger = logging.getLogger(__name__) @@ -80,7 +77,7 @@ class IoTboxHomepage(web.Home): iot_device.append({ 'name': iot_devices[device].device_name + ' : ' + str(iot_devices[device].data['value']), 'type': iot_devices[device].device_type.replace('_', ' '), - 'message': iot_devices[device].device_identifier + iot_devices[device].get_message() + 'identifier': iot_devices[device].device_identifier, }) return { diff --git a/addons/hw_posbox_homepage/views/homepage.html b/addons/hw_posbox_homepage/views/homepage.html index 06f255d978e..482c5451489 100644 --- a/addons/hw_posbox_homepage/views/homepage.html +++ b/addons/hw_posbox_homepage/views/homepage.html @@ -27,7 +27,7 @@ .device-status { margin: 6px 0; } - .device-status .message { + .device-status .identifier { font-size: 0.8rem; max-width: 350px; } @@ -130,7 +130,7 @@ {% for device in iot_devices.list %} <div class="device-status"> <span class="device">{{ device['name'] }}</span> - <div class="message">{{ device['message'] }}</div> + <div class="identifier">{{ device['identifier'] }}</div> </div> {% endfor %} </div> diff --git a/addons/hw_posbox_homepage/views/upgrade_page.html b/addons/hw_posbox_homepage/views/upgrade_page.html index 0c87fc62d78..b9c2ae1d0e7 100644 --- a/addons/hw_posbox_homepage/views/upgrade_page.html +++ b/addons/hw_posbox_homepage/views/upgrade_page.html @@ -13,7 +13,7 @@ url:'/hw_proxy/perform_upgrade/' }).done(function() { $('.message-title').text('Upgrade successful'); - var cpt = 110; + var cpt = 25; setInterval(function() { --cpt; if (cpt === 0) {location.reload();} diff --git a/addons/hw_proxy/__init__.py b/addons/hw_proxy/__init__.py deleted file mode 100644 index dd15b5c32e5..00000000000 --- a/addons/hw_proxy/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from . import controllers diff --git a/addons/hw_proxy/__manifest__.py b/addons/hw_proxy/__manifest__.py deleted file mode 100644 index 9250ced17d3..00000000000 --- a/addons/hw_proxy/__manifest__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -{ - 'name': 'Hardware Proxy', - 'category': 'Sales/Point of Sale', - 'sequence': 6, - 'summary': 'Connect the Web Client to Hardware Peripherals', - 'website': 'https://www.odoo.com/page/point-of-sale-hardware', - '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_proxy/controllers/__init__.py b/addons/hw_proxy/controllers/__init__.py deleted file mode 100644 index 5d4b25db9c0..00000000000 --- a/addons/hw_proxy/controllers/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from . import main diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py deleted file mode 100644 index 797644a395d..00000000000 --- a/addons/hw_proxy/controllers/main.py +++ /dev/null @@ -1,218 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from __future__ import print_function -import logging -import subprocess -import time -import subprocess -from threading import Lock - - -from odoo import http -from odoo.http import request - -_logger = logging.getLogger(__name__) - - -# Those are the builtin raspberry pi USB modules, they should -# not appear in the list of connected devices. -BANNED_DEVICES = { - "0424:9514", # Standard Microsystem Corp. Builtin Ethernet module - "1d6b:0002", # Linux Foundation 2.0 root hub - "0424:ec00", # Standard Microsystem Corp. Other Builtin Ethernet module - "0424:2514", # Standard Microsystems Corp. USB 2.0 Hub (rpi3b+) - "0424:7800", # Standard Microsystems Corp. (rpi3b+) -} - - -# drivers modules must add to drivers an object with a get_status() method -# so that 'status' can return the status of all active drivers -drivers = {} - -# keep a list of RS-232 devices that have been recognized by a driver, -# so other drivers can skip them during probes -rs232_devices = {} # {'/path/to/device': 'driver'} -rs232_lock = Lock() # must be held to update `rs232_devices` - -class Proxy(http.Controller): - - def get_status(self): - statuses = {} - for driver in drivers: - statuses[driver] = drivers[driver].get_status() - return statuses - - @http.route('/hw_proxy/hello', type='http', auth='none', cors='*') - def hello(self): - return "ping" - - @http.route('/hw_proxy/handshake', type='json', auth='none', cors='*') - def handshake(self): - return True - - @http.route('/hw_proxy/status', type='http', auth='none', cors='*') - def status_http(self, debug=None, **kwargs): - resp = """ -<!DOCTYPE HTML> -<html> - <head> - <title>Odoo's IoTBox</title> - <style> - body { - width: 480px; - margin: 60px auto; - font-family: sans-serif; - text-align: justify; - color: #6B6B6B; - } - .device { - border-bottom: solid 1px rgb(216,216,216); - padding: 9px; - } - .device:nth-child(2n) { - background:rgb(240,240,240); - } - </style> - </head> - <body> - <h1>Hardware Status</h1> - <p>The list of enabled drivers and their status</p> -""" - statuses = self.get_status() - for driver in statuses: - - status = statuses[driver] - - if status['status'] == 'connecting': - color = 'black' - elif status['status'] == 'connected': - color = 'green' - else: - color = 'red' - - resp += "<h3 style='color:"+color+";'>"+driver+' : '+status['status']+"</h3>\n" - resp += "<ul>\n" - for msg in status['messages']: - resp += '<li>'+msg+'</li>\n' - resp += "</ul>\n" - resp += """ - <h2>Connected Devices</h2> - <p>The list of connected USB devices as seen by the IoTBox</p> - """ - if debug is None: - resp += """(<a href="/hw_proxy/status?debug=1">debug version</a>)""" - devices = subprocess.check_output("lsusb").decode('utf-8').split('\n') - count = 0 - resp += "<div class='devices'>\n" - for device in devices: - device_name = device[device.find('ID')+2:] - if device_name: # to avoid last empty line - device_id = device_name.split()[0] - if not (device_id in BANNED_DEVICES): - resp += "<div class='device' data-device='"+device+"'>"+device_name+"</div>\n" - count += 1 - - if count == 0: - resp += "<div class='device'>No USB Device Found</div>" - - resp += "</div>\n</body>\n</html>\n\n" - - if debug is not None: - resp += """ - - <h3>Debug version</h3> - <p><tt>lsusb -v</tt> output:</p> - <pre> - %s - </pre> - - """ % subprocess.check_output(['lsusb', '-v']).decode('utf-8') - - return request.make_response(resp,{ - 'Cache-Control': 'no-cache', - 'Content-Type': 'text/html; charset=utf-8', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET', - }) - - @http.route('/hw_proxy/status_json', type='json', auth='none', cors='*') - def status_json(self): - return self.get_status() - - @http.route('/hw_proxy/scan_item_success', type='json', auth='none', cors='*') - def scan_item_success(self, ean): - """ - A product has been scanned with success - """ - print('scan_item_success: %s', ean) - - @http.route('/hw_proxy/scan_item_error_unrecognized', type='json', auth='none', cors='*') - def scan_item_error_unrecognized(self, ean): - """ - A product has been scanned without success - """ - print('scan_item_error_unrecognized: %s', ean) - - @http.route('/hw_proxy/help_needed', type='json', auth='none', cors='*') - def help_needed(self): - """ - The user wants an help (ex: light is on) - """ - print("help_needed") - - @http.route('/hw_proxy/help_canceled', type='json', auth='none', cors='*') - def help_canceled(self): - """ - The user stops the help request - """ - print("help_canceled") - - @http.route('/hw_proxy/payment_request', type='json', auth='none', cors='*') - def payment_request(self, price): - """ - The PoS will activate the method payment - """ - print("payment_request: price:%s" % price) - return 'ok' - - @http.route('/hw_proxy/payment_status', type='json', auth='none', cors='*') - def payment_status(self): - print("payment_status") - return { 'status':'waiting' } - - @http.route('/hw_proxy/payment_cancel', type='json', auth='none', cors='*') - def payment_cancel(self): - print("payment_cancel") - - @http.route('/hw_proxy/transaction_start', type='json', auth='none', cors='*') - def transaction_start(self): - print('transaction_start') - - @http.route('/hw_proxy/transaction_end', type='json', auth='none', cors='*') - def transaction_end(self): - print('transaction_end') - - @http.route('/hw_proxy/cashier_mode_activated', type='json', auth='none', cors='*') - def cashier_mode_activated(self): - print('cashier_mode_activated') - - @http.route('/hw_proxy/cashier_mode_deactivated', type='json', auth='none', cors='*') - def cashier_mode_deactivated(self): - print('cashier_mode_deactivated') - - @http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*') - def open_cashbox(self): - print('open_cashbox') - - @http.route('/hw_proxy/print_receipt', type='json', auth='none', cors='*') - def print_receipt(self, receipt): - print('print_receipt %s', receipt) - - @http.route('/hw_proxy/log', type='json', auth='none', cors='*') - def log(self, arguments): - _logger.info(' '.join(str(v) for v in arguments)) - - @http.route('/hw_proxy/print_pdf_invoice', type='json', auth='none', cors='*') - def print_pdf_invoice(self, pdfinvoice): - print('print_pdf_invoice %s' % pdfinvoice) diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo index 8b346ec8dcb..8c9559a477c 100755 --- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo +++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo @@ -31,7 +31,7 @@ function _start() { while ! test -a /run/systemd/timesync/synchronized && ! ip route | grep 10.11.12.1 && ((cnt--)); do sleep 1 done - start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_escpos,hw_drivers --limit-time-cpu=600 --limit-time-real=1200 + start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_posbox_homepage,hw_escpos,hw_drivers --limit-time-cpu=600 --limit-time-real=1200 } function _stop() { diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh index 5a961b032e5..f5af6ee7ee5 100755 --- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh +++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh @@ -27,77 +27,65 @@ apt-get update && apt-get -y upgrade # Firmware 4.44 seems to prevent the LED mechanism from working PKGS_TO_INSTALL=" - fswebcam \ - nginx-full \ - dnsmasq \ - dbus \ - dbus-x11 \ + console-data \ cups \ - printer-driver-all \ cups-ipp-utils \ - libcups2-dev \ - pcscd \ - localepurge \ - vim \ - mc \ - mg \ - screen \ - iw \ - hostapd \ + dbus \ + dbus-x11 \ + dnsmasq \ + firefox-esr \ + fswebcam \ git \ - rsync \ + hostapd \ + iw \ kpartx \ - swig \ - console-data \ + libcups2-dev \ + libpq-dev \ lightdm \ - xserver-xorg-video-fbdev \ - xserver-xorg-input-evdev \ - firefox-esr \ - xdotool \ - unclutter \ - x11-utils \ - xserver-xorg-video-dummy \ + localepurge \ + nginx-full \ openbox \ - rpi-update \ - adduser \ - libpq-dev \ + printer-driver-all \ python-cups \ python3 \ - python3-pyscard \ - python3-urllib3 \ + python3-babel \ python3-dateutil \ python3-decorator \ + python3-dev \ python3-docutils \ python3-feedparser \ - python3-pil \ + python3-html2text \ python3-jinja2 \ python3-ldap \ + python3-libsass \ python3-lxml \ python3-mako \ python3-mock \ - python3-openid \ + python3-netifaces \ + python3-passlib \ + python3-pil \ + python3-pip \ python3-psutil \ python3-psycopg2 \ - python3-babel \ python3-pydot \ python3-pyparsing \ python3-pypdf2 \ + python3-qrcode \ python3-reportlab \ python3-requests \ - python3-simplejson \ - python3-stdnum \ + python3-serial \ python3-tz \ + python3-urllib3 \ python3-werkzeug \ - python3-serial \ - python3-pip \ - python3-dev \ - python3-netifaces \ - python3-passlib \ - python3-libsass \ - python3-qrcode \ - python3-html2text \ - python3-unittest2 \ - python3-simplejson" + rsync \ + screen \ + unclutter \ + vim \ + x11-utils \ + xdotool \ + xserver-xorg-input-evdev \ + xserver-xorg-video-dummy \ + xserver-xorg-video-fbdev" echo "Acquire::Retries "16";" > /etc/apt/apt.conf.d/99acquire-retries # KEEP OWN CONFIG FILES DURING PACKAGE CONFIGURATION @@ -114,12 +102,12 @@ rm -rfv /usr/share/doc # so we install the latest pyusb that works with this libusb. # Even in stretch, we had an error with langid (but worked otherwise) PIP_TO_INSTALL=" - pyusb==1.0.0b1 \ evdev \ gatt \ - v4l2 \ polib \ - pycups" + pycups \ + pyusb \ + v4l2" pip3 install ${PIP_TO_INSTALL} |
