[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
X-original-commit: 841c016913a87133bf7257c62ae5f6bf7d99e06d
This commit is contained in:
Antoine Prieels
2020-08-25 11:24:56 +00:00
parent 703178f09b
commit 72681dc53f
33 changed files with 604 additions and 816 deletions
+10 -1
View File
@@ -1 +1,10 @@
from . import controllers
# -*- 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
+74
View File
@@ -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()
+5 -1
View File
@@ -1 +1,5 @@
from . import driver
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import driver
from . import proxy
+11 -371
View File
@@ -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()
+22
View File
@@ -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
+51
View File
@@ -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]
+47
View File
@@ -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()
+23
View File
@@ -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()
+59
View File
@@ -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()
@@ -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,
})
@@ -8,17 +8,19 @@ import logging
from lxml import etree
import os
from pathlib import Path
import subprocess
import time
from threading import Lock
import usb
import urllib3
from queue import Queue, Empty
import subprocess
from threading import Lock
import time
import urllib3
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='*')
@@ -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
@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import namedtuple
from contextlib import contextmanager
import logging
import serial
from threading import Lock
import time
import traceback
from collections import namedtuple
from threading import Lock
from contextlib import contextmanager
import serial
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()
@@ -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":
@@ -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",
}
@@ -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()
@@ -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):
@@ -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
+104
View File
@@ -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()
+24 -5
View File
@@ -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()
-1
View File
@@ -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'],
},
+5 -7
View File
@@ -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):
@@ -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,
}
+11 -14
View File
@@ -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.modules.module import get_resource_path
from odoo.addons.hw_drivers.tools import helpers
from odoo.addons.hw_drivers.controllers.driver import iot_devices
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.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 {
@@ -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>
@@ -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();}
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
-21
View File
@@ -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,
}
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main
-218
View File
@@ -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)
@@ -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() {
@@ -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 \
console-data \
cups \
cups-ipp-utils \
dbus \
dbus-x11 \
cups \
printer-driver-all \
cups-ipp-utils \
libcups2-dev \
pcscd \
localepurge \
vim \
mc \
mg \
screen \
iw \
hostapd \
git \
rsync \
kpartx \
swig \
console-data \
lightdm \
xserver-xorg-video-fbdev \
xserver-xorg-input-evdev \
dnsmasq \
firefox-esr \
xdotool \
unclutter \
x11-utils \
xserver-xorg-video-dummy \
openbox \
rpi-update \
adduser \
fswebcam \
git \
hostapd \
iw \
kpartx \
libcups2-dev \
libpq-dev \
lightdm \
localepurge \
nginx-full \
openbox \
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-tz \
python3-werkzeug \
python3-serial \
python3-pip \
python3-dev \
python3-netifaces \
python3-passlib \
python3-libsass \
python3-qrcode \
python3-html2text \
python3-unittest2 \
python3-simplejson"
python3-tz \
python3-urllib3 \
python3-werkzeug \
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}
+9 -9
View File
@@ -61,7 +61,7 @@ determined connection type. Creating a new interface requires
.. code-block:: python
from odoo.addons.hw_drivers.controllers.driver import Interface
from odoo.addons.hw_drivers.interface import Interface
class InterfaceName(Interface):
connection_type = 'ConnectionType'
@@ -96,16 +96,16 @@ Creating a new Driver requires:
.. code-block:: python
from odoo.addons.hw_drivers.controllers.driver import Driver
from odoo.addons.hw_drivers.driver import Driver
class DriverName(Driver):
connection_type = 'ConnectionType'
def __init__(self, device):
super(NewDriver, self).__init__(device)
self._device_type = 'DeviceType'
self._device_connection = 'DeviceConnection'
self._device_name = 'DeviceName'
def __init__(self, identifier, device):
super(NewDriver, self).__init__(identifier, device)
self.device_type = 'DeviceType'
self.device_connection = 'DeviceConnection'
self.device_name = 'DeviceName'
@classmethod
def supported(cls, device):
@@ -128,7 +128,7 @@ instantiated using the IP of the IoT Box and the device identifier.
.. code-block:: javascript
var DeviceProxy = require('iot.widgets').DeviceProxy;
var DeviceProxy = require('iot.DeviceProxy');
var iot_device = new DeviceProxy({
iot_ip: iot_ip,
@@ -182,7 +182,7 @@ with `self.data` as argument.
.. code-block:: python
from odoo.addons.hw_drivers.controllers.driver import event_manager
from odoo.addons.hw_drivers.event_manager import event_manager
class DriverName(Driver):
connection_type = 'ConnectionType'