-
-
Save featherbear/621493bb08ba402321d6df8d6b5b6bd6 to your computer and use it in GitHub Desktop.
Revisions
-
stecman revised this gist
Dec 21, 2017 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 30 pixels of each side (length-wise) are not printed. I haven't tested with narrower labels. ## Python code -
stecman revised this gist
Dec 10, 2017 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # 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. -
stecman revised this gist
Dec 10, 2017 . 1 changed file with 12 additions and 10 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 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, 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 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/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 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 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 (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 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 of each side (length-wise) is not printed. I haven't tested with narrower labels. ## Python code 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: -
stecman revised this gist
Dec 5, 2017 . 1 changed file with 3 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 1 bit per pixel image data for transfer over serial to the printer """ buf = bytearray() # 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): -
stecman revised this gist
Dec 5, 2017 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 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 (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. -
stecman revised this gist
Dec 4, 2017 . 1 changed file with 1 addition and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 using the -monochrome flag. """ buf = bytearray() -
stecman revised this gist
Dec 4, 2017 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 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. This needs a few modules installed to run: ``` pyserial pypng packbits ``` Then it can be used as: ```sh # Existing image formated to spec above -
stecman created this gist
Dec 4, 2017 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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] ``` This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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() This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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