Skip to content

Commit

Permalink
[IMP] hw_*: Add possibility to run IoT in Windows
Browse files Browse the repository at this point in the history
Currently it is only possible to run the modules
for the IoT Box on Raspios.
The modifications brought by this commit brings the possibility
that the various hw_* modules can be executed whatever the OS
of the hardware (Linux or Windows).
If the modules should be versioned according to the OS,
the file will be renamed so that the end of
the file includes *_L (for linux) or *_W (for windows)

closes odoo#107350

X-original-commit: b7b8809
Related: odoo/enterprise#34713
Signed-off-by: Quentin Lejeune (qle) <qle@odoo.com>
  • Loading branch information
qle-odoo committed Dec 7, 2022
1 parent 355b004 commit b49e7e4
Show file tree
Hide file tree
Showing 27 changed files with 470 additions and 226 deletions.
7 changes: 2 additions & 5 deletions addons/hw_drivers/connection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,12 @@ def _connect_box(self):
_logger.error('A error encountered : %s ' % e)

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'])
helpers.save_conf_server(url, token, db_uuid, enterprise_code)
# 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"])
helpers.odoo_restart(2)

def _refresh_displays(self):
"""Refresh all displays to hide the pairing code"""
Expand Down
30 changes: 0 additions & 30 deletions addons/hw_drivers/controllers/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,36 +65,6 @@ def event(self, listener):
req['result']['session_id'] = req['session_id']
return req['result']

@http.route('/hw_drivers/box/connect', type='http', auth='none', cors='*', csrf=False, save_session=False)
def connect_box(self, token):
"""
This route is called when we want that a IoT Box will be connected to a Odoo DB
token is a base 64 encoded string and have 2 argument separate by |
1 - url of odoo DB
2 - token. This token will be compared to the token of Odoo. He have 1 hour lifetime
"""
server = helpers.get_odoo_server_url()
image = get_resource_path('hw_drivers', 'static/img', 'False.jpg')
if not server:
credential = b64decode(token).decode('utf-8').split('|')
url = credential[0]
token = credential[1]
if len(credential) > 2:
# IoT Box send token with db_uuid and enterprise_code only since V13
db_uuid = credential[2]
enterprise_code = credential[3]
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'])
manager.send_alldevices()
image = get_resource_path('hw_drivers', 'static/img', 'True.jpg')
helpers.odoo_restart(3)
except subprocess.CalledProcessError as e:
_logger.error('A error encountered : %s ' % e.output)
if os.path.isfile(image):
with open(image, 'rb') as f:
return f.read()

