summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine Prieels <anp@odoo.com>2020-10-19 09:32:19 +0000
committerAntoine Prieels <anp@odoo.com>2020-10-20 11:28:37 +0000
commitcd0545b98fc1e7a66dd46eb6a283736ef58ac4ff (patch)
tree3dbdb307eebebc4688f63c57fd58cad465a5984d
parentf997e673b7c326797e00dd70d4b2e5684be8c45b (diff)
[IMP] hw_drivers: Support for Star receipt printers
Add support for Star Line Mode protocol, used by most Star receipt printers. closes odoo/odoo#60365 Taskid: 2365648 X-original-commit: 2cf0df13394e0b468afa348c6b1913f35239be5b Signed-off-by: Quentin Lejeune (qle) <qle@odoo.com>
-rw-r--r--addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py145
1 files changed, 58 insertions, 87 deletions
diff --git a/addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py b/addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py
index f3558be545b..bbd79f1cb4e 100644
--- a/addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py
+++ b/addons/hw_drivers/iot_handlers/drivers/PrinterDriver.py
@@ -25,31 +25,20 @@ from odoo.addons.hw_drivers.tools import helpers
_logger = logging.getLogger(__name__)
-def print_star_error(deviceId):
- """We communicate with receipt printers using the ESCPOS protocol.
- By default, Star printers use the Star mode. This can be changed by
- modifying the position of the DIP-Switches at the bottom of the printer.
- """
- error_page = (
- "\x1B\x1D\x61\x01" # Centering start
- "\x1B\x69\x01\x01Bad configuration\x1B\x69\x00\x00" # Title, double size
- "\n\n--------------------\n\n"
- "\x1B\x1D\x61\x00" # Centering Stop
- "Your printer is in Star line mode, but should\n"
- "use ESC/POS mode. You will not be able to print\n"
- "receipts without changing your configuration.\n\n"
- "For more details and instructions on how to\n"
- "configure your printer, please refer to:\n\n"
- "\x1B\x1D\x61\x01" # Centering start
- "\x1B\x2D\x01" # Underline start
- "http://www.odoo.com" # TODO: Replace URL
- "\x0A\x0A"
- "\x1B\x2D\x00" # Underline stop
- "\x1B\x1D\x61\x00" # Centering stop
- "\x1B\x64\x02" # Full Cut
- )
- process = subprocess.Popen(["lp", "-d", deviceId], stdin=subprocess.PIPE)
- process.communicate(error_page.encode("utf-8"))
+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
+ }
+}
def cups_notification_handler(message, uri, device_identifier, state, reason, accepting_jobs):
if device_identifier in iot_devices:
@@ -90,7 +79,9 @@ class PrinterDriver(Driver):
'reason': None,
}
self.send_status()
- if 'direct' in self.device_connection and 'CMD:ESC/POS;' in device['device-id']:
+
+ self.receipt_protocol = 'star' if 'STR_T' in device['device-id'] else 'escpos'
+ if 'direct' in self.device_connection and any(cmd in device['device-id'] for cmd in ['CMD:STAR;', 'CMD:ESC/POS;']):
self.print_status()
@classmethod
@@ -116,11 +107,7 @@ class PrinterDriver(Driver):
conn.setPrinterUsersAllowed(device['identifier'], ['all'])
conn.addPrinterOptionDefault(device['identifier'], "usb-no-reattach", "true")
conn.addPrinterOptionDefault(device['identifier'], "usb-unidir", "true")
- if 'STR_T' in device['device-id']:
- # Star printers have either STR_T or ESP in their name depending on the protocol used.
- print_star_error(device['identifier'])
- else:
- return True
+ return True
return False
@classmethod
@@ -189,52 +176,40 @@ class PrinterDriver(Driver):
im = ImageOps.invert(im)
im = im.convert("1")
- '''ESC a n
- - ESC a: Select justification, in hex: "1B 61"
- - n: Justification:
- - 0: left
- - 1: center
- - 2: right
- '''
- center = b'\x1b\x61\x01'
-
- '''GS v0 m x y
- - GS v0: Print raster bit image, in hex: "1D 76 30"
- - m: Density mode:
- - 0: 180dpi x 180dpi
- - 1: 180dpi x 90dpi
- - 2: 90dpi x 180 dpi
- - 3: 90dpi x 90 dpi
- - x: Length in X direction, in bytes, represented as 2 bytes in little endian
- --> Must be <= 255
- - y: Length in Y direction, in dots, represented as 2 bytes in little endian
- '''
- width_pixels, height_pixels = im.size
- width_bytes = int((width_pixels + 7) / 8)
- print_command = b"\x1d\x76\x30" + b'\x00' + (width_bytes).to_bytes(2, 'little') + height_pixels.to_bytes(2, 'little')
-
- # There is a height limit when printing images, so we split the image
- # into slices and print each slice with a separate command.
- blobs = []
- slice_offset = 0
- while slice_offset < height_pixels:
- slice_height_pixels = min(255, height_pixels - slice_offset)
- im_slice = im.crop((0, slice_offset, width_pixels, slice_offset + slice_height_pixels))
- print_command = b"\x1d\x76\x30" + b'\x00' + (width_bytes).to_bytes(2, 'little') + slice_height_pixels.to_bytes(2, 'little')
- blobs += [print_command + im_slice.tobytes()]
- slice_offset += slice_height_pixels
-
- '''GS V m
- - GS V: Cut, in hex: "1D 56"
- - m: Cut mode:
- - 0: Full cut
- - 1: Partial cut
- - 65 (0x41): Feed paper then full cut
- - 66 (0x42): Feed paper then partial cut
- '''
- cut = b'\x1d\x56' + b'\x41'
-
- self.print_raw(center + b"".join(blobs) + cut + b'\n')
+ print_command = getattr(self, 'format_%s' % self.receipt_protocol)(im)
+ self.print_raw(print_command)
+
+ def format_star(self, im):
+ width = int((im.width + 7) / 8)
+
+ raster_init = b'\x1b\x2a\x72\x41'
+ raster_page_length = b'\x1b\x2a\x72\x50\x30\x00'
+ raster_send = b'\x62'
+ raster_close = b'\x1b\x2a\x72\x42'
+
+ raster_data = b''
+ dots = im.tobytes()
+ while len(dots):
+ raster_data += raster_send + width.to_bytes(2, 'little') + dots[:width]
+ dots = dots[width:]
+
+ return raster_init + raster_page_length + raster_data + raster_close
+
+ 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 len(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 print_status(self):
"""Prints the status ticket of the IoTBox on the current printer."""
@@ -272,19 +247,15 @@ class PrinterDriver(Driver):
if code:
pairing_code = '\nPairing Code:\n%s\n' % code
- center = b'\x1b\x61\x01'
- title = b'\n\x1b\x21\x30\x1b\x4d\x01IoTBox Status\x1b\x4d\x00\x1b\x21\x00\n'
- cut = b'\x1d\x56\x41'
-
- self.print_raw(center + title + wlan.encode() + mac.encode() + ip.encode() + homepage.encode() + pairing_code.encode() + cut + b'\n')
+ commands = RECEIPT_PRINTER_COMMANDS[self.receipt_protocol]
+ title = commands['title'] % b'IoTBox Status'
+ self.print_raw(commands['center'] + title + b'\n' + wlan.encode() + mac.encode() + ip.encode() + homepage.encode() + pairing_code.encode() + commands['cut'])
def open_cashbox(self):
"""Sends a signal to the current printer to open the connected cashbox."""
- # ESC = --> Set peripheral device
- self.print_raw(b'\x1b\x3d\x01')
- for drawer in [b'\x1b\x70\x00', b'\x1b\x70\x01']: # Kick pin 2 and 5
- command = drawer + b'\x19\x19' # Pulse ON during 50ms then OFF during 50ms
- self.print_raw(command)
+ commands = RECEIPT_PRINTER_COMMANDS[self.receipt_protocol]
+ for drawer in commands['drawers']:
+ self.print_raw(drawer)
class PrinterController(http.Controller):