#*************************************************************************
# This is a Python library for the Adafruit Thermal Printer.
# Pick one up at --> http://www.adafruit.com/products/597
# These printers use TTL serial to communicate, 2 pins are required.
# IMPORTANT: On 3.3V systems (e.g. Raspberry Pi), use a 10K resistor on
# the RX pin (TX on the printer, green wire), or simply leave unconnected.
#
# Adafruit invests time and resources providing this open source code.
# Please support Adafruit and open-source hardware by purchasing products
# from Adafruit!
#
# Written by Limor Fried/Ladyada for Adafruit Industries.
# Python port by Phil Burgess for Adafruit Industries.
# MIT license, all text above must be included in any redistribution.
#*************************************************************************

# This is pretty much a 1:1 direct Python port of the Adafruit_Thermal
# library for Arduino.  All methods use the same naming conventions as the
# Arduino library, with only slight changes in parameter behavior where
# needed.  This should simplify porting existing Adafruit_Thermal-based
# printer projects to Raspberry Pi, BeagleBone, etc.  See printertest.py
# for an example.
#
# One significant change is the addition of the printImage() function,
# which ties this to the Python Imaging Library and opens the door to a
# lot of cool graphical stuff!
#
# TO DO:
# - Might use standard ConfigParser library to put thermal calibration
#   settings in a global configuration file (rather than in the library).
# - Make this use proper Python library installation procedure.
# - Trap errors properly.  Some stuff just falls through right now.
# - Add docstrings throughout!

from serial import Serial
import time
import sys
import math

