1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-08-24 09:03:34 +00:00

reformat codebase

This commit is contained in:
Patrick Kanzler
2021-10-30 18:15:22 +02:00
parent 109a5d8a92
commit 435f2bba24
41 changed files with 1706 additions and 1398 deletions

View File

@@ -9,7 +9,7 @@ try:
from .version import version as __version__ # noqa
except ImportError: # pragma: no cover
raise ImportError(
'Failed to find (autogenerated) version.py. '
'This might be because you are installing from GitHub\'s tarballs, '
'use the PyPI ones.'
"Failed to find (autogenerated) version.py. "
"This might be because you are installing from GitHub's tarballs, "
"use the PyPI ones."
)

View File

@@ -16,48 +16,53 @@ from typing import Any, Dict
logging.basicConfig()
logger = logging.getLogger(__name__)
pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', gettempdir())
pickle_path = path.join(pickle_dir, '{v}.capabilities.pickle'.format(v=platform.python_version()))
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", gettempdir())
pickle_path = path.join(
pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version())
)
# get a temporary file from pkg_resources if no file is specified in env
capabilities_path = environ.get('ESCPOS_CAPABILITIES_FILE',
pkg_resources.resource_filename(__name__, 'capabilities.json'))
capabilities_path = environ.get(
"ESCPOS_CAPABILITIES_FILE",
pkg_resources.resource_filename(__name__, "capabilities.json"),
)
# Load external printer database
t0 = time.time()
logger.debug('Using capabilities from file: %s', capabilities_path)
logger.debug("Using capabilities from file: %s", capabilities_path)
if path.exists(pickle_path):
if path.getmtime(capabilities_path) > path.getmtime(pickle_path):
logger.debug('Found a more recent capabilities file')
logger.debug("Found a more recent capabilities file")
full_load = True
else:
full_load = False
logger.debug('Loading capabilities from pickle in %s', pickle_path)
with open(pickle_path, 'rb') as cf:
logger.debug("Loading capabilities from pickle in %s", pickle_path)
with open(pickle_path, "rb") as cf:
CAPABILITIES = pickle.load(cf)
else:
logger.debug('Capabilities pickle file not found: %s', pickle_path)
logger.debug("Capabilities pickle file not found: %s", pickle_path)
full_load = True
if full_load:
logger.debug('Loading and pickling capabilities')
with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp:
logger.debug("Loading and pickling capabilities")
with open(capabilities_path) as cp, open(pickle_path, "wb") as pp:
CAPABILITIES = yaml.safe_load(cp)
pickle.dump(CAPABILITIES, pp, protocol=2)
logger.debug('Finished loading capabilities took %.2fs', time.time() - t0)
logger.debug("Finished loading capabilities took %.2fs", time.time() - t0)
PROFILES: Dict[str, Any] = CAPABILITIES['profiles']
PROFILES: Dict[str, Any] = CAPABILITIES["profiles"]
class NotSupported(Exception):
"""Raised if a requested feature is not supported by the
printer profile.
"""
pass
BARCODE_B = 'barcodeB'
BARCODE_B = "barcodeB"
class BaseProfile(object):
@@ -76,37 +81,35 @@ class BaseProfile(object):
"""Return the escpos index for `font`. Makes sure that
the requested `font` is valid.
"""
font = {'a': 0, 'b': 1}.get(font, font)
font = {"a": 0, "b": 1}.get(font, font)
if not six.text_type(font) in self.fonts:
raise NotSupported(
'"{}" is not a valid font in the current profile'.format(font))
'"{}" is not a valid font in the current profile'.format(font)
)
return font
def get_columns(self, font):
""" Return the number of columns for the given font.
"""
"""Return the number of columns for the given font."""
font = self.get_font(font)
return self.fonts[six.text_type(font)]['columns']
return self.fonts[six.text_type(font)]["columns"]
def supports(self, feature):
"""Return true/false for the given feature.
"""
"""Return true/false for the given feature."""
return self.features.get(feature)
def get_code_pages(self):
"""Return the support code pages as a ``{name: index}`` dict.
"""
"""Return the support code pages as a ``{name: index}`` dict."""
return {v: k for k, v in self.codePages.items()}
def get_profile(name: str=None, **kwargs):
def get_profile(name: str = None, **kwargs):
"""Get the profile by name; if no name is given, return the
default profile.
"""
if isinstance(name, Profile):
return name
clazz = get_profile_class(name or 'default')
clazz = get_profile_class(name or "default")
return clazz(**kwargs)
@@ -120,9 +123,8 @@ def get_profile_class(name: str):
if name not in CLASS_CACHE:
profile_data = PROFILES[name]
profile_name = clean(name)
class_name = '{}{}Profile'.format(
profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {'profile_data': profile_data})
class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
CLASS_CACHE[name] = new_class
return CLASS_CACHE[name]
@@ -130,13 +132,13 @@ def get_profile_class(name: str):
def clean(s):
# Remove invalid characters
s = re.sub('[^0-9a-zA-Z_]', '', s)
s = re.sub("[^0-9a-zA-Z_]", "", s)
# Remove leading characters until we find a letter or underscore
s = re.sub('^[^a-zA-Z_]+', '', s)
s = re.sub("^[^a-zA-Z_]+", "", s)
return str(s)
class Profile(get_profile_class('default')):
class Profile(get_profile_class("default")):
"""
For users, who want to provide their profile
"""

View File

