summaryrefslogtreecommitdiff
path: root/addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py')
-rw-r--r--addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py220
1 files changed, 220 insertions, 0 deletions
diff --git a/addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py b/addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py
new file mode 100644
index 00000000..0b79dd5b
--- /dev/null
+++ b/addons/hw_drivers/iot_handlers/drivers/DisplayDriver.py
@@ -0,0 +1,220 @@
+# -*- 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
+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
+
+path = os.path.realpath(os.path.join(os.path.dirname(__file__), '../../views'))
+loader = jinja2.FileSystemLoader(path)
+
+jinja_env = jinja2.Environment(loader=loader, autoescape=True)
+jinja_env.filters["json"] = json.dumps
+
+pos_display_template = jinja_env.get_template('pos_display.html')
+
+_logger = logging.getLogger(__name__)
+
+
+class DisplayDriver(Driver):
+ connection_type = 'display'
+
+ 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 = ''
+ if self.device_identifier != 'distant_display':
+ self._x_screen = device.get('x_screen', '0')
+ self.load_url()
+
+ @classmethod
+ def supported(cls, device):
+ return True # All devices with connection_type == 'display' are supported
+
+ @classmethod
+ def get_default_display(cls):
+ displays = list(filter(lambda d: iot_devices[d].device_type == 'display', iot_devices))
+ return len(displays) and iot_devices[displays[0]]
+
+ def action(self, data):
+ if data.get('action') == "update_url" and self.device_identifier != 'distant_display':
+ self.update_url(data.get('url'))
+ elif data.get('action') == "display_refresh" and self.device_identifier != 'distant_display':
+ self.call_xdotools('F5')
+ elif data.get('action') == "take_control":
+ self.take_control(self.data['owner'], data.get('html'))
+ elif data.get('action') == "customer_facing_display":
+ self.update_customer_facing_display(self.data['owner'], data.get('html'))
+ elif data.get('action') == "get_owner":
+ self.data = {
+ 'value': '',
+ 'owner': self.owner,
+ }
+ event_manager.device_changed(self)
+
+ def run(self):
+ while self.device_identifier != 'distant_display' and not self._stopped.isSet():
+ time.sleep(60)
+ if self.url != 'http://localhost:8069/point_of_sale/display/' + self.device_identifier:
+ # Refresh the page every minute
+ self.call_xdotools('F5')
+
+ def update_url(self, url=None):
+ os.environ['DISPLAY'] = ":0." + self._x_screen
+ os.environ['XAUTHORITY'] = '/run/lightdm/pi/xauthority'
+ firefox_env = os.environ.copy()
+ firefox_env['HOME'] = '/tmp/' + self._x_screen
+ self.url = url or 'http://localhost:8069/point_of_sale/display/' + self.device_identifier
+ new_window = subprocess.call(['xdotool', 'search', '--onlyvisible', '--screen', self._x_screen, '--class', 'Firefox'])
+ subprocess.Popen(['firefox', self.url], env=firefox_env)
+ if new_window:
+ self.call_xdotools('F11')
+
+ def load_url(self):
+ url = None
+ if helpers.get_odoo_server_url():
+ # disable certifiacte verification
+ urllib3.disable_warnings()
+ http = urllib3.PoolManager(cert_reqs='CERT_NONE')
+ try:
+ 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]
+ except json.decoder.JSONDecodeError:
+ url = response.data.decode('utf8')
+ except Exception:
+ pass
+ return self.update_url(url)
+
+ def call_xdotools(self, keystroke):
+ os.environ['DISPLAY'] = ":0." + self._x_screen
+ os.environ['XAUTHORITY'] = "/run/lightdm/pi/xauthority"
+ try:
+ subprocess.call(['xdotool', 'search', '--sync', '--onlyvisible', '--screen', self._x_screen, '--class', 'Firefox', 'key', keystroke])
+ return "xdotool succeeded in stroking " + keystroke
+ except:
+ return "xdotool threw an error, maybe it is not installed on the IoTBox"
+
+ def update_customer_facing_display(self, origin, html=None):
+ if origin == self.owner:
+ self.rendered_html = html
+ self.event_data.set()
+
+ def get_serialized_order(self):
+ # IMPLEMENTATION OF LONGPOLLING
+ # Times out 2 seconds before the JS request does
+ if self.event_data.wait(28):
+ self.event_data.clear()
+ return {'rendered_html': self.rendered_html}
+ return {'rendered_html': False}
+
+ def take_control(self, new_owner, html=None):
+ # ALLOW A CASHIER TO TAKE CONTROL OVER THE POSBOX, IN CASE OF MULTIPLE CASHIER PER DISPLAY
+ self.owner = new_owner
+ self.rendered_html = html
+ self.data = {
+ 'value': '',
+ 'owner': self.owner,
+ }
+ event_manager.device_changed(self)
+ self.event_data.set()
+
+class DisplayController(http.Controller):
+
+ @http.route('/hw_proxy/display_refresh', type='json', auth='none', cors='*')
+ def display_refresh(self):
+ display = DisplayDriver.get_default_display()
+ if display and display.device_identifier != 'distant_display':
+ return display.call_xdotools('F5')
+
+ @http.route('/hw_proxy/customer_facing_display', type='json', auth='none', cors='*')
+ def customer_facing_display(self, html=None):
+ display = DisplayDriver.get_default_display()
+ if display:
+ display.update_customer_facing_display(http.request.httprequest.remote_addr, html)
+ return {'status': 'updated'}
+ return {'status': 'failed'}
+
+ @http.route('/hw_proxy/take_control', type='json', auth='none', cors='*')
+ def take_control(self, html=None):
+ display = DisplayDriver.get_default_display()
+ if display:
+ display.take_control(http.request.httprequest.remote_addr, html)
+ return {
+ 'status': 'success',
+ 'message': 'You now have access to the display',
+ }
+
+ @http.route('/hw_proxy/test_ownership', type='json', auth='none', cors='*')
+ def test_ownership(self):
+ display = DisplayDriver.get_default_display()
+ if display and display.owner == http.request.httprequest.remote_addr:
+ return {'status': 'OWNER'}
+ return {'status': 'NOWNER'}
+
+ @http.route(['/point_of_sale/get_serialized_order', '/point_of_sale/get_serialized_order/<string:display_identifier>'], type='json', auth='none')
+ def get_serialized_order(self, display_identifier=None):
+ if display_identifier:
+ display = iot_devices.get(display_identifier)
+ else:
+ display = DisplayDriver.get_default_display()
+
+ if display:
+ return display.get_serialized_order()
+ return {
+ 'rendered_html': False,
+ 'error': "No display found",
+ }
+
+ @http.route(['/point_of_sale/display', '/point_of_sale/display/<string:display_identifier>'], type='http', auth='none')
+ def display(self, display_identifier=None):
+ cust_js = None
+ interfaces = ni.interfaces()
+
+ with open(os.path.join(os.path.dirname(__file__), "../../static/src/js/worker.js")) as js:
+ cust_js = js.read()
+
+ display_ifaces = []
+ for iface_id in interfaces:
+ if 'wlan' in iface_id or 'eth' in iface_id:
+ iface_obj = ni.ifaddresses(iface_id)
+ ifconfigs = iface_obj.get(ni.AF_INET, [])
+ essid = helpers.get_ssid()
+ for conf in ifconfigs:
+ if conf.get('addr'):
+ display_ifaces.append({
+ 'iface_id': iface_id,
+ 'essid': essid,
+ 'addr': conf.get('addr'),
+ 'icon': 'sitemap' if 'eth' in iface_id else 'wifi',
+ })
+
+ if not display_identifier:
+ display_identifier = DisplayDriver.get_default_display().device_identifier
+
+ return pos_display_template.render({
+ 'title': "Odoo -- Point of Sale",
+ 'breadcrumb': 'POS Client display',
+ 'cust_js': cust_js,
+ 'display_ifaces': display_ifaces,
+ 'display_identifier': display_identifier,
+ 'pairing_code': connection_manager.pairing_code,
+ })