Wiki

Clone wiki

Pipsta / Printing to 2 Pipstas from 1 Pi

Overview

This bulletin describes a method of modifying the BasicPrint.py script to support printing to two Pipsta printers connected to a single Raspberry Pi. This is useful for implementations where tickets need to be printed at different locations, e.g. customer receipts and kitchen food orders.

Background

The BasicPrint.py example prints simple text to a single Pipsta. two_pipstas.py shows how communications can be established with two printers from a single Pi.

Example

A new script, two_pipstas.py has been added to the repository as of 24th February 2016. If your git clone or download pre-dates this, you may wish to get an update of the repository now. For convenience, however, this script is also shown in full below:

#!python
# two_pipstas.py
# Copyright (c) 2016 Able Systems Limited. All rights reserved.
'''This simple code example is provided as-is, and is for demonstration
purposes only. Able Systems takes no responsibility for any system
implementations based on this code.

This very simple python script hack establishes USB communication with
TWO Pipsta printers and sends a simple text string to both.

The hack is heavily based on BasicPrint.py.

Copyright (c) 2016 Able Systems Limited. All rights reserved.
'''
import argparse
import platform
import sys
import time

import usb.core
import usb.util

FEED_PAST_CUTTER = b'\n' * 5
USB_BUSY = 66

# NOTE: The following section establishes communication to the Pipsta printer
# via USB. YOU DO NOT NEED TO UNDERSTAND THIS SECTION TO PROGRESS WITH THE
# TUTORIALS! ALTERING THIS SECTION IN ANY WAY CAN CAUSE A FAILURE TO COMMUNICATE
# WITH THE PIPSTA. If you are interested in learning about what is happening
# herein, please look at the following references:
#
# PyUSB: http://sourceforge.net/apps/trac/pyusb/
# ...which is a wrapper for...
# LibUSB: http://www.libusb.org/
#
# For full help on PyUSB, at the IDLE prompt, type:
# >>> import usb
# >>> help(usb)
# 'Deeper' help can be trawled by (e.g.):
# >>> help(usb.core)
#
# or at the Linux prompt, type:
# pydoc usb
# pydoc usb.core
PIPSTA_USB_VENDOR_ID = 0x0483
PIPSTA_USB_PRODUCT_ID = 0xA053

def parse_arguments():
    '''Parse the arguments passed to the script looking for a font file name
    and a text string to print.  If either are mssing defaults are used.
    '''
    txt = 'Hello World from Pipsta!'
    parser = argparse.ArgumentParser()
    parser.add_argument('text', help='the text to print',
                        nargs='*', default=txt.split())
    args = parser.parse_args()

    return ' '.join(args.text)