@@ -11,6 +11,7 @@ It requires you to have a configuration file. See documentation for details.
import argparse
try:
import argcomplete
except ImportError:
@@ -24,14 +25,14 @@ from . import version
# Must be defined before it's used in DEMO_FUNCTIONS
def str_to_bool(string):
""" Used as a type in argparse so that we get back a proper
"""Used as a type in argparse so that we get back a proper
bool instead of always True
"""
return string.lower() in ('y', 'yes', '1', 'true')
return string.lower() in ("y", "yes", "1", "true")
# A list of functions that work better with a newline to be sent after them.
REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text')
REQUIRES_NEWLINE = ("qr", "barcode", "text", "block_text")
# Used in demo method
@@ -39,40 +40,46 @@ REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text')
# manual translation is done in the case of barcodes_a -> barcode.
# Value: A list of dictionaries to pass to the escpos function as arguments.
DEMO_FUNCTIONS = {
'text': [
{'txt': 'Hello, World!\n', }
"text": [
{
"txt": "Hello, World!\n",
}
],
'qr': [
{'content': 'This tests a QR code'},
{'content': 'https://en.wikipedia.org/'}
"qr": [
{"content": "This tests a QR code"},
{"content": "https://en.wikipedia.org/"},
],
'barcodes_a': [
{'bc': 'UPC-A', 'code': '13243546576'},
{'bc': 'UPC-E', 'code': '132435'},
{'bc': 'EAN13', 'code': '1324354657687'},
{'bc': 'EAN8', 'code': '1324354'},
{'bc': 'CODE39', 'code': 'TEST'},
{'bc': 'ITF', 'code': '55867492279103'},
{'bc': 'NW7', 'code': 'A00000000A'},
"barcodes_a": [
{"bc": "UPC-A", "code": "13243546576"},
{"bc": "UPC-E", "code": "132435"},
{"bc": "EAN13", "code": "1324354657687"},
{"bc": "EAN8", "code": "1324354"},
{"bc": "CODE39", "code": "TEST"},
{"bc": "ITF", "code": "55867492279103"},
{"bc": "NW7", "code": "A00000000A"},
],
'barcodes_b': [
{'bc': 'UPC-A', 'code': '13243546576', 'function_type': 'B'},
{'bc': 'UPC-E', 'code': '132435', 'function_type': 'B'},
{'bc': 'EAN13', 'code': '1324354657687', 'function_type': 'B'},
{'bc': 'EAN8', 'code': '1324354', 'function_type': 'B'},
{'bc': 'CODE39', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'ITF', 'code': '55867492279103', 'function_type': 'B'},
{'bc': 'NW7', 'code': 'A00000000A', 'function_type': 'B'},
{'bc': 'CODE93', 'code': 'A00000000A', 'function_type': 'B'},
{'bc': 'CODE93', 'code': '1324354657687', 'function_type': 'B'},
{'bc': 'CODE128A', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'CODE128B', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'CODE128C', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'GS1-128', 'code': '00123456780000000001', 'function_type': 'B'},
{'bc': 'GS1 DataBar Omnidirectional', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Truncated', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Limited', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Expanded', 'code': '00AAAAAAA', 'function_type': 'B'},
"barcodes_b": [
{"bc": "UPC-A", "code": "13243546576", "function_type": "B"},
{"bc": "UPC-E", "code": "132435", "function_type": "B"},
{"bc": "EAN13", "code": "1324354657687", "function_type": "B"},
{"bc": "EAN8", "code": "1324354", "function_type": "B"},
{"bc": "CODE39", "code": "TEST", "function_type": "B"},
{"bc": "ITF", "code": "55867492279103", "function_type": "B"},
{"bc": "NW7", "code": "A00000000A", "function_type": "B"},
{"bc": "CODE93", "code": "A00000000A", "function_type": "B"},
{"bc": "CODE93", "code": "1324354657687", "function_type": "B"},
{"bc": "CODE128A", "code": "TEST", "function_type": "B"},
{"bc": "CODE128B", "code": "TEST", "function_type": "B"},
{"bc": "CODE128C", "code": "TEST", "function_type": "B"},
{"bc": "GS1-128", "code": "00123456780000000001", "function_type": "B"},
{
"bc": "GS1 DataBar Omnidirectional",
"code": "0000000000000",
"function_type": "B",
},
{"bc": "GS1 DataBar Truncated", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Limited", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Expanded", "code": "00AAAAAAA", "function_type": "B"},
],
}
@@ -84,356 +91,355 @@ DEMO_FUNCTIONS = {
# arguments: A list of dicts of args for subparser.add_argument
ESCPOS_COMMANDS = [
{
'parser': {
'name': 'qr',
'help': 'Print a QR code',
"parser": {
"name": "qr",
"help": "Print a QR code",
},
'defaults': {
'func': 'qr',
"defaults": {
"func": "qr",
},
'arguments': [
"arguments": [
{
'option_strings': ('--content',),
'help': 'Text to print as a qr code',
'required': True,
"option_strings": ("--content",),
"help": "Text to print as a qr code",
"required": True,
},
{
'option_strings': ('--size',),
'help': 'QR code size (1-16) [default:3]',
'required': False,
'type': int,
"option_strings": ("--size",),
"help": "QR code size (1-16) [default:3]",
"required": False,
"type": int,
},
],
},
{
"parser": {
"name": "barcode",
"help": "Print a barcode",
},
"defaults": {
"func": "barcode",
},
"arguments": [
{
"option_strings": ("--code",),
"help": "Barcode data to print",
"required": True,
},
{
"option_strings": ("--bc",),
"help": "Barcode format",
"required": True,
},
{
"option_strings": ("--height",),
"help": "Barcode height in px",
"type": int,
},
{
"option_strings": ("--width",),
"help": "Barcode width",
"type": int,
},
{
"option_strings": ("--pos",),
"help": "Label position",
"choices": ["BELOW", "ABOVE", "BOTH", "OFF"],
},
{
"option_strings": ("--font",),
"help": "Label font",
"choices": ["A", "B"],
},
{
"option_strings": ("--align_ct",),
"help": "Align barcode center",
"type": str_to_bool,
},
{
"option_strings": ("--function_type",),
"help": "ESCPOS function type",
"choices": ["A", "B"],
},
],
},
{
"parser": {
"name": "text",
"help": "Print plain text",
},
"defaults": {
"func": "text",
},
"arguments": [
{
"option_strings": ("--txt",),
"help": "Plain text to print",
"required": True,
}
],
},
{
'parser': {
'name': 'barcode',
'help': 'Print a barcode',
"parser": {
"name": "block_text",
"help": "Print wrapped text",
},
'defaults': {
'func': 'barcode',
"defaults": {
"func": "block_text",
},
'arguments': [
"arguments": [
{
'option_strings': ('--code',),
'help': 'Barcode data to print',
'required': True,
"option_strings": ("--txt",),
"help": "block_text to print",
"required": True,
},
{
'option_strings': ('--bc',),
'help': 'Barcode format',
'required': True,
},
{
'option_strings': ('--height',),
'help': 'Barcode height in px',
'type': int,
},
{
'option_strings': ('--width',),
'help': 'Barcode width',
'type': int,
},
{
'option_strings': ('--pos',),
'help': 'Label position',
'choices': ['BELOW', 'ABOVE', 'BOTH', 'OFF'],
},
{
'option_strings': ('--font',),
'help': 'Label font',
'choices': ['A', 'B'],
},
{
'option_strings': ('--align_ct',),
'help': 'Align barcode center',
'type': str_to_bool,
},
{
'option_strings': ('--function_type',),
'help': 'ESCPOS function type',
'choices': ['A', 'B'],
"option_strings": ("--columns",),
"help": "Number of columns",
"type": int,
},
],
},
{
'parser': {
'name': 'text',
'help': 'Print plain text',
"parser": {
"name": "cut",
"help": "Cut the paper",
},
'defaults': {
'func': 'text',
"defaults": {
"func": "cut",
},
'arguments': [
"arguments": [
{
'option_strings': ('--txt',),
'help': 'Plain text to print',
'required': True,
}
],
},
{
'parser': {
'name': 'block_text',
'help': 'Print wrapped text',
},
'defaults': {
'func': 'block_text',
},
'arguments': [
{
'option_strings': ('--txt',),
'help': 'block_text to print',
'required': True,
},
{
'option_strings': ('--columns',),
'help': 'Number of columns',
'type': int,
"option_strings": ("--mode",),
"help": "Type of cut",
"choices": ["FULL", "PART"],
},
],
},
{
'parser': {
'name': 'cut',
'help': 'Cut the paper',
"parser": {
"name": "cashdraw",
"help": "Kick the cash drawer",
},
'defaults': {
'func': 'cut',
"defaults": {
"func": "cashdraw",
},
'arguments': [
"arguments": [
{
'option_strings': ('--mode',),
'help': 'Type of cut',
'choices': ['FULL', 'PART'],
"option_strings": ("--pin",),
"help": "Which PIN to kick",
"choices": [2, 5],
},
],
},
{
'parser': {
'name': 'cashdraw',
'help': 'Kick the cash drawer',
"parser": {
"name": "image",
"help": "Print an image",
},
'defaults': {
'func': 'cashdraw',
"defaults": {
"func": "image",
},
'arguments': [
"arguments": [
{
'option_strings': ('--pin',),
'help': 'Which PIN to kick',
'choices': [2, 5],
"option_strings": ("--img_source",),
"help": "Path to image",
"required": True,
},
{
"option_strings": ("--impl",),
"help": "Implementation to use",
"choices": ["bitImageRaster", "bitImageColumn", "graphics"],
},
{
"option_strings": ("--high_density_horizontal",),
"help": "Image density (horizontal)",
"type": str_to_bool,
},
{
"option_strings": ("--high_density_vertical",),
"help": "Image density (vertical)",
"type": str_to_bool,
},
],
},
{
'parser': {
'name': 'image',
'help': 'Print an image',
"parser": {
"name": "fullimage",
"help": "Print a fullimage",
},
'defaults': {
'func': 'image',
"defaults": {
"func": "fullimage",
},
'arguments': [
"arguments": [
{
'option_strings': ('--img_source',),
'help': 'Path to image',
'required': True,
"option_strings": ("--img",),
"help": "Path to img",
"required": True,
},
{
'option_strings': ('--impl',),
'help': 'Implementation to use',
'choices': ['bitImageRaster', 'bitImageColumn', 'graphics'],
"option_strings": ("--max_height",),
"help": "Max height of image in px",
"type": int,
},
{
'option_strings': ('--high_density_horizontal',),
'help': 'Image density (horizontal)',
'type': str_to_bool,
"option_strings": ("--width",),
"help": "Max width of image in px",
"type": int,
},
{
'option_strings': ('--high_density_vertical',),
'help': 'Image density (vertical)',
'type': str_to_bool,
},
],
},
{
'parser': {
'name': 'fullimage',
'help': 'Print a fullimage',
},
'defaults': {
'func': 'fullimage',
},
'arguments': [
{
'option_strings': ('--img',),
'help': 'Path to img',
'required': True,
"option_strings": ("--histeq",),
"help": "Equalize the histrogram",
"type": str_to_bool,
},
{
'option_strings': ('--max_height',),
'help': 'Max height of image in px',
'type': int,
},
{
'option_strings': ('--width',),
'help': 'Max width of image in px',
'type': int,
},
{
'option_strings': ('--histeq',),
'help': 'Equalize the histrogram',
'type': str_to_bool,
},
{
'option_strings': ('--bandsize',),
'help': 'Size of bands to divide into when printing',
'type': int,
"option_strings": ("--bandsize",),
"help": "Size of bands to divide into when printing",
"type": int,
},
],
},
{
'parser': {
'name': 'charcode',
'help': 'Set character code table',
"parser": {
"name": "charcode",
"help": "Set character code table",
},
'defaults': {
'func': 'charcode',
"defaults": {
"func": "charcode",
},
'arguments': [
"arguments": [
{
'option_strings': ('--code',),
'help': 'Character code',
'required': True,
"option_strings": ("--code",),
"help": "Character code",
"required": True,
},
],
},
{
'parser': {
'name': 'set',
'help': 'Set text properties',
"parser": {
"name": "set",
"help": "Set text properties",
},
'defaults': {
'func': 'set',
"defaults": {
"func": "set",
},
'arguments': [
"arguments": [
{
'option_strings': ('--align',),
'help': 'Horizontal alignment',
'choices': ['left', 'center', 'right'],
"option_strings": ("--align",),
"help": "Horizontal alignment",
"choices": ["left", "center", "right"],
},
{
'option_strings': ('--font',),
'help': 'Font choice',
'choices': ['left', 'center', 'right'],
"option_strings": ("--font",),
"help": "Font choice",
"choices": ["left", "center", "right"],
},
{
'option_strings': ('--text_type',),
'help': 'Text properties',
'choices': ['B', 'U', 'U2', 'BU', 'BU2', 'NORMAL'],
"option_strings": ("--text_type",),
"help": "Text properties",
"choices": ["B", "U", "U2", "BU", "BU2", "NORMAL"],
},
{
'option_strings': ('--width',),
'help': 'Width multiplier',
'type': int,
"option_strings": ("--width",),
"help": "Width multiplier",
"type": int,
},
{
'option_strings': ('--height',),
'help': 'Height multiplier',
'type': int,
"option_strings": ("--height",),
"help": "Height multiplier",
"type": int,
},
{
'option_strings': ('--density',),
'help': 'Print density',
'type': int,
"option_strings": ("--density",),
"help": "Print density",
"type": int,
},
{
'option_strings': ('--invert',),
'help': 'White on black printing',
'type': str_to_bool,
"option_strings": ("--invert",),
"help": "White on black printing",
"type": str_to_bool,
},
{
'option_strings': ('--smooth',),
'help': 'Text smoothing. Effective on >: 4x4 text',
'type': str_to_bool,
"option_strings": ("--smooth",),
"help": "Text smoothing. Effective on >: 4x4 text",
"type": str_to_bool,
},
{
'option_strings': ('--flip',),
'help': 'Text smoothing. Effective on >: 4x4 text',
'type': str_to_bool,
"option_strings": ("--flip",),
"help": "Text smoothing. Effective on >: 4x4 text",
"type": str_to_bool,
},
],
},
{
'parser': {
'name': 'hw',
'help': 'Hardware operations',
"parser": {
"name": "hw",
"help": "Hardware operations",
},
'defaults': {
'func': 'hw',
"defaults": {
"func": "hw",
},
'arguments': [
"arguments": [
{
'option_strings': ('--hw',),
'help': 'Operation',
'choices': ['INIT', 'SELECT', 'RESET'],
'required': True,
"option_strings": ("--hw",),
"help": "Operation",
"choices": ["INIT", "SELECT", "RESET"],
"required": True,
},
],
},
{
'parser': {
'name': 'control',
'help': 'Control sequences',
"parser": {
"name": "control",
"help": "Control sequences",
},
'defaults': {
'func': 'control',
"defaults": {
"func": "control",
},
'arguments': [
"arguments": [
{
'option_strings': ('--ctl',),
'help': 'Control sequence',
'choices': ['LF', 'FF', 'CR', 'HT', 'VT'],
'required': True,
"option_strings": ("--ctl",),
"help": "Control sequence",
"choices": ["LF", "FF", "CR", "HT", "VT"],
"required": True,
},
{
'option_strings': ('--pos',),
'help': 'Horizontal tab position (1-4)',
'type': int,
"option_strings": ("--pos",),
"help": "Horizontal tab position (1-4)",
"type": int,
},
],
},
{
'parser': {
'name': 'panel_buttons',
'help': 'Controls panel buttons',
"parser": {
"name": "panel_buttons",
"help": "Controls panel buttons",
},
'defaults': {
'func': 'panel_buttons',
"defaults": {
"func": "panel_buttons",
},
'arguments': [
"arguments": [
{
'option_strings': ('--enable',),
'help': 'Feed button enabled',
'type': str_to_bool,
'required': True,
"option_strings": ("--enable",),
"help": "Feed button enabled",
"type": str_to_bool,
"required": True,
},
],
},
{
'parser': {
'name': 'raw',
'help': 'Raw data',
"parser": {
"name": "raw",
"help": "Raw data",
},
'defaults': {
'func': '_raw',
"defaults": {
"func": "_raw",
},
'arguments': [
"arguments": [
{
'option_strings': ('--msg',),
'help': 'Raw data to send',
'required': True,
"option_strings": ("--msg",),
"help": "Raw data to send",
"required": True,
},
],
},
@@ -449,68 +455,71 @@ def main():
"""
parser = argparse.ArgumentParser(
description='CLI for python-escpos',
epilog='Printer configuration is defined in the python-escpos config'
'file. See documentation for details.',
description="CLI for python-escpos",
epilog="Printer configuration is defined in the python-escpos config"
"file. See documentation for details.",
)
parser.register('type', 'bool', str_to_bool)
parser.register("type", "bool", str_to_bool)
# Allow config file location to be passed
parser.add_argument(
'-c', '--config',
help='Alternate path to the configuration file',
"-c",
"--config",
help="Alternate path to the configuration file",
)
# Everything interesting runs off of a subparser so we can use the format
# cli [subparser] -args
command_subparsers = parser.add_subparsers(
title='ESCPOS Command',
dest='parser',
title="ESCPOS Command",
dest="parser",
)
# fix inconsistencies in the behaviour of some versions of argparse
command_subparsers.required = False # force 'required' testing
command_subparsers.required = False # force 'required' testing
# Build the ESCPOS command arguments
for command in ESCPOS_COMMANDS:
parser_command = command_subparsers.add_parser(**command['parser'])
parser_command.set_defaults(**command['defaults'])
for argument in command['arguments']:
option_strings = argument.pop('option_strings')
parser_command = command_subparsers.add_parser(**command["parser"])
parser_command.set_defaults(**command["defaults"])
for argument in command["arguments"]:
option_strings = argument.pop("option_strings")
parser_command.add_argument(*option_strings, **argument)
# Build any custom arguments
parser_command_demo = command_subparsers.add_parser('demo',
help='Demonstrates various functions')
parser_command_demo.set_defaults(func='demo')
parser_command_demo = command_subparsers.add_parser(
"demo", help="Demonstrates various functions"
)
parser_command_demo.set_defaults(func="demo")
demo_group = parser_command_demo.add_mutually_exclusive_group()
demo_group.add_argument(
'--barcodes-a',
help='Print demo barcodes for function type A',
action='store_true',
"--barcodes-a",
help="Print demo barcodes for function type A",
action="store_true",
)
demo_group.add_argument(
'--barcodes-b',
help='Print demo barcodes for function type B',
action='store_true',
"--barcodes-b",
help="Print demo barcodes for function type B",
action="store_true",
)
demo_group.add_argument(
'--qr',
help='Print some demo QR codes',
action='store_true',
"--qr",
help="Print some demo QR codes",
action="store_true",
)
demo_group.add_argument(
'--text',
help='Print some demo text',
action='store_true',
"--text",
help="Print some demo text",
action="store_true",
)
parser_command_version = command_subparsers.add_parser('version',
help='Print the version of python-escpos')
parser_command_version = command_subparsers.add_parser(
"version", help="Print the version of python-escpos"
)
parser_command_version.set_defaults(version=True)
# hook in argcomplete
if 'argcomplete' in globals():
if "argcomplete" in globals():
argcomplete.autocomplete(parser)
# Get only arguments actually passed
@@ -518,16 +527,18 @@ def main():
if not args_dict:
parser.print_help()
sys.exit()
command_arguments = dict([k, v] for k, v in six.iteritems(args_dict) if v is not None)
command_arguments = dict(
[k, v] for k, v in six.iteritems(args_dict) if v is not None
)
# If version should be printed, do this, then exit
print_version = command_arguments.pop('version', None)
print_version = command_arguments.pop("version", None)
if print_version:
print(version.version)
sys.exit()
# If there was a config path passed, grab it
config_path = command_arguments.pop('config', None)
config_path = command_arguments.pop("config", None)
# Load the configuration and defined printer
saved_config = config.Config()
@@ -535,12 +546,12 @@ def main():
printer = saved_config.printer()
if not printer:
raise Exception('No printers loaded from config')
raise Exception("No printers loaded from config")
target_command = command_arguments.pop('func')
target_command = command_arguments.pop("func")
# remove helper-argument 'parser' from dict
command_arguments.pop('parser', None)
command_arguments.pop("parser", None)
if hasattr(printer, target_command):
# print command with args
@@ -548,7 +559,7 @@ def main():
if target_command in REQUIRES_NEWLINE:
printer.text("\n")
else:
command_arguments['printer'] = printer
command_arguments["printer"] = printer
globals()[target_command](**command_arguments)
@@ -564,14 +575,14 @@ def demo(printer, **kwargs):
for demo_choice in kwargs.keys():
command = getattr(
printer,
demo_choice
.replace('barcodes_a', 'barcode')
.replace('barcodes_b', 'barcode')
demo_choice.replace("barcodes_a", "barcode").replace(
"barcodes_b", "barcode"
),
)
for params in DEMO_FUNCTIONS[demo_choice]:
command(**params)
printer.cut()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -21,4 +21,4 @@ class CodePageManager:
return self.data[encoding]
CodePages = CodePageManager(CAPABILITIES['encodings'])
CodePages = CodePageManager(CAPABILITIES["encodings"])