@http.route('/hw_drivers/download_logs', type='http', auth='none', cors='*', csrf=False, save_session=False)
def download_logs(self):
"""
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
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.iot_handlers.interfaces.PrinterInterface_L import PPDs, conn, cups_lock
from odoo.addons.hw_drivers.main import iot_devices
from odoo.addons.hw_drivers.tools import helpers

Expand Down
155 changes: 155 additions & 0 deletions addons/hw_drivers/iot_handlers/drivers/PrinterDriver_W.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from PIL import Image, ImageOps
import logging
from base64 import b64decode
import io
import win32print
import ghostscript

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

_logger = logging.getLogger(__name__)

RECEIPT_PRINTER_COMMANDS = {
'star': {
'center': b'\x1b\x1d\x61\x01', # ESC GS a n
'cut': b'\x1b\x64\x02', # ESC d n
'title': b'\x1b\x69\x01\x01%s\x1b\x69\x00\x00', # ESC i n1 n2
'drawers': [b'\x07', b'\x1a'] # BEL & SUB
},
'escpos': {
'center': b'\x1b\x61\x01', # ESC a n
'cut': b'\x1d\x56\x41\n', # GS V m
'title': b'\x1b\x21\x30%s\x1b\x21\x00', # ESC ! n
'drawers': [b'\x1b\x3d\x01', b'\x1b\x70\x00\x19\x19', b'\x1b\x70\x01\x19\x19'] # ESC = n then ESC p m t1 t2
}
}

class PrinterDriver(Driver):
connection_type = 'printer'

def __init__(self, identifier, device):
super().__init__(identifier, device)
self.device_type = 'printer'
self.device_connection = 'network'
self.device_name = device.get('identifier')
self.printer_handle = device.get('printer_handle')
self.state = {
'status': 'connecting',
'message': 'Connecting to printer',
'reason': None,
}
self.send_status()

self._actions.update({
'cashbox': self.open_cashbox,
'print_receipt': self.print_receipt,
'': self._action_default,
})

self.receipt_protocol = 'escpos'

@classmethod
def supported(cls, device):
return True

@classmethod
def get_status(cls):
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': ''}

def disconnect(self):
self.update_status('disconnected', 'Printer was disconnected')
super(PrinterDriver, self).disconnect()

def update_status(self, status, message, reason=None):
"""Updates the state of the current printer.
Args:
status (str): The new value of the status
message (str): A comprehensive message describing the status
reason (str): The reason fo the current status
"""
if self.state['status'] != status or self.state['reason'] != reason:
self.state = {
'status': status,
'message': message,
'reason': reason,
}
self.send_status()

def send_status(self):
""" Sends the current status of the printer to the connected Odoo instance.
"""
self.data = {
'value': '',
'state': self.state,
}
event_manager.device_changed(self)

def print_raw(self, data):
win32print.StartDocPrinter(self.printer_handle, 1, ('', None, "RAW"))
win32print.StartPagePrinter(self.printer_handle)
win32print.WritePrinter(self.printer_handle, data)
win32print.EndPagePrinter(self.printer_handle)
win32print.EndDocPrinter(self.printer_handle)

def print_report(self, data):
helpers.write_file('document.pdf', data, 'wb')
file_name = helpers.path_file('document.pdf')
printer = self.device_name

args = [
"-dPrinted", "-dBATCH", "-dNOSAFER", "-dNOPAUSE", "-dNOPROMPT"
"-q",
"-sDEVICE#mswinpr2",
f'-sOutputFile#%printer%{printer}',
f'{file_name}'
]

ghostscript.Ghostscript(*args)

def print_receipt(self, data):
receipt = b64decode(data['receipt'])
im = Image.open(io.BytesIO(receipt))

# Convert to greyscale then to black and white
im = im.convert("L")
im = ImageOps.invert(im)
im = im.convert("1")

print_command = getattr(self, 'format_%s' % self.receipt_protocol)(im)
self.print_raw(print_command)

def format_escpos(self, im):
width = int((im.width + 7) / 8)

raster_send = b'\x1d\x76\x30\x00'
max_slice_height = 255

raster_data = b''
dots = im.tobytes()
while dots:
im_slice = dots[:width*max_slice_height]
slice_height = int(len(im_slice) / width)
raster_data += raster_send + width.to_bytes(2, 'little') + slice_height.to_bytes(2, 'little') + im_slice
dots = dots[width*max_slice_height:]

return raster_data + RECEIPT_PRINTER_COMMANDS['escpos']['cut']

def open_cashbox(self, data):
"""Sends a signal to the current printer to open the connected cashbox."""
commands = RECEIPT_PRINTER_COMMANDS[self.receipt_protocol]
for drawer in commands['drawers']:
self.print_raw(drawer)

def _action_default(self, data):
self.print_report(b64decode(data['document']))

proxy_drivers['printer'] = PrinterDriver
24 changes: 24 additions & 0 deletions addons/hw_drivers/iot_handlers/interfaces/PrinterInterface_W.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import win32print

from odoo.addons.hw_drivers.interface import Interface

class PrinterInterface(Interface):
_loop_delay = 30
connection_type = 'printer'

def get_devices(self):
printer_devices = {}
printers = win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL)

for printer in printers:
identifier = printer[2]
handle_printer = win32print.OpenPrinter(identifier)
win32print.GetPrinter(handle_printer, 2)
printer_devices[identifier] = {
'identifier': identifier,
'printer_handle': handle_printer,
}
return printer_devices
8 changes: 4 additions & 4 deletions addons/hw_drivers/iot_handlers/interfaces/SerialInterface.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from glob import glob
import serial.tools.list_ports

from odoo.addons.hw_drivers.interface import Interface

Expand All @@ -11,8 +11,8 @@ class SerialInterface(Interface):

def get_devices(self):
serial_devices = {}
for identifier in glob('/dev/serial/by-path/*'):
serial_devices[identifier] = {
'identifier': identifier
for port in serial.tools.list_ports.comports():
serial_devices[port.device] = {
'identifier': port.device
}
return serial_devices
19 changes: 13 additions & 6 deletions addons/hw_drivers/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from traceback import format_exc

from dbus.mainloop.glib import DBusGMainLoop
import json
import platform
import logging
import socket
from threading import Thread
Expand All @@ -14,6 +13,12 @@

_logger = logging.getLogger(__name__)

try:
from dbus.mainloop.glib import DBusGMainLoop
except ImportError:
DBusGMainLoop = None
_logger.error('Could not import library dbus')

drivers = []
interfaces = {}
iot_devices = {}
Expand Down Expand Up @@ -72,7 +77,9 @@ def run(self):
Thread that will load interfaces and drivers and contact the odoo server with the updates
"""

helpers.check_git_branch()
helpers.start_nginx_server()
if platform.system() == 'Linux':
helpers.check_git_branch()
helpers.check_certificate()

# We first add the IoT Box to the connected DB because IoT handlers cannot be downloaded if
Expand All @@ -93,16 +100,16 @@ def run(self):
while 1:
try:
if iot_devices != self.previous_iot_devices:
self.send_alldevices()
self.previous_iot_devices = iot_devices.copy()
self.send_alldevices()
time.sleep(3)
except Exception:
# No matter what goes wrong, the Manager loop needs to keep running
_logger.error(format_exc())


# Must be started from main thread
DBusGMainLoop(set_as_default=True)
if DBusGMainLoop:
DBusGMainLoop(set_as_default=True)

manager = Manager()
manager.daemon = True
Expand Down
Binary file removed addons/hw_drivers/static/img/False.jpg
Binary file not shown.
Binary file removed addons/hw_drivers/static/img/True.jpg
Binary file not shown.
Loading

0 comments on commit b49e7e4

Please sign in to comment.