Skip to content

Instantly share code, notes, and snippets.

@featherbear
Forked from stecman/_readme.md
Created August 28, 2022 15:17
Show Gist options
  • Select an option

  • Save featherbear/621493bb08ba402321d6df8d6b5b6bd6 to your computer and use it in GitHub Desktop.

Select an option

Save featherbear/621493bb08ba402321d6df8d6b5b6bd6 to your computer and use it in GitHub Desktop.

Revisions

  1. @stecman stecman revised this gist Dec 21, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion _readme.md
    Original file line number Diff line number Diff line change
    @@ -62,7 +62,7 @@ Image data is sent to the printer as a 1-bit-per-pixel bitmap. The Brother app s

    Once in the correct orientation, image data needs to be mirrored horizontally (with the settings above at least). It looks like the command `1B 69 4D` can be used to enable mirroring by the printer, but I haven't tested this.

    The outer edges of a 12mm label do not appear to be printable (print head too narrow?). The outer 10-20 pixels of each side (length-wise) is not printed. I haven't tested with narrower labels.
    The outer edges of a 12mm label do not appear to be printable (print head too narrow?). The outer 30 pixels of each side (length-wise) are not printed. I haven't tested with narrower labels.

    ## Python code

  2. @stecman stecman revised this gist Dec 10, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion _readme.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # Controlling the Brother P-Touch Cube from a computer
    # Controlling the Brother P-Touch Cube label maker from a computer

    The Brother PTP300BT label maker is intended to be controlled using the official Brother P-Touch Design & Print iOS/Android app. The app has arbitrary limits on what you can print (1 text object and up to 3 preset icons), so I thought it would be a fun challenge to reverse engineer the protocol to print whatever I wanted.

  3. @stecman stecman revised this gist Dec 10, 2017. 1 changed file with 12 additions and 10 deletions.
    22 changes: 12 additions & 10 deletions _readme.md
    Original file line number Diff line number Diff line change
    @@ -1,22 +1,24 @@
    # Controlling the Brother P-Touch Cube from a computer

    The Brother PTP300BT label maker is intended to be controlled using the official Brother P-Touch Design & Print iOS/Android app. The app has arbitrary limits on what you can print (1 text object and up to 3 preset icons), so I though it would be a fun challenge to reverse engineer the protocol to print whatever I wanted.
    The Brother PTP300BT label maker is intended to be controlled using the official Brother P-Touch Design & Print iOS/Android app. The app has arbitrary limits on what you can print (1 text object and up to 3 preset icons), so I thought it would be a fun challenge to reverse engineer the protocol to print whatever I wanted.

    Python code at the bottom if you want to skip the fine details.

    ## Process

    Intitially I had a quick peek at the Android APK to see if there was any useful information inside. The code that handles the communication with the printer in *Print&Design* turned out to be a native library (at least on Android), but the app clearly prepares a bitmap image and passes it to this native library for printing. Bitmaps are something we can work with.
    Intitially I had a quick peek at the Android APK to see if there was any useful information inside. The code that handles the communication with the printer in *Print&Design* turned out to be a native library, but the app clearly prepares a bitmap image and passes it to this native library for printing. Bitmaps are definitely something we can work with.

    Next I used the bluetooth sniffing capability of stock Android to capture a few label prints using the official app. Inspecting these packet captures in Wireshark, it was apparent that all of the communication was done using bluetooth's serial port profile (SPP). The printer also shows up as "Fujitsu" in the packet captures, and since Brother has a lot of label maker products I figured there was a good chance they were using some existing hardware and firmware with a bluetooth to serial adapter bolted on.
    Next I used the bluetooth sniffing capability of stock Android to capture a few label prints from the official app. Inspecting these packet captures in Wireshark, it was apparent that all of the communication used bluetooth's serial port profile (SPP). Interestingly, the the printer shows up as "Fujitsu" in packet captures, and since Brother has a lot of label maker products I figured there was a good chance they were using some existing label maker hardware and firmware with a bluetooth to serial adapter bolted on.

    After a little Googling, this hunch paid off - a bunch of developer documentation for some of Brother's higher-end label maker products that matched up with the bytes being sent over the bluetooth to serial connection. Mainly:
    After a little Googling, this hunch paid off - a bunch of developer documentation for some of Brother's higher-end/business label maker products matched up with the bytes being sent over the bluetooth serial connection. Mainly:

    - [PT-9500PC Command Reference: CBP-RASTER Mode (PTCBP Mode) Volume](http://etc.nkadesign.com/uploads/Printers/95CRRASE.pdf)

    At first I found a manual for ESC/P, which has the same command format, initialisation command and 32 byte status format, but the P-Touch Cube doesn't appear to support this (based on trying the ESC/P commands on the device).
    At first I found similarities in a manual for Brother's ESC/P protocol, which has the same command format, initialisation command and 32 byte status format, but the P-Touch Cube doesn't appear to support this (based on trying the ESC/P commands on the device).

    ## Serial protocol

    From a few Brother developer docs, the packet captures could be broken down as:
    From Brother's developer docs for a different device, the packet captures could be broken down as:

    ```
    // 64 bytes of 0x0 (to clear print buffer?)
    @@ -33,7 +35,7 @@ From a few Brother developer docs, the packet captures could be broken down as:
    // See http://www.undocprint.org/formats/page_description_languages/brother_p-touch
    1B 69 7A C4 01 0C 00 08 01 00 00 00 00
    // Set expanded mode bits (set chain off with a mask)
    // Set expanded mode bits (print chaining: off)
    1B 69 4B 08
    // Set mode bits (mirror print: no, auto tape cut: no)
    @@ -56,15 +58,15 @@ From a few Brother developer docs, the packet captures could be broken down as:

    ## Image data

    Image data is sent to the printer as a 1-bit-per-pixel bitmap. The Brother app sends a 128 pixel wide image (regardless of tape width), oriented as lines across the print head. For a horizontal label (printing along the length of tape), the input image needs to be rotated by 90 degrees.
    Image data is sent to the printer as a 1-bit-per-pixel bitmap. The Brother app sends a 128 pixel wide image (regardless of tape width), oriented as lines across the print head. For a horizontal label (printing along the length of tape), the input image needs to be rotated by 90 degrees before sending.

    Once in the correct orientation, image data needs to be mirrored horizontally (with the settings above at least). It looks like the command `1B 69 4D` can be used to enable mirroring by the printer, but I haven't tested this.

    The outer edges of a 12mm label do not appear to be printable (print head too narrow?). The outer 10-20 pixels on each side is not printed. I haven't tested with narrower labels.
    The outer edges of a 12mm label do not appear to be printable (print head too narrow?). The outer 10-20 pixels of each side (length-wise) is not printed. I haven't tested with narrower labels.

    ## Python code

    The code here currently is what I had at the point I got this working - it's a bit hacked together. It prints images, but the status messages aren't complete and the main script needs tidying up. The printer sometimes goes to an error state after printing (haven't figured out why yet), which can be cleared by pressing the power button once.
    The code here is what I had at the point I got this working - it's a bit hacked together. It prints images, but the status messages printed aren't complete and the main script needs some tidying up. The printer sometimes goes to an error state after printing (haven't figured out why yet), which can be cleared by pressing the power button once.

    This needs a few modules installed to run:

  4. @stecman stecman revised this gist Dec 5, 2017. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions labelmaker_encode.py
    Original file line number Diff line number Diff line change
    @@ -11,10 +11,11 @@ def as_unsigned_char(byte):
    return unsigned_char.unpack(byte)[0]

    def encode_raster_transfer(data):
    """ Encode a raw 1 bit per pixel image for transfer to in """
    """ Encode 1 bit per pixel image data for transfer over serial to the printer """
    buf = bytearray()

    # The official app from Brother sends data in 16 byte chunks
    # Send in chunks of 1 line (128px @ 1bpp = 16 bytes)
    # This mirrors the official app from Brother. Other values haven't been tested.
    chunk_size = 16

    for i in xrange(0, len(data), chunk_size):
  5. @stecman stecman revised this gist Dec 5, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions _readme.md
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@ Intitially I had a quick peek at the Android APK to see if there was any useful

    Next I used the bluetooth sniffing capability of stock Android to capture a few label prints using the official app. Inspecting these packet captures in Wireshark, it was apparent that all of the communication was done using bluetooth's serial port profile (SPP). The printer also shows up as "Fujitsu" in the packet captures, and since Brother has a lot of label maker products I figured there was a good chance they were using some existing hardware and firmware with a bluetooth to serial adapter bolted on.

    After a little Googling, this hunch paid off - a whole bunch of developer documentation for Brother's higher-end label maker product that matched up with the bytes being sent over the bluetooth to serial connection. Mainly:
    After a little Googling, this hunch paid off - a bunch of developer documentation for some of Brother's higher-end label maker products that matched up with the bytes being sent over the bluetooth to serial connection. Mainly:

    - [PT-9500PC Command Reference: CBP-RASTER Mode (PTCBP Mode) Volume](http://etc.nkadesign.com/uploads/Printers/95CRRASE.pdf)

    @@ -58,7 +58,7 @@ From a few Brother developer docs, the packet captures could be broken down as:

    Image data is sent to the printer as a 1-bit-per-pixel bitmap. The Brother app sends a 128 pixel wide image (regardless of tape width), oriented as lines across the print head. For a horizontal label (printing along the length of tape), the input image needs to be rotated by 90 degrees.

    Once in the correct orientation, image data needs to be mirrored horizontally. Presumably this is due to the orientation of the print head and the printer not having any built in compensation for this.
    Once in the correct orientation, image data needs to be mirrored horizontally (with the settings above at least). It looks like the command `1B 69 4D` can be used to enable mirroring by the printer, but I haven't tested this.

    The outer edges of a 12mm label do not appear to be printable (print head too narrow?). The outer 10-20 pixels on each side is not printed. I haven't tested with narrower labels.

  6. @stecman stecman revised this gist Dec 4, 2017. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions labelmaker_encode.py
    Original file line number Diff line number Diff line change
    @@ -69,9 +69,7 @@ def read_png(path):
    """ Read a (monochrome) PNG image and convert to 1bpp raw data
    This should work with any 8 bit PNG. To ensure compatibility, the image can
    be processed with Imagemagick first:
    convert image.
    be processed with Imagemagick first using the -monochrome flag.
    """

    buf = bytearray()
  7. @stecman stecman revised this gist Dec 4, 2017. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions _readme.md
    Original file line number Diff line number Diff line change
    @@ -64,17 +64,17 @@ The outer edges of a 12mm label do not appear to be printable (print head too na

    ## Python code

    The state here is what I hacked together. It prints images, but the status messages aren't complete and the main script needs tidying up.
    The code here currently is what I had at the point I got this working - it's a bit hacked together. It prints images, but the status messages aren't complete and the main script needs tidying up. The printer sometimes goes to an error state after printing (haven't figured out why yet), which can be cleared by pressing the power button once.

    It needs a few modules to run:
    This needs a few modules installed to run:

    ```
    pyserial
    pypng
    packbits
    ```

    Then can be used as:
    Then it can be used as:

    ```sh
    # Existing image formated to spec above
  8. @stecman stecman created this gist Dec 4, 2017.
    103 changes: 103 additions & 0 deletions _readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,103 @@
    # Controlling the Brother P-Touch Cube from a computer

    The Brother PTP300BT label maker is intended to be controlled using the official Brother P-Touch Design & Print iOS/Android app. The app has arbitrary limits on what you can print (1 text object and up to 3 preset icons), so I though it would be a fun challenge to reverse engineer the protocol to print whatever I wanted.

    ## Process

    Intitially I had a quick peek at the Android APK to see if there was any useful information inside. The code that handles the communication with the printer in *Print&Design* turned out to be a native library (at least on Android), but the app clearly prepares a bitmap image and passes it to this native library for printing. Bitmaps are something we can work with.

    Next I used the bluetooth sniffing capability of stock Android to capture a few label prints using the official app. Inspecting these packet captures in Wireshark, it was apparent that all of the communication was done using bluetooth's serial port profile (SPP). The printer also shows up as "Fujitsu" in the packet captures, and since Brother has a lot of label maker products I figured there was a good chance they were using some existing hardware and firmware with a bluetooth to serial adapter bolted on.

    After a little Googling, this hunch paid off - a whole bunch of developer documentation for Brother's higher-end label maker product that matched up with the bytes being sent over the bluetooth to serial connection. Mainly:

    - [PT-9500PC Command Reference: CBP-RASTER Mode (PTCBP Mode) Volume](http://etc.nkadesign.com/uploads/Printers/95CRRASE.pdf)

    At first I found a manual for ESC/P, which has the same command format, initialisation command and 32 byte status format, but the P-Touch Cube doesn't appear to support this (based on trying the ESC/P commands on the device).

    ## Serial protocol

    From a few Brother developer docs, the packet captures could be broken down as:

    ```
    // 64 bytes of 0x0 (to clear print buffer?)
    ...
    // Initialize/reset settings registers
    1B 40
    // Enter raster mode (aka. PTCBP)
    1B 69 61 01
    // Set media and quality (most importantly, the number of 128px lines to expect)
    // Found docs for this last by searching for the first three bytes (command)
    // See http://www.undocprint.org/formats/page_description_languages/brother_p-touch
    1B 69 7A C4 01 0C 00 08 01 00 00 00 00
    // Set expanded mode bits (set chain off with a mask)
    1B 69 4B 08
    // Set mode bits (mirror print: no, auto tape cut: no)
    1B 69 4D 00
    // Set margin (feed) size
    1B 69 64 1C 00
    // Set compression mode: TIFF (packbits)
    4D 02
    // Transfer n1 + n2*256 bytes of raster data
    // The official app transfers one line of data at a time (16 bytes = 128 pixels)
    47 n1 n2 [data]
    ...
    // Print and feed
    1A
    ```

    ## Image data

    Image data is sent to the printer as a 1-bit-per-pixel bitmap. The Brother app sends a 128 pixel wide image (regardless of tape width), oriented as lines across the print head. For a horizontal label (printing along the length of tape), the input image needs to be rotated by 90 degrees.

    Once in the correct orientation, image data needs to be mirrored horizontally. Presumably this is due to the orientation of the print head and the printer not having any built in compensation for this.

    The outer edges of a 12mm label do not appear to be printable (print head too narrow?). The outer 10-20 pixels on each side is not printed. I haven't tested with narrower labels.

    ## Python code

    The state here is what I hacked together. It prints images, but the status messages aren't complete and the main script needs tidying up.

    It needs a few modules to run:

    ```
    pyserial
    pypng
    packbits
    ```

    Then can be used as:

    ```sh
    # Existing image formated to spec above
    ./labelmaker.py monochrome-128px-wide-image.png

    # Using imagemagick to get a usable input image from any horizontal oriented image
    # -resize 128x can be used instead of -crop 128x as needed
    # -rotate 90 can be removed if the image is portrait already
    convert inputimage.png -monochrome -gravity center -crop 128x -rotate 90 -flop out.png
    ```

    I was working on Linux, so the serial device is currently hard-coded as `/dev/rfcomm0`. On OSX, a `/dev/tty.*` device will show up once the printer is paired.

    To pair the printer with my Linux machine, I used:

    ```sh
    # Pair device
    $ bluetoothctl
    > scan on
    ... (turn printer on and wait for it to show up: PT-P300BT8894)
    > pair [address]

    # Setup serial port
    $ sudo modprobe rfcomm
    $ sudo rfcomm bind rfcomm0 [address of printer]
    ```
    139 changes: 139 additions & 0 deletions labelmaker.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,139 @@
    #!/usr/bin/env python

    from labelmaker_encode import encode_raster_transfer, read_png, unsigned_char

    import binascii
    import packbits
    import serial
    import sys
    import time

    STATUS_OFFSET_BATTERY = 6
    STATUS_OFFSET_EXTENDED_ERROR = 7
    STATUS_OFFSET_ERROR_INFO_1 = 8
    STATUS_OFFSET_ERROR_INFO_2 = 9
    STATUS_OFFSET_STATUS_TYPE = 18
    STATUS_OFFSET_PHASE_TYPE = 19
    STATUS_OFFSET_NOTIFICATION = 22

    STATUS_TYPE = [
    "Reply to status request",
    "Printing completed",
    "Error occured",
    "IF mode finished",
    "Power off",
    "Notification",
    "Phase change",
    ]

    STATUS_BATTERY = [
    "Full",
    "Half",
    "Low",
    "Change batteries",
    "AC adapter in use"
    ]

    def print_status(raw):
    if len(raw) != 32:
    print "Error: status must be 32 bytes. Got " + len(raw)
    return

    if raw[STATUS_OFFSET_STATUS_TYPE] < len(STATUS_TYPE):
    print "Status: " + STATUS_TYPE[raw[STATUS_OFFSET_STATUS_TYPE]]
    else:
    print "Status: 0x" + binascii.hexlify(raw[STATUS_OFFSET_STATUS_TYPE])

    if raw[STATUS_OFFSET_BATTERY] < len(STATUS_BATTERY):
    print "Battery: " + STATUS_BATTERY[raw[STATUS_OFFSET_BATTERY]]
    else:
    print "Battery: 0x" + binascii.hexlify(raw[STATUS_OFFSET_BATTERY])

    print "Error info 1: 0x" + binascii.hexlify(raw[STATUS_OFFSET_ERROR_INFO_1])
    print "Error info 2: 0x" + binascii.hexlify(raw[STATUS_OFFSET_ERROR_INFO_2])
    print "Extended error: 0x" + binascii.hexlify(raw[STATUS_OFFSET_EXTENDED_ERROR])
    print


    # Check for input image
    if len(sys.argv) < 2:
    print "Usage: %s <path-to-image>" % sys.argv[0]
    sys.exit(1)

    # Get serial device
    ser = serial.Serial(
    '/dev/rfcomm0',
    baudrate=9600,
    stopbits=serial.STOPBITS_ONE,
    parity=serial.PARITY_NONE,
    bytesize=8,
    dsrdtr=True
    )

    print(ser.name)

    # Read input image into memory
    data = read_png(sys.argv[1])

    # Enter raster graphics (PTCBP) mode
    ser.write(b"\x1b\x69\x61\x01")

    # Initialize
    ser.write(b"\x1b\x40")

    # Dump status
    ser.write(b"\x1b\x69\x53")
    print_status( ser.read(size=32) )

    # Flush print buffer
    for i in range(64):
    ser.write(b"\x00")

    # Initialize
    ser.write(b"\x1b\x40")

    # Enter raster graphics (PTCBP) mode
    ser.write(b"\x1b\x69\x61\x01")

    # Found docs on http://www.undocprint.org/formats/page_description_languages/brother_p-touch
    ser.write(b"\x1B\x69\x7A") # Set media & quality
    ser.write(b"\xC4\x01") # print quality, continuous roll
    ser.write(b"\x0C") # Tape width in mm
    ser.write(b"\x00") # Label height in mm (0 for continuous roll)

    # Number of raster lines in image data
    raster_lines = len(data) / 16
    print raster_lines, raster_lines % 256, int(raster_lines / 256)
    ser.write( unsigned_char.pack( raster_lines % 256 ) )
    ser.write( unsigned_char.pack( int(raster_lines / 256) ) )

    # Unused data bytes in the "set media and quality" command
    ser.write(b"\x00\x00\x00\x00")

    # Set print chaining off (0x8) or on (0x0)
    ser.write(b"\x1B\x69\x4B\x08")

    # Set no mirror, no auto tape cut
    ser.write(b"\x1B\x69\x4D\x00")

    # Set margin amount (feed amount)
    ser.write(b"\x1B\x69\x64\x00\x00")

    # Set compression mode: TIFF
    ser.write(b"\x4D\x02")

    # Send image data
    print("Sending image data")
    ser.write( encode_raster_transfer(data) )
    print "Done"

    # Print and feed
    ser.write(b"\x1A")

    # Dump status that the printer returns
    print_status( ser.read(size=32) )

    # Initialize
    ser.write(b"\x1b\x40")

    ser.close()
    100 changes: 100 additions & 0 deletions labelmaker_encode.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,100 @@
    import packbits
    import png
    import struct

    # "Raster graphics transfer" serial command
    TRANSFER_COMMAND = b"\x47"

    unsigned_char = struct.Struct('B');
    def as_unsigned_char(byte):
    """ Interpret a byte as an unsigned int """
    return unsigned_char.unpack(byte)[0]

    def encode_raster_transfer(data):
    """ Encode a raw 1 bit per pixel image for transfer to in """
    buf = bytearray()

    # The official app from Brother sends data in 16 byte chunks
    chunk_size = 16

    for i in xrange(0, len(data), chunk_size):
    chunk = data[i : i + chunk_size]

    # Encode as tiff
    packed_chunk = packbits.encode(chunk)

    # Write header
    buf.append(TRANSFER_COMMAND)

    # Write number of bytes to transfer (n1 + n2*256)
    length = len(packed_chunk)
    buf.append(unsigned_char.pack( int(length % 256) ))
    buf.append(unsigned_char.pack( int(length / 256) ))

    # Write data
    buf.extend(packed_chunk)

    return buf

    def decode_raster_transfer(data):
    """ Read data encoded as T encoded as TIFF with transfer headers """

    buf = bytearray()
    i = 0

    while i < len(data):
    if data[i] == TRANSFER_COMMAND:
    # Decode number of bytes to transfer
    n1 = as_unsigned_char(data[i+1])
    n2 = as_unsigned_char(data[i+2])
    num_bytes = n1 + n2*256

    # Copy contents of transfer to output buffer
    transferedData = data[i + 3 : i + 3 + num_bytes]
    buf.extend(transferedData)

    # Confirm
    if len(transferedData) != num_bytes:
    raise Exception("Failed to read %d bytes at index %s: end of input data reached." % (num_bytes, i))

    # Shift to the next position after these command and data bytes
    i = i + 3 + num_bytes

    else:
    raise Exception("Unexpected byte %s" % data[i])

    return buf

    def read_png(path):
    """ Read a (monochrome) PNG image and convert to 1bpp raw data
    This should work with any 8 bit PNG. To ensure compatibility, the image can
    be processed with Imagemagick first:
    convert image.
    """

    buf = bytearray()

    # State for bit packing
    bit_cursor = 8
    byte = 0

    # Read the PNG image
    reader = png.Reader(filename=path)
    width, height, rows, metadata = reader.asRGB()

    # Loop over image and pack into 1bpp buffer
    for row in rows:
    for pixel in xrange(0, len(row), 3):
    bit_cursor -= 1

    if row[pixel] == 0:
    byte |= (1 << bit_cursor)

    if bit_cursor == 0:
    buf.append(unsigned_char.pack(byte))
    byte = 0
    bit_cursor = 8

    return buf