class Adafruit_Thermal(Serial):

	resumeTime      =   0.0
	byteTime        =   0.0
	dotPrintTime    =   0.0
	dotFeedTime     =   0.0
	prevByte        =  '\n'
	column          =     0
	maxColumn       =    32
	charHeight      =    24
	lineSpacing     =     8
	barcodeHeight   =    50
	printMode       =     0
	defaultHeatTime =   200
	firmwareVersion =   268
	writeToStdout   = False

	def __init__(self, *args, **kwargs):
		# NEW BEHAVIOR: if no parameters given, output is written
		# to stdout, to be piped through 'lp -o raw' (old behavior
		# was to use default port & baud rate).
		baudrate = 19200
		if len(args) == 0:
			self.writeToStdout = True
		if len(args) == 1:
			# If only port is passed, use default baud rate.
			args = [ args[0], baudrate ]
		elif len(args) == 2:
			# If both passed, use those values.
			baudrate = args[1]

		# Firmware is assumed version 2.68.  Can override this
		# with the 'firmware=X' argument, where X is the major
		# version number * 100 + the minor version number (e.g.
		# pass "firmware=264" for version 2.64.
		self.firmwareVersion = kwargs.get('firmware', 268)

		if self.writeToStdout is False:
			# Calculate time to issue one byte to the printer.
			# 11 bits (not 8) to accommodate idle, start and
			# stop bits.  Idle time might be unnecessary, but
			# erring on side of caution here.
			self.byteTime = 11.0 / float(baudrate)

			Serial.__init__(self, *args, **kwargs)

			# Remainder of this method was previously in begin()

			# The printer can't start receiving data immediately
			# upon power up -- it needs a moment to cold boot
			# and initialize.  Allow at least 1/2 sec of uptime
			# before printer can receive data.
			self.timeoutSet(0.5)

			self.wake()
			self.reset()

			# Description of print settings from p. 23 of manual:
			# ESC 7 n1 n2 n3 Setting Control Parameter Command
			# Decimal: 27 55 n1 n2 n3
			# max heating dots, heating time, heating interval
			# n1 = 0-255 Max heat dots, Unit (8dots), Default: 7 (64 dots)
			# n2 = 3-255 Heating time, Unit (10us), Default: 80 (800us)
			# n3 = 0-255 Heating interval, Unit (10us), Default: 2 (20us)
			# The more max heating dots, the more peak current
			# will cost when printing, the faster printing speed.
			# The max heating dots is 8*(n1+1).  The more heating
			# time, the more density, but the slower printing
			# speed.  If heating time is too short, blank page
			# may occur.  The more heating interval, the more
			# clear, but the slower printing speed.

			heatTime = kwargs.get('heattime', self.defaultHeatTime)
			self.writeBytes(
			  27,       # Esc
			  55,       # 7 (print settings)
			  11,       # Heat dots
			  heatTime, # Lib default
			  40)       # Heat interval

			# Description of print density from p. 23 of manual:
			# DC2 # n Set printing density
			# Decimal: 18 35 n
			# D4..D0 of n is used to set the printing density.
			# Density is 50% + 5% * n(D4-D0) printing density.
			# D7..D5 of n is used to set the printing break time.
			# Break time is n(D7-D5)*250us.
			# (Unsure of default values -- not documented)

			printDensity   = 10 # 100%
			printBreakTime =  2 # 500 uS

			self.writeBytes(
			  18, # DC2
			  35, # Print density
			  (printBreakTime << 5) | printDensity)
			self.dotPrintTime = 0.03
			self.dotFeedTime  = 0.0021
		else:
			self.reset() # Inits some vars

	# Because there's no flow control between the printer and computer,
	# special care must be taken to avoid overrunning the printer's
	# buffer.  Serial output is throttled based on serial speed as well
	# as an estimate of the device's print and feed rates (relatively
	# slow, being bound to moving parts and physical reality).  After
	# an operation is issued to the printer (e.g. bitmap print), a
	# timeout is set before which any other printer operations will be
	# suspended.  This is generally more efficient than using a delay
	# in that it allows the calling code to continue with other duties
	# (e.g. receiving or decoding an image) while the printer
	# physically completes the task.

	# Sets estimated completion time for a just-issued task.
	def timeoutSet(self, x):
		self.resumeTime = time.time() + x

	# Waits (if necessary) for the prior task to complete.
	def timeoutWait(self):
		if self.writeToStdout is False:
			while (time.time() - self.resumeTime) < 0: pass

	# Printer performance may vary based on the power supply voltage,
	# thickness of paper, phase of the moon and other seemingly random
	# variables.  This method sets the times (in microseconds) for the
	# paper to advance one vertical 'dot' when printing and feeding.
	# For example, in the default initialized state, normal-sized text
	# is 24 dots tall and the line spacing is 32 dots, so the time for
	# one line to be issued is approximately 24 * print time + 8 * feed
	# time.  The default print and feed times are based on a random
	# test unit, but as stated above your reality may be influenced by
	# many factors.  This lets you tweak the timing to avoid excessive
	# delays and/or overrunning the printer buffer.
	def setTimes(self, p, f):
		# Units are in microseconds for
		# compatibility with Arduino library
		self.dotPrintTime = p / 1000000.0
		self.dotFeedTime  = f / 1000000.0

	# 'Raw' byte-writing method
	def writeBytes(self, *args):
		if self.writeToStdout:
			for arg in args:
				sys.stdout.write(bytes([arg]))
		else:
			for arg in args:
				self.timeoutWait()
				self.timeoutSet(len(args) * self.byteTime)
				super(Adafruit_Thermal, self).write(bytes([arg]))

	# Override write() method to keep track of paper feed.
	def write(self, *data):
		for i in range(len(data)):
			c = data[i]
			if self.writeToStdout:
				sys.stdout.write(c)
				continue
			if c != 0x13:
				self.timeoutWait()
				super(Adafruit_Thermal, self).write(c)
				d = self.byteTime
				if ((c == '\n') or
				    (self.column == self.maxColumn)):
					# Newline or wrap
					if self.prevByte == '\n':
						# Feed line (blank)
						d += ((self.charHeight +
						       self.lineSpacing) *
						      self.dotFeedTime)
					else:
						# Text line
						d += ((self.charHeight *
						       self.dotPrintTime) +
						      (self.lineSpacing *
						       self.dotFeedTime))
						self.column = 0
						# Treat wrap as newline
						# on next pass
						c = '\n'
				else:
					self.column += 1
				self.timeoutSet(d)
				self.prevByte = c

	# The bulk of this method was moved into __init__,
	# but this is left here for compatibility with older
	# code that might get ported directly from Arduino.
	def begin(self, heatTime=defaultHeatTime):
		self.writeBytes(
		  27,       # Esc
		  55,       # 7 (print settings)
		  11,       # Heat dots
		  heatTime,
		  40)       # Heat interval

	def reset(self):
		self.writeBytes(27, 64) # Esc @ = init command
		self.prevByte      = '\n' # Treat as if prior line is blank
		self.column        =  0
		self.maxColumn     = 32
		self.charHeight    = 24
		self.lineSpacing   =  6
		self.barcodeHeight = 50
		if self.firmwareVersion >= 264:
			# Configure tab stops on recent printers
			self.writeBytes(27, 68)         # Set tab stops
			self.writeBytes( 4,  8, 12, 16) # every 4 columns,
			self.writeBytes(20, 24, 28,  0) # 0 is end-of-list.

	# Reset text formatting parameters.
	def setDefault(self):
		self.online()
		self.justify('L')
		self.inverseOff()
		self.doubleHeightOff()
		self.setLineHeight(30)
		self.boldOff()
		self.underlineOff()
		self.setBarcodeHeight(50)
		self.setSize('s')
		self.setCharset()
		self.setCodePage()

	def test(self):
		self.write("Hello world!".encode('cp437', 'ignore'))
		self.feed(2)

	def testPage(self):
		self.writeBytes(18, 84)
		self.timeoutSet(
		  self.dotPrintTime * 24 * 26 +
		  self.dotFeedTime * (6 * 26 + 30))

	def setBarcodeHeight(self, val=50):
		if val < 1: val = 1
		self.barcodeHeight = val
		self.writeBytes(29, 104, val)

	UPC_A   =  0
	UPC_E   =  1
	EAN13   =  2
	EAN8    =  3
	CODE39  =  4
	I25     =  5
	CODEBAR =  6
	CODE93  =  7
	CODE128 =  8
	CODE11  =  9
	MSI     = 10
	ITF     = 11
	CODABAR = 12

	def printBarcode(self, text, type):

		newDict = { # UPC codes & values for firmwareVersion >= 264
			self.UPC_A   : 65,
			self.UPC_E   : 66,
			self.EAN13   : 67,
			self.EAN8    : 68,
			self.CODE39  : 69,
			self.ITF     : 70,
			self.CODABAR : 71,
			self.CODE93  : 72,
			self.CODE128 : 73,
			self.I25     : -1, # NOT IN NEW FIRMWARE
			self.CODEBAR : -1,
			self.CODE11  : -1,
			self.MSI     : -1
		}
		oldDict = { # UPC codes & values for firmwareVersion < 264
			self.UPC_A   :  0,
			self.UPC_E   :  1,
			self.EAN13   :  2,
			self.EAN8    :  3,
			self.CODE39  :  4,
			self.I25     :  5,
			self.CODEBAR :  6,
			self.CODE93  :  7,
			self.CODE128 :  8,
			self.CODE11  :  9,
			self.MSI     : 10,
			self.ITF     : -1, # NOT IN OLD FIRMWARE
			self.CODABAR : -1
		}

		if self.firmwareVersion >= 264:
			n = newDict[type]
		else:
			n = oldDict[type]
		if n == -1: return
		self.feed(1) # Recent firmware requires this?
		self.writeBytes(
		  29,  72, 2, # Print label below barcode
		  29, 119, 3, # Barcode width
		  29, 107, n) # Barcode type
		self.timeoutWait()
		self.timeoutSet((self.barcodeHeight + 40) * self.dotPrintTime)
		# Print string
		if self.firmwareVersion >= 264:
			# Recent firmware: write length byte + string sans NUL
			n = len(text)
			if n > 255: n = 255
			if self.writeToStdout:
				sys.stdout.write((chr(n)).encode('cp437', 'ignore'))
				for i in range(n):
					sys.stdout.write(text[i].encode('utf-8', 'ignore'))
			else:
				super(Adafruit_Thermal, self).write((chr(n)).encode('utf-8', 'ignore'))
				for i in range(n):
					super(Adafruit_Thermal,
					  self).write(text[i].encode('utf-8', 'ignore'))
		else:
			# Older firmware: write string + NUL
			if self.writeToStdout:
				sys.stdout.write(text.encode('utf-8', 'ignore'))
			else:
				super(Adafruit_Thermal, self).write(text.encode('utf-8', 'ignore'))
		self.prevByte = '\n'

	# === Character commands ===

	INVERSE_MASK       = (1 << 1) # Not in 2.6.8 firmware (see inverseOn())
	UPDOWN_MASK        = (1 << 2)
	BOLD_MASK          = (1 << 3)
	DOUBLE_HEIGHT_MASK = (1 << 4)
	DOUBLE_WIDTH_MASK  = (1 << 5)
	STRIKE_MASK        = (1 << 6)

	def setPrintMode(self, mask):
		self.printMode |= mask
		self.writePrintMode()
		if self.printMode & self.DOUBLE_HEIGHT_MASK:
			self.charHeight = 48
		else:
			self.charHeight = 24
		if self.printMode & self.DOUBLE_WIDTH_MASK:
			self.maxColumn  = 16
		else:
			self.maxColumn  = 32

	def unsetPrintMode(self, mask):
		self.printMode &= ~mask
		self.writePrintMode()
		if self.printMode & self.DOUBLE_HEIGHT_MASK:
			self.charHeight = 48
		else:
			self.charHeight = 24
		if self.printMode & self.DOUBLE_WIDTH_MASK:
			self.maxColumn  = 16
		else:
			self.maxColumn  = 32

	def writePrintMode(self):
		self.writeBytes(27, 33, self.printMode)

	def normal(self):
		self.printMode = 0
		self.writePrintMode()

	def inverseOn(self):
		if self.firmwareVersion >= 268:
			self.writeBytes(29, 66, 1)
		else:
			self.setPrintMode(self.INVERSE_MASK)

	def inverseOff(self):
		if self.firmwareVersion >= 268:
			self.writeBytes(29, 66, 0)
		else:
			self.unsetPrintMode(self.INVERSE_MASK)

	def upsideDownOn(self):
		self.setPrintMode(self.UPDOWN_MASK)

	def upsideDownOff(self):
		self.unsetPrintMode(self.UPDOWN_MASK)

	def doubleHeightOn(self):
		self.setPrintMode(self.DOUBLE_HEIGHT_MASK)

	def doubleHeightOff(self):
		self.unsetPrintMode(self.DOUBLE_HEIGHT_MASK)

	def doubleWidthOn(self):
		self.setPrintMode(self.DOUBLE_WIDTH_MASK)

	def doubleWidthOff(self):
		self.unsetPrintMode(self.DOUBLE_WIDTH_MASK)

	def strikeOn(self):
		self.setPrintMode(self.STRIKE_MASK)

	def strikeOff(self):
		self.unsetPrintMode(self.STRIKE_MASK)

	def boldOn(self):
		self.setPrintMode(self.BOLD_MASK)

	def boldOff(self):
		self.unsetPrintMode(self.BOLD_MASK)

	def justify(self, value):
		c = value.upper()
		if   c == 'C':
			pos = 1
		elif c == 'R':
			pos = 2
		else:
			pos = 0
		self.writeBytes(0x1B, 0x61, pos)

	# Feeds by the specified number of lines
	def feed(self, x=1):
		if self.firmwareVersion >= 264:
			self.writeBytes(27, 100, x)
			self.timeoutSet(self.dotFeedTime * self.charHeight)
			self.prevByte = '\n'
			self.column   =    0

		else:
			# datasheet claims sending bytes 27, 100, <x> works,
			# but it feeds much more than that.  So, manually:
			while x > 0:
				self.write('\n'.encode('cp437', 'ignore'))
				x -= 1

	# Feeds by the specified number of individual pixel rows
	def feedRows(self, rows):
		self.writeBytes(27, 74, rows)
		self.timeoutSet(rows * dotFeedTime)
		self.prevByte = '\n'
		self.column = 0

	def flush(self):
		self.writeBytes(12) # ASCII FF

	def setSize(self, value):
		c = value.upper()
		if c == 'L':   # Large: double width and height
			size            = 0x11
			self.charHeight = 48
			self.maxColumn  = 16
		elif c == 'M': # Medium: double height
			size            = 0x01
			self.charHeight = 48
			self.maxColumn  = 32
		else:          # Small: standard width and height
			size            = 0x00
			self.charHeight = 24
			self.maxColumn  = 32

		self.writeBytes(29, 33, size)
		prevByte = '\n' # Setting the size adds a linefeed

	# Underlines of different weights can be produced:
	# 0 - no underline
	# 1 - normal underline
	# 2 - thick underline
	def underlineOn(self, weight=1):
		if weight > 2: weight = 2
		self.writeBytes(27, 45, weight)

	def underlineOff(self):
		self.writeBytes(27, 45, 0)

	def printBitmap(self, w, h, bitmap, LaaT=False):
		rowBytes = math.floor((w + 7) / 8)  # Round up to next byte boundary
		if rowBytes >= 48:
			rowBytesClipped = 48  # 384 pixels max width
		else:
			rowBytesClipped = rowBytes

		# if LaaT (line-at-a-time) is True, print bitmaps
		# scanline-at-a-time (rather than in chunks).
		# This tends to make for much cleaner printing
		# (no feed gaps) on large images...but has the
		# opposite effect on small images that would fit
		# in a single 'chunk', so use carefully!
		if LaaT: maxChunkHeight = 1
		else:    maxChunkHeight = 255

		i = 0
		for rowStart in range(0, h, maxChunkHeight):
			chunkHeight = h - rowStart
			if chunkHeight > maxChunkHeight:
				chunkHeight = maxChunkHeight

			# Timeout wait happens here
			self.writeBytes(18, 42, chunkHeight, rowBytesClipped)

			for y in range(chunkHeight):
				for x in range(rowBytesClipped):
					if self.writeToStdout:
						sys.stdout.write(bytes([bitmap[i]]))
					else:
						super(Adafruit_Thermal,
						  self).write(bytes([bitmap[i]]))
					i += 1
				i += rowBytes - rowBytesClipped
			self.timeoutSet(chunkHeight * self.dotPrintTime)

		self.prevByte = '\n'

	# Print Image.  Requires Python Imaging Library.  This is
	# specific to the Python port and not present in the Arduino
	# library.  Image will be cropped to 384 pixels width if
	# necessary, and converted to 1-bit w/diffusion dithering.
	# For any other behavior (scale, B&W threshold, etc.), use
	# the Imaging Library to perform such operations before
	# passing the result to this function.
	def printImage(self, image_file, LaaT=False):
		from PIL import Image
		image = Image.open(image_file)
		if image.mode != '1':
			image = image.convert('1')

		width  = image.size[0]
		height = image.size[1]
		if width > 384:
			width = 384
		rowBytes = math.floor((width + 7) / 8)
		bitmap   = bytearray(rowBytes * height)
		pixels   = image.load()

		for y in range(height):
			n = y * rowBytes
			x = 0
			for b in range(rowBytes):
				sum = 0
				bit = 128
				while bit > 0:
					if x >= width: break
					if pixels[x, y] == 0:
						sum |= bit
					x    += 1
					bit >>= 1
				bitmap[n + b] = sum

		self.printBitmap(width, height, bitmap, LaaT)

	# Take the printer offline. Print commands sent after this
	# will be ignored until 'online' is called.
	def offline(self):
		self.writeBytes(27, 61, 0)

	# Take the printer online. Subsequent print commands will be obeyed.
	def online(self):
		self.writeBytes(27, 61, 1)

	# Put the printer into a low-energy state immediately.
	def sleep(self):
		self.sleepAfter(1) # Can't be 0, that means "don't sleep"

	# Put the printer into a low-energy state after
	# the given number of seconds.
	def sleepAfter(self, seconds):
		if self.firmwareVersion >= 264:
			self.writeBytes(27, 56, seconds & 0xFF, seconds >> 8)
		else:
			self.writeBytes(27, 56, seconds)

	def wake(self):
		self.timeoutSet(0)
		self.writeBytes(255)
		if self.firmwareVersion >= 264:
			time.sleep(0.05)            # 50 ms
			self.writeBytes(27, 118, 0) # Sleep off (important!)
		else:
			for i in range(10):
				self.writeBytes(27)
				self.timeoutSet(0.1)

	# Empty method, included for compatibility
	# with existing code ported from Arduino.
	def listen(self):
		pass

	# Check the status of the paper using the printers self reporting
	# ability. Doesn't match the datasheet...
	# Returns True for paper, False for no paper.
	def hasPaper(self):
		if self.firmwareVersion >= 264:
			self.writeBytes(27, 118, 0)
		else:
			self.writeBytes(29, 114, 0)
		# Bit 2 of response seems to be paper status
		stat = ord(self.read(1)) & 0b00000100
		# If set, we have paper; if clear, no paper
		return stat == 0

	def setLineHeight(self, val=32):
		if val < 24: val = 24
		self.lineSpacing = val - 24

		# The printer doesn't take into account the current text
		# height when setting line height, making this more akin
		# to inter-line spacing.  Default line spacing is 32
		# (char height of 24, line spacing of 8).
		self.writeBytes(27, 51, val)

	CHARSET_USA          =  0
	CHARSET_FRANCE       =  1
	CHARSET_GERMANY      =  2
	CHARSET_UK           =  3
	CHARSET_DENMARK1     =  4
	CHARSET_SWEDEN       =  5
	CHARSET_ITALY        =  6
	CHARSET_SPAIN1       =  7
	CHARSET_JAPAN        =  8
	CHARSET_NORWAY       =  9
	CHARSET_DENMARK2     = 10
	CHARSET_SPAIN2       = 11
	CHARSET_LATINAMERICA = 12
	CHARSET_KOREA        = 13
	CHARSET_SLOVENIA     = 14
	CHARSET_CROATIA      = 14
	CHARSET_CHINA        = 15

	# Alters some chars in ASCII 0x23-0x7E range; see datasheet
	def setCharset(self, val=0):
		if val > 15: val = 15
		self.writeBytes(27, 82, val)

	CODEPAGE_CP437       =  0 # USA, Standard Europe
	CODEPAGE_KATAKANA    =  1
	CODEPAGE_CP850       =  2 # Multilingual
	CODEPAGE_CP860       =  3 # Portuguese
	CODEPAGE_CP863       =  4 # Canadian-French
	CODEPAGE_CP865       =  5 # Nordic
	CODEPAGE_WCP1251     =  6 # Cyrillic
	CODEPAGE_CP866       =  7 # Cyrillic #2
	CODEPAGE_MIK         =  8 # Cyrillic/Bulgarian
	CODEPAGE_CP755       =  9 # East Europe, Latvian 2
	CODEPAGE_IRAN        = 10
	CODEPAGE_CP862       = 15 # Hebrew
	CODEPAGE_WCP1252     = 16 # Latin 1
	CODEPAGE_WCP1253     = 17 # Greek
	CODEPAGE_CP852       = 18 # Latin 2
	CODEPAGE_CP858       = 19 # Multilingual Latin 1 + Euro
	CODEPAGE_IRAN2       = 20
	CODEPAGE_LATVIAN     = 21
	CODEPAGE_CP864       = 22 # Arabic
	CODEPAGE_ISO_8859_1  = 23 # West Europe
	CODEPAGE_CP737       = 24 # Greek
	CODEPAGE_WCP1257     = 25 # Baltic
	CODEPAGE_THAI        = 26
	CODEPAGE_CP720       = 27 # Arabic
	CODEPAGE_CP855       = 28
	CODEPAGE_CP857       = 29 # Turkish
	CODEPAGE_WCP1250     = 30 # Central Europe
	CODEPAGE_CP775       = 31
	CODEPAGE_WCP1254     = 32 # Turkish
	CODEPAGE_WCP1255     = 33 # Hebrew
	CODEPAGE_WCP1256     = 34 # Arabic
	CODEPAGE_WCP1258     = 35 # Vietnam
	CODEPAGE_ISO_8859_2  = 36 # Latin 2
	CODEPAGE_ISO_8859_3  = 37 # Latin 3
	CODEPAGE_ISO_8859_4  = 38 # Baltic
	CODEPAGE_ISO_8859_5  = 39 # Cyrillic
	CODEPAGE_ISO_8859_6  = 40 # Arabic
	CODEPAGE_ISO_8859_7  = 41 # Greek
	CODEPAGE_ISO_8859_8  = 42 # Hebrew
	CODEPAGE_ISO_8859_9  = 43 # Turkish
	CODEPAGE_ISO_8859_15 = 44 # Latin 3
	CODEPAGE_THAI2       = 45
	CODEPAGE_CP856       = 46
	CODEPAGE_CP874       = 47

	# Selects alt symbols for 'upper' ASCII values 0x80-0xFF
	def setCodePage(self, val=0):
		if val > 47: val = 47
		self.writeBytes(27, 116, val)

	# Copied from Arduino lib for parity; may not work on all printers
	def tab(self):
		self.writeBytes(9)
		self.column = (self.column + 4) & 0xFC

	# Copied from Arduino lib for parity; may not work on all printers
	def setCharSpacing(self, spacing):
		self.writeBytes(27, 32, spacing)

	# Overloading print() in Python pre-3.0 is dirty pool,
	# but these are here to provide more direct compatibility
	# with existing code written for the Arduino library.
	def print(self, *args, **kwargs):
		for arg in args:
			self.write((str(arg)).encode('cp437', 'ignore'))

	# For Arduino code compatibility again
	def println(self, *args, **kwargs):
		for arg in args:
			self.write((str(arg)).encode('cp437', 'ignore'))
		self.write('\n'.encode('cp437', 'ignore'))