def main():
    """The main loop of the application.  Wrapping the code in a function
    prevents it being executed when various tools import the code.
    """
    if platform.system() != 'Linux':
        sys.exit('This script has only been written for Linux')

    # The following line finds all connected Pipstas
    printers = usb.core.find(find_all=True, idVendor=PIPSTA_USB_VENDOR_ID,
                        idProduct=PIPSTA_USB_PRODUCT_ID)

    # We can iterate through the connected Pipstas using next()
    dev1 = printers.next()
    if dev1 is None:  # if no such device is connected...
        raise IOError('Printer  not found')  # ...report error

    try:
        # Linux requires USB devices to be reset before configuring, may not be
        # required on other operating systems.
        dev1.reset()

        # Initialisation. Passing no arguments sets the configuration to the
        # currently active configuration.
        dev1.set_configuration()
    except usb.core.USBError as ex:
        raise IOError('Failed to configure the printer', ex)


    # The following steps get an 'Endpoint instance'. It uses
    # PyUSB's versatile find_descriptor functionality to claim
    # the interface and get a handle to the endpoint
    # An introduction to this (forming the basis of the code below)
    # can be found at:

    cfg1 = dev1.get_active_configuration()  # Get a handle to the active interface

    interface_number1 = cfg1[(0, 0)].bInterfaceNumber
    # added to silence Linux complaint about unclaimed interface, it should be
    # release automatically
    usb.util.claim_interface(dev1, interface_number1)
    alternate_setting1 = usb.control.get_interface(dev1, interface_number1)
    interface1 = usb.util.find_descriptor(
        cfg1, bInterfaceNumber=interface_number1,
        bAlternateSetting=alternate_setting1)

    usb_endpoint1 = usb.util.find_descriptor(
        interface1,
        custom_match=lambda e:
        usb.util.endpoint_direction(e.bEndpointAddress) ==
        usb.util.ENDPOINT_OUT
    )

    if usb_endpoint1 is None:  # check we have a real endpoint handle
        raise IOError("Could not find an endpoint to print to")


   # Move onto the next Pipsta...
    dev2 = printers.next()
    if dev2 is None:  # if no such device is connected...
        raise IOError('Printer  not found')  # ...report error

    try:
        # Linux requires USB devices to be reset before configuring, may not be
        # required on other operating systems.
        dev2.reset()


        # Initialisation. Passing no arguments sets the configuration to the
        # currently active configuration.
        dev2.set_configuration()
    except usb.core.USBError as ex:
        raise IOError('Failed to configure the printer', ex)

    cfg2 = dev2.get_active_configuration()  # Get a handle to the active interface

    interface_number2 = cfg2[(0, 0)].bInterfaceNumber
    # added to silence Linux complaint about unclaimed interface, it should be
    # release automatically
    usb.util.claim_interface(dev2, interface_number2)
    alternate_setting2 = usb.control.get_interface(dev2, interface_number2)
    interface2 = usb.util.find_descriptor(
        cfg2, bInterfaceNumber=interface_number2,
        bAlternateSetting=alternate_setting2)

    usb_endpoint2 = usb.util.find_descriptor(
        interface2,
        custom_match=lambda e:
        usb.util.endpoint_direction(e.bEndpointAddress) ==
        usb.util.ENDPOINT_OUT
    )

    if usb_endpoint2 is None:  # check we have a real endpoint handle
        raise IOError("Could not find an endpoint to print to")

    # Now that the USB endpoints are open, we can start to send data to the
    # printers.
    # The following opens the text_file, by using the 'with' statemnent there is
    # no need to close the text_file manually.  This method ensures that the
    # close is called in all situation (including unhandled exceptions).

    txt = parse_arguments()
    usb_endpoint1.write(b'\x1b!\x00')   
    usb_endpoint1.write("{}:{}".format(txt,"\nStation 1"))
    usb_endpoint1.write(FEED_PAST_CUTTER)
    time.sleep(3)
    usb_endpoint2.write(b'\x1b!\x00')
    usb_endpoint2.write("{}:{}".format(txt,"\nStation 2"))
    usb_endpoint2.write(FEED_PAST_CUTTER)
    usb.util.dispose_resources(dev1)
    usb.util.dispose_resources(dev2)

# Ensure that two_pipstas.py is ran in a stand-alone fashion (as intended) and not
# imported as a module. Prevents accidental execution of code.
if __name__ == '__main__':
    main()

How it Works

NOTE: The script sends only simple text (i.e. no graphics) to the printer.

• The script interrogates the USB for all devices with the appropriate Pipsta USB Vendor ID (VID) and Product ID (PID)

• This is achieved by supplying find_all=True during the find() query

• Rather than opening a single USB endpoint, two endpoints are opened

• This is achieved by using the next() method to iterate through the list of returned printers

• In practice, this simple code handles 2 such printers only.

• For ease of visibility, the code style from BasicPrint.py has been maintained

• Evidently a more 'Pythonic' approach is achievable

• In principle, the code could be adjusted for more than 2 Pipsta printers. We would be interested to know how any such experiments go - please let us know if you are trying this: particularly if you're attempting >4 printers (using a USB hub.)

• If this script is attempted on a Pi with only a single Pipsta attached, you will see the following:

two_pipstas-with only 1 Pipsta.png

[END]

Updated