View File

@@ -14,16 +14,17 @@ from . import exceptions
class Config(object):
""" Configuration handler class.
"""Configuration handler class.
This class loads configuration from a default or specificed directory. It
can create your defined printer and return it to you.
"""
_app_name = 'python-escpos'
_config_file = 'config.yaml'
_app_name = "python-escpos"
_config_file = "config.yaml"
def __init__(self):
""" Initialize configuration.
"""Initialize configuration.
Remember to add anything that needs to be reset between configurations
to self._reset_config
@@ -35,7 +36,7 @@ class Config(object):
self._printer_config = None
def _reset_config(self):
""" Clear the loaded configuration.
"""Clear the loaded configuration.
If we are loading a changed config, we don't want to have leftover
data.
@@ -47,7 +48,7 @@ class Config(object):
self._printer_config = None
def load(self, config_path=None):
""" Load and parse the configuration file using pyyaml
"""Load and parse the configuration file using pyyaml
:param config_path: An optional file path, file handle, or byte string
for the configuration file.
@@ -58,31 +59,32 @@ class Config(object):
if not config_path:
config_path = os.path.join(
appdirs.user_config_dir(self._app_name),
self._config_file
appdirs.user_config_dir(self._app_name), self._config_file
)
try:
# First check if it's file like. If it is, pyyaml can load it.
# I'm checking type instead of catching exceptions to keep the
# exception handling simple
if hasattr(config_path, 'read'):
if hasattr(config_path, "read"):
config = yaml.safe_load(config_path)
else:
# If it isn't, it's a path. We have to open it first, otherwise
# pyyaml will try to read it as yaml
with open(config_path, 'rb') as config_file:
with open(config_path, "rb") as config_file:
config = yaml.safe_load(config_file)
except EnvironmentError:
raise exceptions.ConfigNotFoundError('Couldn\'t read config at {config_path}'.format(
config_path=str(config_path),
))
raise exceptions.ConfigNotFoundError(
"Couldn't read config at {config_path}".format(
config_path=str(config_path),
)
)
except yaml.YAMLError:
raise exceptions.ConfigSyntaxError('Error parsing YAML')
raise exceptions.ConfigSyntaxError("Error parsing YAML")
if 'printer' in config:
self._printer_config = config['printer']
self._printer_name = self._printer_config.pop('type').title()
if "printer" in config:
self._printer_config = config["printer"]
self._printer_name = self._printer_config.pop("type").title()
if not self._printer_name or not hasattr(printer, self._printer_name):
raise exceptions.ConfigSyntaxError(
@@ -94,7 +96,7 @@ class Config(object):
self._has_loaded = True
def printer(self):
""" Returns a printer that was defined in the config, or throws an
"""Returns a printer that was defined in the config, or throws an
exception.
This method loads the default config if one hasn't beeen already loaded.
@@ -104,7 +106,7 @@ class Config(object):
self.load()
if not self._printer_name:
raise exceptions.ConfigSectionMissingError('printer')
raise exceptions.ConfigSectionMissingError("printer")
if not self._printer:
# We could catch init errors and make them a ConfigSyntaxError,

View File

@@ -16,120 +16,125 @@ import six
# Control characters
# as labelled in https://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
NUL = b'\x00'
EOT = b'\x04'
ENQ = b'\x05'
DLE = b'\x10'
DC4 = b'\x14'
CAN = b'\x18'
ESC = b'\x1b'
FS = b'\x1c'
GS = b'\x1d'
NUL = b"\x00"
EOT = b"\x04"
ENQ = b"\x05"
DLE = b"\x10"
DC4 = b"\x14"
CAN = b"\x18"
ESC = b"\x1b"
FS = b"\x1c"
GS = b"\x1d"
# Feed control sequences
CTL_LF = b'\n' # Print and line feed
CTL_FF = b'\f' # Form feed
CTL_CR = b'\r' # Carriage return
CTL_HT = b'\t' # Horizontal tab
CTL_SET_HT = ESC + b'\x44' # Set horizontal tab positions
CTL_VT = b'\v' # Vertical tab
CTL_LF = b"\n" # Print and line feed
CTL_FF = b"\f" # Form feed
CTL_CR = b"\r" # Carriage return
CTL_HT = b"\t" # Horizontal tab
CTL_SET_HT = ESC + b"\x44" # Set horizontal tab positions
CTL_VT = b"\v" # Vertical tab
# Printer hardware
HW_INIT = ESC + b'@' # Clear data in buffer and reset modes
HW_SELECT = ESC + b'=\x01' # Printer select
HW_INIT = ESC + b"@" # Clear data in buffer and reset modes
HW_SELECT = ESC + b"=\x01" # Printer select
HW_RESET = ESC + b'\x3f\x0a\x00' # Reset printer hardware
# (TODO: Where is this specified?)
HW_RESET = ESC + b"\x3f\x0a\x00" # Reset printer hardware
# (TODO: Where is this specified?)
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
_CASH_DRAWER = lambda m, t1='', t2='': ESC + b'p' + m + six.int2byte(t1) + six.int2byte(t2)
CD_KICK_DEC_SEQUENCE = lambda esc, p, m, t1=50, t2=50: six.int2byte(esc) + six.int2byte(p) + six.int2byte(m) + six.int2byte(t1) + six.int2byte(t2)
CD_KICK_2 = _CASH_DRAWER(b'\x00', 50, 50) # Sends a pulse to pin 2 []
CD_KICK_5 = _CASH_DRAWER(b'\x01', 50, 50) # Sends a pulse to pin 5 []
_CASH_DRAWER = (
lambda m, t1="", t2="": ESC + b"p" + m + six.int2byte(t1) + six.int2byte(t2)
)
CD_KICK_DEC_SEQUENCE = (
lambda esc, p, m, t1=50, t2=50: six.int2byte(esc)
+ six.int2byte(p)
+ six.int2byte(m)
+ six.int2byte(t1)
+ six.int2byte(t2)
)
CD_KICK_2 = _CASH_DRAWER(b"\x00", 50, 50) # Sends a pulse to pin 2 []
CD_KICK_5 = _CASH_DRAWER(b"\x01", 50, 50) # Sends a pulse to pin 5 []
# Paper Cutter
_CUT_PAPER = lambda m: GS + b'V' + m
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper
_CUT_PAPER = lambda m: GS + b"V" + m
PAPER_FULL_CUT = _CUT_PAPER(b"\x00") # Full cut paper
PAPER_PART_CUT = _CUT_PAPER(b"\x01") # Partial cut paper
# Beep (please note that the actual beep sequence may differ between devices)
BEEP = b'\x07'
BEEP = b"\x07"
# Panel buttons (e.g. the FEED button)
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n)
_PANEL_BUTTON = lambda n: ESC + b"c5" + six.int2byte(n)
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons
# Line display printing
LINE_DISPLAY_OPEN = ESC + b'\x3d\x02'
LINE_DISPLAY_CLEAR = ESC + b'\x40'
LINE_DISPLAY_CLOSE = ESC + b'\x3d\x01'
LINE_DISPLAY_OPEN = ESC + b"\x3d\x02"
LINE_DISPLAY_CLEAR = ESC + b"\x40"
LINE_DISPLAY_CLOSE = ESC + b"\x3d\x01"
# Sheet modes
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper
SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll
SHEET_SLIP_MODE = ESC + b"\x63\x30\x04" # slip paper
SHEET_ROLL_MODE = ESC + b"\x63\x30\x01" # paper roll
# Text format
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
# Printers" and tidy up this stuff too.
TXT_SIZE = GS + b'!'
TXT_SIZE = GS + b"!"
TXT_NORMAL = ESC + b'!\x00' # Normal text
TXT_NORMAL = ESC + b"!\x00" # Normal text
TXT_STYLE = {
'bold': {
False: ESC + b'\x45\x00', # Bold font OFF
True: ESC + b'\x45\x01' # Bold font ON
"bold": {
False: ESC + b"\x45\x00", # Bold font OFF
True: ESC + b"\x45\x01", # Bold font ON
},
'underline': {
0: ESC + b'\x2d\x00', # Underline font OFF
1: ESC + b'\x2d\x01', # Underline font 1-dot ON
2: ESC + b'\x2d\x02' # Underline font 2-dot ON
"underline": {
0: ESC + b"\x2d\x00", # Underline font OFF
1: ESC + b"\x2d\x01", # Underline font 1-dot ON
2: ESC + b"\x2d\x02", # Underline font 2-dot ON
},
'size': {
'normal': TXT_NORMAL + ESC + b'!\x00', # Normal text
'2h': TXT_NORMAL + ESC + b'!\x10', # Double height text
'2w': TXT_NORMAL + ESC + b'!\x20', # Double width text
'2x': TXT_NORMAL + ESC + b'!\x30' # Quad area text
"size": {
"normal": TXT_NORMAL + ESC + b"!\x00", # Normal text
"2h": TXT_NORMAL + ESC + b"!\x10", # Double height text
"2w": TXT_NORMAL + ESC + b"!\x20", # Double width text
"2x": TXT_NORMAL + ESC + b"!\x30", # Quad area text
},
'font': {
'a': ESC + b'\x4d\x00', # Font type A
'b': ESC + b'\x4d\x00' # Font type B
"font": {
"a": ESC + b"\x4d\x00", # Font type A
"b": ESC + b"\x4d\x00", # Font type B
},
'align': {
'left': ESC + b'\x61\x00', # Left justification
'center': ESC + b'\x61\x01', # Centering
'right': ESC + b'\x61\x02' # Right justification
"align": {
"left": ESC + b"\x61\x00", # Left justification
"center": ESC + b"\x61\x01", # Centering
"right": ESC + b"\x61\x02", # Right justification
},
'invert': {
True: GS + b'\x42\x01', # Inverse Printing ON
False: GS + b'\x42\x00' # Inverse Printing OFF
"invert": {
True: GS + b"\x42\x01", # Inverse Printing ON
False: GS + b"\x42\x00", # Inverse Printing OFF
},
'color': {
'black': ESC + b'\x72\x00', # Default Color
'red': ESC + b'\x72\x01' # Alternative Color, Usually Red
"color": {
"black": ESC + b"\x72\x00", # Default Color
"red": ESC + b"\x72\x01", # Alternative Color, Usually Red
},
'flip': {
True: ESC + b'\x7b\x01', # Flip ON
False: ESC + b'\x7b\x00' # Flip OFF
"flip": {True: ESC + b"\x7b\x01", False: ESC + b"\x7b\x00"}, # Flip ON # Flip OFF
"density": {
0: GS + b"\x7c\x00", # Printing Density -50%
1: GS + b"\x7c\x01", # Printing Density -37.5%
2: GS + b"\x7c\x02", # Printing Density -25%
3: GS + b"\x7c\x03", # Printing Density -12.5%
4: GS + b"\x7c\x04", # Printing Density 0%
5: GS + b"\x7c\x08", # Printing Density +50%
6: GS + b"\x7c\x07", # Printing Density +37.5%
7: GS + b"\x7c\x06", # Printing Density +25%
8: GS + b"\x7c\x05", # Printing Density +12.5%
},
'density': {
0: GS + b'\x7c\x00', # Printing Density -50%
1: GS + b'\x7c\x01', # Printing Density -37.5%
2: GS + b'\x7c\x02', # Printing Density -25%
3: GS + b'\x7c\x03', # Printing Density -12.5%
4: GS + b'\x7c\x04', # Printing Density 0%
5: GS + b'\x7c\x08', # Printing Density +50%
6: GS + b'\x7c\x07', # Printing Density +37.5%
7: GS + b'\x7c\x06', # Printing Density +25%
8: GS + b'\x7c\x05' # Printing Density +12.5%
"smooth": {
True: GS + b"\x62\x01", # Smooth ON
False: GS + b"\x62\x00", # Smooth OFF
},
'smooth': {
True: GS + b'\x62\x01', # Smooth ON
False: GS + b'\x62\x00' # Smooth OFF
},
'height': { # Custom text height
"height": { # Custom text height
1: 0x00,
2: 0x01,
3: 0x02,
@@ -137,9 +142,9 @@ TXT_STYLE = {
5: 0x04,
6: 0x05,
7: 0x06,
8: 0x07
8: 0x07,
},
'width': { # Custom text width
"width": { # Custom text width
1: 0x00,
2: 0x10,
3: 0x20,
@@ -147,101 +152,104 @@ TXT_STYLE = {
5: 0x40,
6: 0x50,
7: 0x60,
8: 0x70
}
8: 0x70,
},
}
# Fonts
SET_FONT = lambda n: ESC + b'\x4d' + n
TXT_FONT_A = SET_FONT(b'\x00') # Font type A
TXT_FONT_B = SET_FONT(b'\x01') # Font type B
SET_FONT = lambda n: ESC + b"\x4d" + n
TXT_FONT_A = SET_FONT(b"\x00") # Font type A
TXT_FONT_B = SET_FONT(b"\x01") # Font type B
# Spacing
LINESPACING_RESET = ESC + b'2'
LINESPACING_RESET = ESC + b"2"
LINESPACING_FUNCS = {
60: ESC + b'A', # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255
60: ESC + b"A", # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b"+", # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b"3", # line_spacing/180 of an inch, 0 <= line_spacing <= 255
}
# Prefix to change the codepage. You need to attach a byte to indicate
# the codepage to use. We use escpos-printer-db as the data source.
CODEPAGE_CHANGE = ESC + b'\x74'
CODEPAGE_CHANGE = ESC + b"\x74"
# Barcode format
_SET_BARCODE_TXT_POS = lambda n: GS + b'H' + n
BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b'\x00') # HRI barcode chars OFF
BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b'\x01') # HRI barcode chars above
BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b'\x02') # HRI barcode chars below
BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b'\x03') # HRI both above and below
_SET_BARCODE_TXT_POS = lambda n: GS + b"H" + n
BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b"\x00") # HRI barcode chars OFF
BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b"\x01") # HRI barcode chars above
BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b"\x02") # HRI barcode chars below
BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b"\x03") # HRI both above and below
_SET_HRI_FONT = lambda n: GS + b'f' + n
BARCODE_FONT_A = _SET_HRI_FONT(b'\x00') # Font type A for HRI barcode chars
BARCODE_FONT_B = _SET_HRI_FONT(b'\x01') # Font type B for HRI barcode chars
_SET_HRI_FONT = lambda n: GS + b"f" + n
BARCODE_FONT_A = _SET_HRI_FONT(b"\x00") # Font type A for HRI barcode chars
BARCODE_FONT_B = _SET_HRI_FONT(b"\x01") # Font type B for HRI barcode chars
BARCODE_HEIGHT = GS + b'h' # Barcode Height [1-255]
BARCODE_WIDTH = GS + b'w' # Barcode Width [2-6]
BARCODE_HEIGHT = GS + b"h" # Barcode Height [1-255]
BARCODE_WIDTH = GS + b"w" # Barcode Width [2-6]
# NOTE: This isn't actually an ESC/POS command. It's the common prefix to the
# two "print bar code" commands:
# - Type A: "GS k <type as integer> <data> NUL"
# - TYPE B: "GS k <type as letter> <data length> <data>"
# The latter command supports more barcode types
_SET_BARCODE_TYPE = lambda m: GS + b'k' + six.int2byte(m)
_SET_BARCODE_TYPE = lambda m: GS + b"k" + six.int2byte(m)
# Barcodes for printing function type A
BARCODE_TYPE_A = {
'UPC-A': _SET_BARCODE_TYPE(0),
'UPC-E': _SET_BARCODE_TYPE(1),
'EAN13': _SET_BARCODE_TYPE(2),
'EAN8': _SET_BARCODE_TYPE(3),
'CODE39': _SET_BARCODE_TYPE(4),
'ITF': _SET_BARCODE_TYPE(5),
'NW7': _SET_BARCODE_TYPE(6),
'CODABAR': _SET_BARCODE_TYPE(6), # Same as NW7
"UPC-A": _SET_BARCODE_TYPE(0),
"UPC-E": _SET_BARCODE_TYPE(1),
"EAN13": _SET_BARCODE_TYPE(2),
"EAN8": _SET_BARCODE_TYPE(3),
"CODE39": _SET_BARCODE_TYPE(4),
"ITF": _SET_BARCODE_TYPE(5),
"NW7": _SET_BARCODE_TYPE(6),
"CODABAR": _SET_BARCODE_TYPE(6), # Same as NW7
}
# Barcodes for printing function type B
# The first 8 are the same barcodes as type A
BARCODE_TYPE_B = {
'UPC-A': _SET_BARCODE_TYPE(65),
'UPC-E': _SET_BARCODE_TYPE(66),
'EAN13': _SET_BARCODE_TYPE(67),
'EAN8': _SET_BARCODE_TYPE(68),
'CODE39': _SET_BARCODE_TYPE(69),
'ITF': _SET_BARCODE_TYPE(70),
'NW7': _SET_BARCODE_TYPE(71),
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
'CODE93': _SET_BARCODE_TYPE(72),
'CODE128': _SET_BARCODE_TYPE(73),
'GS1-128': _SET_BARCODE_TYPE(74),
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76),
'GS1 DATABAR LIMITED': _SET_BARCODE_TYPE(77),
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78),
"UPC-A": _SET_BARCODE_TYPE(65),
"UPC-E": _SET_BARCODE_TYPE(66),
"EAN13": _SET_BARCODE_TYPE(67),
"EAN8": _SET_BARCODE_TYPE(68),
"CODE39": _SET_BARCODE_TYPE(69),
"ITF": _SET_BARCODE_TYPE(70),
"NW7": _SET_BARCODE_TYPE(71),
"CODABAR": _SET_BARCODE_TYPE(71), # Same as NW7
"CODE93": _SET_BARCODE_TYPE(72),
"CODE128": _SET_BARCODE_TYPE(73),
"GS1-128": _SET_BARCODE_TYPE(74),
"GS1 DATABAR OMNIDIRECTIONAL": _SET_BARCODE_TYPE(75),
"GS1 DATABAR TRUNCATED": _SET_BARCODE_TYPE(76),
"GS1 DATABAR LIMITED": _SET_BARCODE_TYPE(77),
"GS1 DATABAR EXPANDED": _SET_BARCODE_TYPE(78),
}
BARCODE_FORMATS = {
'UPC-A': ([(11, 12)], "^[0-9]{11,12}$"),
'UPC-E': ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"),
'EAN13': ([(12, 13)], "^[0-9]{12,13}$"),
'EAN8': ([(7, 8)], "^[0-9]{7,8}$"),
'CODE39': ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
'ITF': ([(2, 255)], "^([0-9]{2})+$"),
'NW7': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),
'CODABAR': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
'CODE93': ([(1, 255)], "^[\\x00-\\x7F]+$"),
'CODE128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"),
'GS1-128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128
'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"),
'GS1 DATABAR TRUNCATED': ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional
'GS1 DATABAR LIMITED': ([(13,13)], "^[01][0-9]{12}$"),
'GS1 DATABAR EXPANDED': ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"),
"UPC-A": ([(11, 12)], "^[0-9]{11,12}$"),
"UPC-E": ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"),
"EAN13": ([(12, 13)], "^[0-9]{12,13}$"),
"EAN8": ([(7, 8)], "^[0-9]{7,8}$"),
"CODE39": ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
"ITF": ([(2, 255)], "^([0-9]{2})+$"),
"NW7": ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),
"CODABAR": ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
"CODE93": ([(1, 255)], "^[\\x00-\\x7F]+$"),
"CODE128": ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"),
"GS1-128": ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128
"GS1 DATABAR OMNIDIRECTIONAL": ([(13, 13)], "^[0-9]{13}$"),
"GS1 DATABAR TRUNCATED": ([(13, 13)], "^[0-9]{13}$"), # same as GS1 omnidirectional
"GS1 DATABAR LIMITED": ([(13, 13)], "^[01][0-9]{12}$"),
"GS1 DATABAR EXPANDED": (
[(2, 255)],
"^\([0-9][A-Za-z0-9 \!\"\%\&'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$",
),
}
BARCODE_TYPES = {
'A': BARCODE_TYPE_A,
'B': BARCODE_TYPE_B,
"A": BARCODE_TYPE_A,
"B": BARCODE_TYPE_B,
}
# QRCode error correction levels
@@ -258,17 +266,17 @@ QR_MICRO = 3
# Image format
# NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image"
# command. The constants include a fragment of the data's header.
_PRINT_RASTER_IMG = lambda data: GS + b'v0' + data
S_RASTER_N = _PRINT_RASTER_IMG(b'\x00') # Set raster image normal size
S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01') # Set raster image double width
S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height
S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple
_PRINT_RASTER_IMG = lambda data: GS + b"v0" + data
S_RASTER_N = _PRINT_RASTER_IMG(b"\x00") # Set raster image normal size
S_RASTER_2W = _PRINT_RASTER_IMG(b"\x01") # Set raster image double width
S_RASTER_2H = _PRINT_RASTER_IMG(b"\x02") # Set raster image double height
S_RASTER_Q = _PRINT_RASTER_IMG(b"\x03") # Set raster image quadruple
# Status Command
RT_STATUS = DLE + EOT
RT_STATUS_ONLINE = RT_STATUS + b'\x01'
RT_STATUS_PAPER = RT_STATUS + b'\x04'
RT_STATUS_ONLINE = RT_STATUS + b"\x01"
RT_STATUS_PAPER = RT_STATUS + b"\x04"
RT_MASK_ONLINE = 8
RT_MASK_PAPER = 18
RT_MASK_LOWPAPER = 30
RT_MASK_NOPAPER = 114
RT_MASK_NOPAPER = 114

View File

@@ -22,17 +22,51 @@ from barcode.writer import ImageWriter
import os
from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q
from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH
from .constants import (
ESC,
GS,
NUL,
QR_ECLEVEL_L,
QR_ECLEVEL_M,
QR_ECLEVEL_H,
QR_ECLEVEL_Q,
)
from .constants import (
QR_MODEL_1,
QR_MODEL_2,
QR_MICRO,
BARCODE_TYPES,
BARCODE_HEIGHT,
BARCODE_WIDTH,
)
from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS
from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW
from .constants import (
BARCODE_TXT_OFF,
BARCODE_TXT_BTH,
BARCODE_TXT_ABV,
BARCODE_TXT_BLW,
)
from .constants import TXT_SIZE, TXT_NORMAL
from .constants import SET_FONT
from .constants import LINESPACING_FUNCS, LINESPACING_RESET
from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE
from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT
from .constants import (
CD_KICK_DEC_SEQUENCE,
CD_KICK_5,
CD_KICK_2,
PAPER_FULL_CUT,
PAPER_PART_CUT,
)
from .constants import HW_RESET, HW_SELECT, HW_INIT
from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
from .constants import (
CTL_VT,
CTL_CR,
CTL_FF,
CTL_LF,
CTL_SET_HT,
PANEL_BUTTON_OFF,
PANEL_BUTTON_ON,
)
from .constants import TXT_STYLE
from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE
from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER
@@ -50,27 +84,28 @@ from escpos.capabilities import get_profile, BARCODE_B
@six.add_metaclass(ABCMeta)
class Escpos(object):
""" ESC/POS Printer object
"""ESC/POS Printer object
This class is the abstract base class for an esc/pos-printer. The printer implementations are children of this
class.
"""
device = None
def __init__(self, profile=None, magic_encode_args=None, **kwargs):
""" Initialize ESCPOS Printer
"""Initialize ESCPOS Printer
:param profile: Printer profile"""
self.profile = get_profile(profile)
self.magic = MagicEncode(self, **(magic_encode_args or {}))
def __del__(self):
""" call self.close upon deletion """
"""call self.close upon deletion"""
self.close()
@abstractmethod
def _raw(self, msg):
""" Sends raw data to the printer
"""Sends raw data to the printer
This function has to be individually implemented by the implementations.
@@ -80,14 +115,21 @@ class Escpos(object):
pass
def _read(self):
""" Returns a NotImplementedError if the instance of the class doesn't override this method.
"""Returns a NotImplementedError if the instance of the class doesn't override this method.
:raises NotImplementedError
"""
raise NotImplementedError()
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster",
fragment_height=960, center=False):
""" Print an image
def image(
self,
img_source,
high_density_vertical=True,
high_density_horizontal=True,
impl="bitImageRaster",
fragment_height=960,
center=False,
):
"""Print an image
You can select whether the printer should print in high density or not. The default value is high density.
When printing in low density, the image will be stretched.
@@ -116,14 +158,16 @@ class Escpos(object):
im = EscposImage(img_source)
try:
if self.profile.profile_data['media']['width']['pixels'] == "Unknown":
print("The media.width.pixel field of the printer profile is not set. " +
"The center flag will have no effect.")
if self.profile.profile_data["media"]["width"]["pixels"] == "Unknown":
print(
"The media.width.pixel field of the printer profile is not set. "
+ "The center flag will have no effect."
)
max_width = int(self.profile.profile_data['media']['width']['pixels'])
max_width = int(self.profile.profile_data["media"]["width"]["pixels"])
if im.width > max_width:
raise ImageWidthError('{} > {}'.format(im.width, max_width))
raise ImageWidthError("{} > {}".format(im.width, max_width))
if center:
im.center(max_width)
@@ -137,41 +181,59 @@ class Escpos(object):
if im.height > fragment_height:
fragments = im.split(fragment_height)
for fragment in fragments:
self.image(fragment,
high_density_vertical=high_density_vertical,
high_density_horizontal=high_density_horizontal,
impl=impl,
fragment_height=fragment_height)
self.image(
fragment,
high_density_vertical=high_density_vertical,
high_density_horizontal=high_density_horizontal,
impl=impl,
fragment_height=fragment_height,
)
return
if impl == "bitImageRaster":
# GS v 0, raster format bit image
density_byte = (0 if high_density_horizontal else 1) + (0 if high_density_vertical else 2)
header = GS + b"v0" + six.int2byte(density_byte) + self._int_low_high(im.width_bytes, 2) +\
self._int_low_high(im.height, 2)
density_byte = (0 if high_density_horizontal else 1) + (
0 if high_density_vertical else 2
)
header = (
GS
+ b"v0"
+ six.int2byte(density_byte)
+ self._int_low_high(im.width_bytes, 2)
+ self._int_low_high(im.height, 2)
)
self._raw(header + im.to_raster_format())
if impl == "graphics":
# GS ( L raster format graphics
img_header = self._int_low_high(im.width, 2) + self._int_low_high(im.height, 2)
tone = b'0'
colors = b'1'
img_header = self._int_low_high(im.width, 2) + self._int_low_high(
im.height, 2
)
tone = b"0"
colors = b"1"
ym = six.int2byte(1 if high_density_vertical else 2)
xm = six.int2byte(1 if high_density_horizontal else 2)
header = tone + xm + ym + colors + img_header
raster_data = im.to_raster_format()
self._image_send_graphics_data(b'0', b'p', header + raster_data)
self._image_send_graphics_data(b'0', b'2', b'')
self._image_send_graphics_data(b"0", b"p", header + raster_data)
self._image_send_graphics_data(b"0", b"2", b"")
if impl == "bitImageColumn":
# ESC *, column format bit image
density_byte = (1 if high_density_horizontal else 0) + (32 if high_density_vertical else 0)
header = ESC + b"*" + six.int2byte(density_byte) + self._int_low_high(im.width, 2)
density_byte = (1 if high_density_horizontal else 0) + (
32 if high_density_vertical else 0
)
header = (
ESC
+ b"*"
+ six.int2byte(density_byte)
+ self._int_low_high(im.width, 2)
)
outp = [ESC + b"3" + six.int2byte(16)] # Adjust line-feed size
for blob in im.to_column_format(high_density_vertical):
outp.append(header + blob + b"\n")
outp.append(ESC + b"2") # Reset line-feed size
self._raw(b''.join(outp))
self._raw(b"".join(outp))
def _image_send_graphics_data(self, m, fn, data):
"""
@@ -182,11 +244,19 @@ class Escpos(object):
:param data: Data to send
"""
header = self._int_low_high(len(data) + 2, 2)
self._raw(GS + b'(L' + header + m + fn + data)
self._raw(GS + b"(L" + header + m + fn + data)
def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2,
native=False, center=False, impl="bitImageRaster"):
""" Print QR Code for the provided string
def qr(
self,
content,
ec=QR_ECLEVEL_L,
size=3,
model=QR_MODEL_2,
native=False,
center=False,
impl="bitImageRaster",
):
"""Print QR Code for the provided string
:param content: The content of the code. Numeric data will be more efficiently compacted.
:param ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or
@@ -206,50 +276,60 @@ class Escpos(object):
if not 1 <= size <= 16:
raise ValueError("Invalid block size (must be 1-16)")
if model not in [QR_MODEL_1, QR_MODEL_2, QR_MICRO]:
raise ValueError("Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)")
raise ValueError(
"Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)"
)
if content == "":
# Handle edge case by printing nothing.
return
if not native:
# Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image
if model != QR_MODEL_2:
raise ValueError("Invalid QR model for qrlib rendering (must be QR_MODEL_2)")
raise ValueError(
"Invalid QR model for qrlib rendering (must be QR_MODEL_2)"
)
python_qr_ec = {
QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H,
QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L,
QR_ECLEVEL_M: qrcode.constants.ERROR_CORRECT_M,
QR_ECLEVEL_Q: qrcode.constants.ERROR_CORRECT_Q
QR_ECLEVEL_Q: qrcode.constants.ERROR_CORRECT_Q,
}
qr_code = qrcode.QRCode(version=None, box_size=size, border=1, error_correction=python_qr_ec[ec])
qr_code = qrcode.QRCode(
version=None, box_size=size, border=1, error_correction=python_qr_ec[ec]
)
qr_code.add_data(content)
qr_code.make(fit=True)
qr_img = qr_code.make_image()
im = qr_img._img.convert("RGB")
# Convert the RGB image in printable image
self.text('\n')
self.text("\n")
self.image(im, center=center, impl=impl)
self.text('\n')
self.text('\n')
self.text("\n")
self.text("\n")
return
if center:
raise NotImplementedError("Centering not implemented for native QR rendering")
raise NotImplementedError(
"Centering not implemented for native QR rendering"
)
# Native 2D code printing
cn = b'1' # Code type for QR code
cn = b"1" # Code type for QR code
# Select model: 1, 2 or micro.
self._send_2d_code_data(six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0))
self._send_2d_code_data(
six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0)
)
# Set dot size.
self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size))
# Set error correction level: L, M, Q, or H
self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec))
# Send content & print
self._send_2d_code_data(six.int2byte(80), cn, content.encode('utf-8'), b'0')
self._send_2d_code_data(six.int2byte(81), cn, b'', b'0')
self._send_2d_code_data(six.int2byte(80), cn, content.encode("utf-8"), b"0")
self._send_2d_code_data(six.int2byte(81), cn, b"", b"0")
def _send_2d_code_data(self, fn, cn, data, m=b''):
""" Wrapper for GS ( k, to calculate and send correct data length.
def _send_2d_code_data(self, fn, cn, data, m=b""):
"""Wrapper for GS ( k, to calculate and send correct data length.
:param fn: Function to use.
:param cn: Output code type. Affects available data.
@@ -259,28 +339,32 @@ class Escpos(object):
if len(m) > 1 or len(cn) != 1 or len(fn) != 1:
raise ValueError("cn and fn must be one byte each.")
header = self._int_low_high(len(data) + len(m) + 2, 2)
self._raw(GS + b'(k' + header + cn + fn + m + data)
self._raw(GS + b"(k" + header + cn + fn + m + data)
@staticmethod
def _int_low_high(inp_number, out_bytes):
""" Generate multiple bytes for a number: In lower and higher parts, or more parts as needed.
"""Generate multiple bytes for a number: In lower and higher parts, or more parts as needed.
:param inp_number: Input number
:param out_bytes: The number of bytes to output (1 - 4).
"""
max_input = (256 << (out_bytes * 8) - 1)
max_input = 256 << (out_bytes * 8) - 1
if not 1 <= out_bytes <= 4:
raise ValueError("Can only output 1-4 bytes")
if not 0 <= inp_number <= max_input:
raise ValueError("Number too large. Can only output up to {0} in {1} bytes".format(max_input, out_bytes))
outp = b''
raise ValueError(
"Number too large. Can only output up to {0} in {1} bytes".format(
max_input, out_bytes
)
)
outp = b""
for _ in range(0, out_bytes):
outp += six.int2byte(inp_number % 256)
inp_number //= 256
return outp
def charcode(self, code="AUTO"):
""" Set Character Code Table
"""Set Character Code Table
Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with
the next text sequence. If you set the variable code to ``AUTO`` it will try to automatically guess the
@@ -318,11 +402,23 @@ class Escpos(object):
return False
bounds, regex = BARCODE_FORMATS[bc]
return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(regex, code)
return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(
regex, code
)
def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A",
align_ct=True, function_type=None, check=True):
""" Print Barcode
def barcode(
self,
code,
bc,
height=64,
width=3,
pos="BELOW",
font="A",
align_ct=True,
function_type=None,
check=True,
):
"""Print Barcode
This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to
be supported by the unit. By default, this method will check whether your barcode text is correct, that is
@@ -407,38 +503,46 @@ class Escpos(object):
"""
if function_type is None:
# Choose the function type automatically.
if bc in BARCODE_TYPES['A']:
function_type = 'A'
if bc in BARCODE_TYPES["A"]:
function_type = "A"
else:
if bc in BARCODE_TYPES['B']:
if bc in BARCODE_TYPES["B"]:
if not self.profile.supports(BARCODE_B):
raise BarcodeTypeError((
"Barcode type '{bc} not supported for "
"the current printer profile").format(bc=bc))
function_type = 'B'
raise BarcodeTypeError(
(
"Barcode type '{bc} not supported for "
"the current printer profile"
).format(bc=bc)
)
function_type = "B"
else:
raise BarcodeTypeError((
"Barcode type '{bc} is not valid").format(bc=bc))
raise BarcodeTypeError(
("Barcode type '{bc} is not valid").format(bc=bc)
)
bc_types = BARCODE_TYPES[function_type.upper()]
if bc.upper() not in bc_types.keys():
raise BarcodeTypeError((
"Barcode '{bc}' not valid for barcode function type "
"{function_type}").format(
raise BarcodeTypeError(
(
"Barcode '{bc}' not valid for barcode function type "
"{function_type}"
).format(
bc=bc,
function_type=function_type,
))
)
)
if check and not self.check_barcode(bc, code):
raise BarcodeCodeError((
"Barcode '{code}' not in a valid format for type '{bc}'").format(
code=code,
bc=bc,
))
raise BarcodeCodeError(
("Barcode '{code}' not in a valid format for type '{bc}'").format(
code=code,
bc=bc,
)
)
# Align Bar Code()
if align_ct:
self._raw(TXT_STYLE['align']['center'])
self._raw(TXT_STYLE["align"]["center"])
# Height
if 1 <= height <= 255:
self._raw(BARCODE_HEIGHT + six.int2byte(height))
@@ -478,35 +582,47 @@ class Escpos(object):
if function_type.upper() == "A":
self._raw(NUL)
def soft_barcode(self, barcode_type, data, impl='bitImageColumn',
module_height=5, module_width=0.2, text_distance=1,
center=True):
def soft_barcode(
self,
barcode_type,
data,
impl="bitImageColumn",
module_height=5,
module_width=0.2,
text_distance=1,
center=True,
):
image_writer = ImageWriter()
# Check if barcode type exists
if barcode_type not in barcode.PROVIDED_BARCODES:
raise BarcodeTypeError(
'Barcode type {} not supported by software barcode renderer'
.format(barcode_type))
"Barcode type {} not supported by software barcode renderer".format(
barcode_type
)
)
# Render the barcode to a fake file
barcode_class = barcode.get_barcode_class(barcode_type)
my_code = barcode_class(data, writer=image_writer)
with open(os.devnull, "wb") as nullfile:
my_code.write(nullfile, {
'module_height': module_height,
'module_width': module_width,
'text_distance': text_distance
})
my_code.write(
nullfile,
{
"module_height": module_height,
"module_width": module_width,
"text_distance": text_distance,
},
)
# Retrieve the Pillow image and print it
image = my_code.writer._image
self.image(image, impl=impl, center=center)
def text(self, txt):
""" Print alpha-numeric text
"""Print alpha-numeric text
The text has to be encoded in the currently selected codepage.
The input text has to be encoded in unicode.
@@ -517,7 +633,7 @@ class Escpos(object):
txt = six.text_type(txt)
self.magic.write(txt)
def textln(self, txt=''):
def textln(self, txt=""):
"""Print alpha-numeric text with a newline
The text has to be encoded in the currently selected codepage.
@@ -526,7 +642,7 @@ class Escpos(object):
:param txt: text to be printed with a newline
:raises: :py:exc:`~escpos.exceptions.TextError`
"""
self.text('{}\n'.format(txt))
self.text("{}\n".format(txt))
def ln(self, count=1):
"""Print a newline or more
@@ -535,12 +651,12 @@ class Escpos(object):
:raises: :py:exc:`ValueError` if count < 0
"""
if count < 0:
raise ValueError('Count cannot be lesser than 0')
raise ValueError("Count cannot be lesser than 0")
if count > 0:
self.text('\n' * count)
self.text("\n" * count)
def block_text(self, txt, font="0", columns=None):
""" Text is printed wrapped to specified columns
"""Text is printed wrapped to specified columns
Text has to be encoded in unicode.
@@ -552,10 +668,23 @@ class Escpos(object):
col_count = self.profile.get_columns(font) if columns is None else columns
self.text(textwrap.fill(txt, col_count))
def set(self, align='left', font='a', bold=False, underline=0, width=1,
height=1, density=9, invert=False, smooth=False, flip=False,
double_width=False, double_height=False, custom_size=False):
""" Set text properties by sending them to the printer
def set(
self,
align="left",
font="a",
bold=False,
underline=0,
width=1,
height=1,
density=9,
invert=False,
smooth=False,
flip=False,
double_width=False,
double_height=False,
custom_size=False,
):
"""Set text properties by sending them to the printer
:param align: horizontal position for text, possible values are:
@@ -596,37 +725,41 @@ class Escpos(object):
"""
if custom_size:
if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and\
isinstance(height, int):
size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height]
if (
1 <= width <= 8
and 1 <= height <= 8
and isinstance(width, int)
and isinstance(height, int)
):
size_byte = TXT_STYLE["width"][width] + TXT_STYLE["height"][height]
self._raw(TXT_SIZE + six.int2byte(size_byte))
else:
raise SetVariableError()
else:
self._raw(TXT_NORMAL)
if double_width and double_height:
self._raw(TXT_STYLE['size']['2x'])
self._raw(TXT_STYLE["size"]["2x"])
elif double_width:
self._raw(TXT_STYLE['size']['2w'])
self._raw(TXT_STYLE["size"]["2w"])
elif double_height:
self._raw(TXT_STYLE['size']['2h'])
self._raw(TXT_STYLE["size"]["2h"])
else:
self._raw(TXT_STYLE['size']['normal'])
self._raw(TXT_STYLE["size"]["normal"])
self._raw(TXT_STYLE['flip'][flip])
self._raw(TXT_STYLE['smooth'][smooth])
self._raw(TXT_STYLE['bold'][bold])
self._raw(TXT_STYLE['underline'][underline])
self._raw(TXT_STYLE["flip"][flip])
self._raw(TXT_STYLE["smooth"][smooth])
self._raw(TXT_STYLE["bold"][bold])
self._raw(TXT_STYLE["underline"][underline])
self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
self._raw(TXT_STYLE['align'][align])
self._raw(TXT_STYLE["align"][align])
if density != 9:
self._raw(TXT_STYLE['density'][density])
self._raw(TXT_STYLE["density"][density])
self._raw(TXT_STYLE['invert'][invert])
self._raw(TXT_STYLE["invert"][invert])
def line_spacing(self, spacing=None, divisor=180):
""" Set line character spacing.
"""Set line character spacing.
If no spacing is given, we reset it to the default.
@@ -646,16 +779,19 @@ class Escpos(object):
if divisor not in LINESPACING_FUNCS:
raise ValueError("divisor must be either 360, 180 or 60")
if (divisor in [360, 180]
and (not(0 <= spacing <= 255))):
raise ValueError("spacing must be a int between 0 and 255 when divisor is 360 or 180")
if divisor == 60 and (not(0 <= spacing <= 85)):
raise ValueError("spacing must be a int between 0 and 85 when divisor is 60")
if divisor in [360, 180] and (not (0 <= spacing <= 255)):
raise ValueError(
"spacing must be a int between 0 and 255 when divisor is 360 or 180"
)
if divisor == 60 and (not (0 <= spacing <= 85)):
raise ValueError(
"spacing must be a int between 0 and 85 when divisor is 60"
)
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
def cut(self, mode='FULL', feed=True):
""" Cut paper.
def cut(self, mode="FULL", feed=True):
"""Cut paper.
Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will
be attempted. Note however, that not all models can do a partial cut. See the documentation of
@@ -667,28 +803,28 @@ class Escpos(object):
"""
if not feed:
self._raw(GS + b'V' + six.int2byte(66) + b'\x00')
self._raw(GS + b"V" + six.int2byte(66) + b"\x00")
return
self.print_and_feed(6)
mode = mode.upper()
if mode not in ('FULL', 'PART'):
if mode not in ("FULL", "PART"):
raise ValueError("Mode must be one of ('FULL', 'PART')")
if mode == "PART":
if self.profile.supports('paperPartCut'):
if self.profile.supports("paperPartCut"):
self._raw(PAPER_PART_CUT)
elif self.profile.supports('paperFullCut'):
elif self.profile.supports("paperFullCut"):
self._raw(PAPER_FULL_CUT)
elif mode == "FULL":
if self.profile.supports('paperFullCut'):
if self.profile.supports("paperFullCut"):
self._raw(PAPER_FULL_CUT)
elif self.profile.supports('paperPartCut'):
elif self.profile.supports("paperPartCut"):
self._raw(PAPER_PART_CUT)
def cashdraw(self, pin):
""" Send pulse to kick the cash drawer
"""Send pulse to kick the cash drawer
Kick cash drawer on pin 2 or pin 5 according to default parameter.
For non default parameter send a decimal sequence i.e. [27,112,48] or [27,112,0,25,255]
@@ -707,7 +843,7 @@ class Escpos(object):
raise CashDrawerError(err)
def linedisplay_select(self, select_display=False):
""" Selects the line display or the printer
"""Selects the line display or the printer
This method is used for line displays that are daisy-chained between your computer and printer.
If you set `select_display` to true, only the display is selected and if you set it to false,
@@ -722,7 +858,7 @@ class Escpos(object):
self._raw(LINE_DISPLAY_CLOSE)
def linedisplay_clear(self):
""" Clears the line display and resets the cursor
"""Clears the line display and resets the cursor
This method is used for line displays that are daisy-chained between your computer and printer.
"""
@@ -743,7 +879,7 @@ class Escpos(object):
self.linedisplay_select(select_display=False)
def hw(self, hw):
""" Hardware operations
"""Hardware operations
:param hw: hardware action, may be:
@@ -761,12 +897,12 @@ class Escpos(object):
pass
def print_and_feed(self, n=1):
""" Print data in print buffer and feed *n* lines
"""Print data in print buffer and feed *n* lines
if n not in range (0, 255) then ValueError will be raised
if n not in range (0, 255) then ValueError will be raised
:param n: number of n to feed. 0 <= n <= 255. default: 1
:raises ValueError: if not 0 <= n <= 255
:param n: number of n to feed. 0 <= n <= 255. default: 1
:raises ValueError: if not 0 <= n <= 255
"""
if 0 <= n <= 255:
# ESC d n
@@ -775,7 +911,7 @@ class Escpos(object):
raise ValueError("n must be betwen 0 and 255")
def control(self, ctl, count=5, tab_size=8):
""" Feed control sequences
"""Feed control sequences
:param ctl: string for the following control sequences:
@@ -797,9 +933,9 @@ class Escpos(object):
elif ctl.upper() == "CR":
self._raw(CTL_CR)
elif ctl.upper() == "HT":
if not (0 <= count <= 32 and
1 <= tab_size <= 255 and
count * tab_size < 256):
if not (
0 <= count <= 32 and 1 <= tab_size <= 255 and count * tab_size < 256
):
raise TabPosError()
else:
# Set tab positions
@@ -811,7 +947,7 @@ class Escpos(object):
self._raw(CTL_VT)
def panel_buttons(self, enable=True):
""" Controls the panel buttons on the printer (e.g. FEED)
"""Controls the panel buttons on the printer (e.g. FEED)
When enable is set to False the panel buttons on the printer will be disabled. Calling the method with
enable=True or without argument will enable the panel buttons.
@@ -872,11 +1008,11 @@ class Escpos(object):
status = self.query_status(RT_STATUS_PAPER)
if len(status) == 0:
return 2
if (status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER):
if status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER:
return 0
if (status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER):
if status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER:
return 1
if (status[0] & RT_MASK_PAPER == RT_MASK_PAPER):
if status[0] & RT_MASK_PAPER == RT_MASK_PAPER:
return 2
@@ -913,7 +1049,7 @@ class EscposIO(object):
self.autoclose = autoclose
def set(self, **kwargs):
""" Set the printer-parameters
"""Set the printer-parameters
Controls which parameters will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`.
For more information on the parameters see the :py:meth:`set() <escpos.escpos.Escpos.set()>`-methods
@@ -929,11 +1065,13 @@ class EscposIO(object):
params.update(kwargs)
if isinstance(text, six.text_type):
lines = text.split('\n')
lines = text.split("\n")
elif isinstance(text, list) or isinstance(text, tuple):
lines = text
else:
lines = ["{0}".format(text), ]
lines = [
"{0}".format(text),
]
# TODO check unicode handling
# TODO flush? or on print? (this should prob rather be handled by the _raw-method)
@@ -945,8 +1083,7 @@ class EscposIO(object):
self.printer.text("{0}\n".format(line))
def close(self):
""" called upon closing the `with`-statement
"""
"""called upon closing the `with`-statement"""
self.printer.close()
def __enter__(self, **kwargs):

View File

@@ -27,7 +27,8 @@ Result/Exit codes:
class Error(Exception):
""" Base class for ESC/POS errors """
"""Base class for ESC/POS errors"""
def __init__(self, msg, status=None):
Exception.__init__(self)
self.msg = msg
@@ -40,12 +41,13 @@ class Error(Exception):
class BarcodeTypeError(Error):
""" No Barcode type defined.
"""No Barcode type defined.
This exception indicates that no known barcode-type has been entered. The barcode-type has to be
one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`.
The returned error code is `10`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -56,12 +58,13 @@ class BarcodeTypeError(Error):
class BarcodeSizeError(Error):
""" Barcode size is out of range.
"""Barcode size is out of range.
This exception indicates that the values for the barcode size are out of range.
The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`.
The resulting returncode is `20`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -72,12 +75,13 @@ class BarcodeSizeError(Error):
class BarcodeCodeError(Error):
""" No Barcode code was supplied, or it is incorrect.
"""No Barcode code was supplied, or it is incorrect.
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter
was True and the check failed.
The returncode for this exception is `30`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -88,24 +92,28 @@ class BarcodeCodeError(Error):
class ImageSizeError(Error):
""" Image height is longer than 255px and can't be printed.
"""Image height is longer than 255px and can't be printed.
The returncode for this exception is `40`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 40
def __str__(self):
return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg)
return "Image height is longer than 255px and can't be printed ({msg})".format(
msg=self.msg
)
class ImageWidthError(Error):
""" Image width is too large.
"""Image width is too large.
The return code for this exception is `41`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -116,26 +124,30 @@ class ImageWidthError(Error):
class TextError(Error):
""" Text string must be supplied to the `text()` method.
"""Text string must be supplied to the `text()` method.
This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`.
The returncode for this exception is `50`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 50
def __str__(self):
return "Text string must be supplied to the text() method ({msg})".format(msg=self.msg)
return "Text string must be supplied to the text() method ({msg})".format(
msg=self.msg
)
class CashDrawerError(Error):
""" Valid pin must be set in order to send pulse.
"""Valid pin must be set in order to send pulse.
A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`.
The returncode for this exception is `60`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -146,27 +158,31 @@ class CashDrawerError(Error):
class TabPosError(Error):
""" Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
"""Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
Both values multiplied must not exceed 255, since it is the maximum tab value.
This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
The returncode for this exception is `70`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 70
def __str__(self):
return "Valid tab positions must be in the range 0 to 16 ({msg})".format(msg=self.msg)
return "Valid tab positions must be in the range 0 to 16 ({msg})".format(
msg=self.msg
)
class CharCodeError(Error):
""" Valid char code must be set.
"""Valid char code must be set.
The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown.
Ths returncode for this exception is `80`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -177,11 +193,12 @@ class CharCodeError(Error):
class USBNotFoundError(Error):
""" Device wasn't found (probably not plugged in)
"""Device wasn't found (probably not plugged in)
The USB device seems to be not plugged in.
Ths returncode for this exception is `90`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -192,11 +209,12 @@ class USBNotFoundError(Error):
class SetVariableError(Error):
""" A set method variable was out of range
"""A set method variable was out of range
Check set variables against minimum and maximum values
Ths returncode for this exception is `100`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -208,12 +226,14 @@ class SetVariableError(Error):
# Configuration errors
class ConfigNotFoundError(Error):
""" The configuration file was not found
"""The configuration file was not found
The default or passed configuration file could not be read
Ths returncode for this exception is `200`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -224,11 +244,12 @@ class ConfigNotFoundError(Error):
class ConfigSyntaxError(Error):
""" The configuration file is invalid
"""The configuration file is invalid
The syntax is incorrect
Ths returncode for this exception is `210`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -239,11 +260,12 @@ class ConfigSyntaxError(Error):
class ConfigSectionMissingError(Error):
""" The configuration file is missing a section
"""The configuration file is missing a section
The part of the config asked for doesn't exist in the loaded configuration
Ths returncode for this exception is `220`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg

View File

@@ -37,7 +37,7 @@ class EscposImage(object):
# Convert to white RGB background, paste over white background
# to strip alpha.
img_original = img_original.convert('RGBA')
img_original = img_original.convert("RGBA")
im = Image.new("RGB", img_original.size, (255, 255, 255))
im.paste(img_original, mask=img_original.split()[3])
# Convert down to greyscale
@@ -85,7 +85,7 @@ class EscposImage(object):
box = (left, top, left + line_height, top + height_pixels)
im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box)
im_bytes = im_slice.tobytes()
yield(im_bytes)
yield (im_bytes)
left += line_height
def to_raster_format(self):
@@ -101,7 +101,7 @@ class EscposImage(object):
:param fragment_height: height of fragment
:return: list of PIL objects
"""
passes = int(math.ceil(self.height/fragment_height))
passes = int(math.ceil(self.height / fragment_height))
fragments = []
for n in range(0, passes):
left = 0

View File

@@ -34,69 +34,68 @@ def encode_katakana(text):
TXT_ENC_KATAKANA_MAP = {
# Maps UTF-8 Katakana symbols to KATAKANA Page Codes
# TODO: has this really to be hardcoded?
# Half-Width Katakanas
'': b'\xa1',
'': b'\xa2',
'': b'\xa3',
'': b'\xa4',
'': b'\xa5',
'': b'\xa6',
'': b'\xa7',
'': b'\xa8',
'': b'\xa9',
'': b'\xaa',
'': b'\xab',
'': b'\xac',
'': b'\xad',
'': b'\xae',
'': b'\xaf',
'': b'\xb0',
'': b'\xb1',
'': b'\xb2',
'': b'\xb3',
'': b'\xb4',
'': b'\xb5',
'': b'\xb6',
'': b'\xb7',
'': b'\xb8',
'': b'\xb9',
'': b'\xba',
'': b'\xbb',
'': b'\xbc',
'': b'\xbd',
'': b'\xbe',
'ソ': b'\xbf',
'': b'\xc0',
'': b'\xc1',
'': b'\xc2',
'': b'\xc3',
'': b'\xc4',
'': b'\xc5',
'': b'\xc6',
'': b'\xc7',
'': b'\xc8',
'': b'\xc9',
'': b'\xca',
'': b'\xcb',
'': b'\xcc',
'': b'\xcd',
'': b'\xce',
'': b'\xcf',
'': b'\xd0',
'': b'\xd1',
'': b'\xd2',
'': b'\xd3',
'': b'\xd4',
'': b'\xd5',
'': b'\xd6',
'': b'\xd7',
'': b'\xd8',
'': b'\xd9',
'': b'\xda',
'': b'\xdb',
'': b'\xdc',
'': b'\xdd',
'': b'\xde',
'': b'\xdf',
"": b"\xa1",
"": b"\xa2",
"": b"\xa3",
"": b"\xa4",
"": b"\xa5",
"": b"\xa6",
"": b"\xa7",
"": b"\xa8",
"": b"\xa9",
"": b"\xaa",
"": b"\xab",
"": b"\xac",
"": b"\xad",
"": b"\xae",
"": b"\xaf",
"": b"\xb0",
"": b"\xb1",
"": b"\xb2",
"": b"\xb3",
"": b"\xb4",
"": b"\xb5",
"": b"\xb6",
"": b"\xb7",
"": b"\xb8",
"": b"\xb9",
"": b"\xba",
"": b"\xbb",
"": b"\xbc",
"": b"\xbd",
"": b"\xbe",
"ソ": b"\xbf",
"": b"\xc0",
"": b"\xc1",
"": b"\xc2",
"": b"\xc3",
"": b"\xc4",
"": b"\xc5",
"": b"\xc6",
"": b"\xc7",
"": b"\xc8",
"": b"\xc9",
"": b"\xca",
"": b"\xcb",
"": b"\xcc",
"": b"\xcd",
"": b"\xce",
"": b"\xcf",
"": b"\xd0",
"": b"\xd1",
"": b"\xd2",
"": b"\xd3",
"": b"\xd4",
"": b"\xd5",
"": b"\xd6",
"": b"\xd7",
"": b"\xd8",
"": b"\xd9",
"": b"\xda",
"": b"\xdb",
"": b"\xdc",
"": b"\xdd",
"": b"\xde",
"": b"\xdf",
}

View File

@@ -55,10 +55,12 @@ class Encoder(object):
"""
encoding = CodePages.get_encoding_name(encoding)
if encoding not in self.codepages:
raise ValueError((
'Encoding "{}" cannot be used for the current profile. '
'Valid encodings are: {}'
).format(encoding, ','.join(self.codepages.keys())))
raise ValueError(
(
'Encoding "{}" cannot be used for the current profile. '
"Valid encodings are: {}"
).format(encoding, ",".join(self.codepages.keys()))
)
return encoding
@staticmethod
@@ -70,16 +72,18 @@ class Encoder(object):
:param encoding: The name of the encoding. This must appear in the CodePage list
"""
codepage = CodePages.get_encoding(encoding)
if 'data' in codepage:
encodable_chars = list("".join(codepage['data']))
assert(len(encodable_chars) == 128)
if "data" in codepage:
encodable_chars = list("".join(codepage["data"]))
assert len(encodable_chars) == 128
return encodable_chars
elif 'python_encode' in codepage:
elif "python_encode" in codepage:
encodable_chars = [u" "] * 128
for i in range(0, 128):
codepoint = i + 128
try:
encodable_chars[i] = bytes([codepoint]).decode(codepage['python_encode'])
encodable_chars[i] = bytes([codepoint]).decode(
codepage["python_encode"]
)
except UnicodeDecodeError:
# Non-encodable character, just skip it
pass
@@ -87,7 +91,7 @@ class Encoder(object):
raise LookupError("Can't find a known encoding for {}".format(encoding))
def _get_codepage_char_map(self, encoding):
""" Get codepage character map
"""Get codepage character map
Process an encoding and return a map of UTF-characters to code points
in this encoding.
@@ -100,7 +104,9 @@ class Encoder(object):
if encoding in self.available_characters:
return self.available_characters[encoding]
codepage_char_list = self._get_codepage_char_list(encoding)
codepage_char_map = dict((utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list))
codepage_char_map = dict(
(utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list)
)
self.available_characters[encoding] = codepage_char_map
return codepage_char_map
@@ -123,7 +129,7 @@ class Encoder(object):
@staticmethod
def _encode_char(char, charmap, defaultchar):
""" Encode a single character with the given encoding map
"""Encode a single character with the given encoding map
:param char: char to encode
:param charmap: dictionary for mapping characters in this code page
@@ -134,23 +140,22 @@ class Encoder(object):
return charmap[char]
return ord(defaultchar)
def encode(self, text, encoding, defaultchar='?'):
""" Encode text under the given encoding
def encode(self, text, encoding, defaultchar="?"):
"""Encode text under the given encoding
:param text: Text to encode
:param encoding: Encoding name to use (must be defined in capabilities)
:param defaultchar: Fallback for non-encodable characters
"""
codepage_char_map = self._get_codepage_char_map(encoding)
output_bytes = bytes([self._encode_char(char, codepage_char_map, defaultchar) for char in text])
output_bytes = bytes(
[self._encode_char(char, codepage_char_map, defaultchar) for char in text]
)
return output_bytes
def __encoding_sort_func(self, item):
key, index = item
return (
key in self.used_encodings,
index
)
return (key in self.used_encodings, index)
def find_suitable_encoding(self, char):
"""The order of our search is a specific one:
@@ -168,9 +173,7 @@ class Encoder(object):
that the code page we pick for this character is actually
supported.
"""
sorted_encodings = sorted(
self.codepages.items(),
key=self.__encoding_sort_func)
sorted_encodings = sorted(self.codepages.items(), key=self.__encoding_sort_func)
for encoding, _ in sorted_encodings:
if self.can_encode(encoding, char):
@@ -205,8 +208,10 @@ class MagicEncode(object):
If the printer does not support a suitable code page, it can
insert an error character.
"""
def __init__(self, driver, encoding=None, disabled=False,
defaultsymbol='?', encoder=None):
def __init__(
self, driver, encoding=None, disabled=False, defaultsymbol="?", encoder=None
):
"""
:param driver:
@@ -219,7 +224,7 @@ class MagicEncode(object):
:param encoder:
"""
if disabled and not encoding:
raise Error('If you disable magic encode, you need to define an encoding!')
raise Error("If you disable magic encode, you need to define an encoding!")
self.driver = driver
self.encoder = encoder or Encoder(driver.profile.get_code_pages())
@@ -241,8 +246,7 @@ class MagicEncode(object):
self.disabled = True
def write(self, text):
"""Write the text, automatically switching encodings.
"""
"""Write the text, automatically switching encodings."""
if self.disabled:
self.write_with_encoding(self.encoding, text)
@@ -268,25 +272,26 @@ class MagicEncode(object):
self.write_with_encoding(encoding, to_write)
def _handle_character_failed(self, char):
"""Called when no codepage was found to render a character.
"""
"""Called when no codepage was found to render a character."""
# Writing the default symbol via write() allows us to avoid
# unnecesary codepage switches.
self.write(self.defaultsymbol)
def write_with_encoding(self, encoding, text):
if text is not None and type(text) is not six.text_type:
raise Error("The supplied text has to be unicode, but is of type {type}.".format(
type=type(text)
))
raise Error(
"The supplied text has to be unicode, but is of type {type}.".format(
type=type(text)
)
)
# We always know the current code page; if the new codepage
# is different, emit a change command.
if encoding != self.encoding:
self.encoding = encoding
self.driver._raw(
CODEPAGE_CHANGE +
six.int2byte(self.encoder.get_sequence(encoding)))
CODEPAGE_CHANGE + six.int2byte(self.encoder.get_sequence(encoding))
)
if text:
self.driver._raw(self.encoder.encode(text, encoding))

View File

@@ -19,7 +19,7 @@ from .exceptions import USBNotFoundError
class Usb(Escpos):
""" USB printer
"""USB printer
This class describes a printer that natively speaks USB.
@@ -30,8 +30,17 @@ class Usb(Escpos):
"""
def __init__(self, idVendor, idProduct, usb_args=None, timeout=0, in_ep=0x82, out_ep=0x01,
*args, **kwargs): # noqa: N803
def __init__(
self,
idVendor,
idProduct,
usb_args=None,
timeout=0,
in_ep=0x82,
out_ep=0x01,
*args,
**kwargs
): # noqa: N803
"""
:param idVendor: Vendor ID
:param idProduct: Product ID
@@ -47,13 +56,13 @@ class Usb(Escpos):
usb_args = usb_args or {}
if idVendor:
usb_args['idVendor'] = idVendor
usb_args["idVendor"] = idVendor
if idProduct:
usb_args['idProduct'] = idProduct
usb_args["idProduct"] = idProduct
self.open(usb_args)
def open(self, usb_args):
""" Search device on USB tree and set it as escpos device.
"""Search device on USB tree and set it as escpos device.
:param usb_args: USB arguments
"""
@@ -92,7 +101,7 @@ class Usb(Escpos):
print("Could not set configuration: {0}".format(str(e)))
def _raw(self, msg):
""" Print any command sent in raw format
"""Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
@@ -100,18 +109,18 @@ class Usb(Escpos):
self.device.write(self.out_ep, msg, self.timeout)
def _read(self):
""" Reads a data buffer and returns it to the caller. """
"""Reads a data buffer and returns it to the caller."""
return self.device.read(self.in_ep, 16)
def close(self):
""" Release USB interface """
"""Release USB interface"""
if self.device:
usb.util.dispose_resources(self.device)
self.device = None
class Serial(Escpos):
""" Serial printer
"""Serial printer
This class describes a printer that is connected by serial interface.
@@ -122,9 +131,19 @@ class Serial(Escpos):
"""
def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1,
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
xonxoff=False, dsrdtr=True, *args, **kwargs):
def __init__(
self,
devfile="/dev/ttyS0",
baudrate=9600,
bytesize=8,
timeout=1,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
xonxoff=False,
dsrdtr=True,
*args,
**kwargs
):
"""
:param devfile: Device file under dev filesystem
@@ -149,13 +168,19 @@ class Serial(Escpos):
self.open()
def open(self):
""" Setup serial port and set is as escpos device """
"""Setup serial port and set is as escpos device"""
if self.device is not None and self.device.is_open:
self.close()
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate,
bytesize=self.bytesize, parity=self.parity,
stopbits=self.stopbits, timeout=self.timeout,
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
self.device = serial.Serial(
port=self.devfile,
baudrate=self.baudrate,
bytesize=self.bytesize,
parity=self.parity,
stopbits=self.stopbits,
timeout=self.timeout,
xonxoff=self.xonxoff,
dsrdtr=self.dsrdtr,
)
if self.device is not None:
print("Serial printer enabled")
@@ -163,7 +188,7 @@ class Serial(Escpos):
print("Unable to open serial printer on: {0}".format(str(self.devfile)))
def _raw(self, msg):
""" Print any command sent in raw format
"""Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
@@ -171,18 +196,18 @@ class Serial(Escpos):
self.device.write(msg)
def _read(self):
""" Reads a data buffer and returns it to the caller. """
"""Reads a data buffer and returns it to the caller."""
return self.device.read(16)
def close(self):
""" Close Serial interface """
"""Close Serial interface"""
if self.device is not None and self.device.is_open:
self.device.flush()
self.device.close()
class Network(Escpos):
""" Network printer
"""Network printer
This class is used to attach to a networked printer. You can also use this in order to attach to a printer that
is forwarded with ``socat``.
@@ -218,7 +243,7 @@ class Network(Escpos):
self.open()
def open(self):
""" Open TCP socket with ``socket``-library and set it as escpos device """
"""Open TCP socket with ``socket``-library and set it as escpos device"""
self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.device.settimeout(self.timeout)
self.device.connect((self.host, self.port))
@@ -227,7 +252,7 @@ class Network(Escpos):
print("Could not open socket for {0}".format(self.host))
def _raw(self, msg):
""" Print any command sent in raw format
"""Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
@@ -235,12 +260,12 @@ class Network(Escpos):
self.device.sendall(msg)
def _read(self):
""" Read data from the TCP socket """
"""Read data from the TCP socket"""
return self.device.recv(16)
def close(self):
""" Close TCP connection """
"""Close TCP connection"""
if self.device is not None:
try:
self.device.shutdown(socket.SHUT_RDWR)
@@ -250,7 +275,7 @@ class Network(Escpos):
class File(Escpos):
""" Generic file printer
"""Generic file printer
This class is used for parallel port printer or other printers that are directly attached to the filesystem.
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
@@ -275,18 +300,18 @@ class File(Escpos):
self.open()
def open(self):
""" Open system file """
"""Open system file"""
self.device = open(self.devfile, "wb")
if self.device is None:
print("Could not open the specified file {0}".format(self.devfile))
def flush(self):
""" Flush printing content """
"""Flush printing content"""
self.device.flush()
def _raw(self, msg):
""" Print any command sent in raw format
"""Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
@@ -296,14 +321,14 @@ class File(Escpos):
self.flush()
def close(self):
""" Close system file """
"""Close system file"""
if self.device is not None:
self.device.flush()
self.device.close()
class Dummy(Escpos):
""" Dummy printer
"""Dummy printer
This class is used for saving commands to a variable, for use in situations where
there is no need to send commands to an actual printer. This includes
@@ -317,13 +342,12 @@ class Dummy(Escpos):
"""
def __init__(self, *args, **kwargs):
"""
"""
""" """
Escpos.__init__(self, *args, **kwargs)
self._output_list = []
def _raw(self, msg):
""" Print any command sent in raw format
"""Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
@@ -332,11 +356,11 @@ class Dummy(Escpos):
@property
def output(self):
""" Get the data that was sent to this printer """
return b''.join(self._output_list)
"""Get the data that was sent to this printer"""
return b"".join(self._output_list)
def clear(self):
""" Clear the buffer of the printer
"""Clear the buffer of the printer
This method can be called if you send the contents to a physical printer
and want to use the Dummy printer for new output.
@@ -356,6 +380,7 @@ except ImportError:
pass
if _WIN32PRINT:
class Win32Raw(Escpos):
def __init__(self, printer_name=None, *args, **kwargs):
Escpos.__init__(self, *args, **kwargs)
@@ -370,7 +395,9 @@ if _WIN32PRINT:
if self.printer_name is None:
raise Exception("Printer not found")
self.hPrinter = win32print.OpenPrinter(self.printer_name)
self.current_job = win32print.StartDocPrinter(self.hPrinter, 1, (job_name, None, "RAW"))
self.current_job = win32print.StartDocPrinter(
self.hPrinter, 1, (job_name, None, "RAW")
)
win32print.StartPagePrinter(self.hPrinter)
def close(self):
@@ -382,7 +409,7 @@ if _WIN32PRINT:
self.hPrinter = None
def _raw(self, msg):
""" Print any command sent in raw format
"""Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes