diff --git a/Adafruit_Thermal/Adafruit_Thermal.py b/Adafruit_Thermal/Adafruit_Thermal.py index 6857fc2..4c2cd3d 100644 --- a/Adafruit_Thermal/Adafruit_Thermal.py +++ b/Adafruit_Thermal/Adafruit_Thermal.py @@ -11,7 +11,6 @@ # # Written by Limor Fried/Ladyada for Adafruit Industries. # Python port by Phil Burgess for Adafruit Industries. -# See `AUTHORS` for a full list of contributors # MIT license, all text above must be included in any redistribution. #************************************************************************* @@ -29,707 +28,709 @@ # 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! # Python 2.X code using the library usu. needs to include the next line: from __future__ import print_function from serial import Serial import time +import sys class Adafruit_Thermal(Serial): - """Main class for interacting with a Adafruit Thermal Printer.""" - - resumeTime = 0.0 - byteTime = 0.0 - dotPrintTime = 0.033 - dotFeedTime = 0.0025 - prevByte = '\n' - column = 0 - maxColumn = 32 - charHeight = 24 - lineSpacing = 8 - barcodeHeight = 50 - printMode = 0 - defaultHeatTime = 60 - - def __init__(self, *args, **kwargs): - """Create a new connection using a given port and baud rate. - -The default port is `/dev/ttyAMA0` and the default baud rate is -19200. If you only pass in one of these parameters, then the -other parameter will use its default value.""" - - # If no parameters given, use default port & baud rate. - # If only port is passed, use default baud rate. - # If both passed, use those values. - baudrate = 19200 - if len(args) == 0: - args = [ "/dev/ttyAMA0", baudrate ] - elif len(args) == 1: - args = [ args[0], baudrate ] - else: - baudrate = args[1] - - # 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 page 23 of the manual: - # ESC 7 n1 n2 n3 Setting Control Parameter Command - # Decimal: 27 55 n1 n2 n3 - # Set "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) - 20, # Heat dots (20 = balance darkness w/no jams) - heatTime, # Lib default = 45 - 250) # Heat interval (500 uS = slower but darker) - - # Description of print density from page 23 of the 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 the default value for either -- not documented) - - printDensity = 14 # 120% (can go higher, but text gets fuzzy) - printBreakTime = 4 # 500 uS - - self.writeBytes( - 18, # DC2 - 35, # Print density - (printBreakTime << 5) | printDensity) - - self.dotPrintTime = 0.03 - self.dotFeedTime = 0.0021 - - # 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): - """Sets the estimates completion time for a just-issued task. - -This is used to ensure that the python script does not overrun -the printer's buffer. Using this method instead of a delay -allows the code calling this method to continue executing -instead of blocking on the print.""" - - self.resumeTime = time.time() + x - - # Waits (if necessary) for the prior task to complete. - def timeoutWait(self): - """Waits (if neccessary) for the prior task to complete.""" - - 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): - """Sets the time (in microseconds) for the paper to advance one -vertical 'dot' when printing and feeding. - -This rate varies based on the power supply voltage, thickness of -paper, phase of the moon, and other seemingly random variables. -As such, they may need tweaked for your individual printer.""" - - # 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): - """Writes 'raw' bytes.""" - - self.timeoutWait() - self.timeoutSet(len(args) * self.byteTime) - for arg in args: - super(Adafruit_Thermal, self).write(chr(arg)) - - # Override write() method to keep track of paper feed. - def write(self, *data): - """Overrides the `write()` method to be able to keep track of -paper feeding.""" - - for i in range(len(data)): - c = data[i] - 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): - """Legacy method for setting up a new printer connection. - -The bulk of this method moved into `__init__`, but this method -has been left here for compatibility with older code that might -get ported directly from Arduino.""" - - self.writeBytes( - 27, # Esc - 55, # 7 (print settings) - 20, # Heat dots (20 = balance darkness w/no jams) - heatTime, # Lib default = 45 - 250) # Heat interval (500 uS = slower but darker) - - def reset(self): - """Resets printer settings to defaults.""" - - self.prevByte = '\n' # Treat as if prior line is blank - self.column = 0 - self.maxColumn = 32 - self.charHeight = 24 - self.lineSpacing = 8 - self.barcodeHeight = 50 - self.writeBytes(27, 64) - - # Reset text formatting parameters. - def setDefault(self): - """Resets text formatting parameters to defaults.""" - - self.online() - self.justify('L') - self.inverseOff() - self.doubleHeightOff() - self.setLineHeight(32) - self.boldOff() - self.underlineOff() - self.setBarcodeHeight(50) - self.setSize('s') - - def test(self): - """Self test method for checking printer/library performance.""" - - self.writeBytes(18, 84) - self.timeoutSet( - self.dotPrintTime * 24 * 26 + - self.dotFeedTime * (8 * 26 + 32)) - - - UPC_A = 0 - UPC_E = 1 - EAN13 = 2 - EAN8 = 3 - CODE39 = 4 - I25 = 5 - CODEBAR = 6 - CODE93 = 7 - CODE128 = 8 - CODE11 = 9 - MSI = 10 - - def printBarcode(self, text, type): - """Prints a barcode of the given type along with a text label. - -Barcode types and their numeric values are: -UPC_A | 0 -UPC_E | 1 -EAN13 | 2 -EAN8 | 3 -CODE39 | 4 -I25 | 5 -CODEBAR | 6 -CODE93 | 7 -CODE128 | 8 -CODE11 | 9 -MSI | 10.""" - - self.writeBytes( - 29, 72, 2, # Print label below barcode - 29, 119, 3, # Barcode width - 29, 107, type) # Barcode type - # Print string - self.timeoutWait() - self.timeoutSet((self.barcodeHeight + 40) * self.dotPrintTime) - super(Adafruit_Thermal, self).write(text) - self.prevByte = '\n' - self.feed(2) - - def setBarcodeHeight(self, val=50): - """Sets the height of a barcode print. - -The default is 50 dots.""" - - if val < 1: - val = 1 - self.barcodeHeight = val - self.writeBytes(29, 104, val) - - - # === Character commands === - - INVERSE_MASK = (1 << 1) - 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): - """Sets the printing mode. - -Possible modes are: -INVERSE_MASK -UPDOWN_MASK -BOLD_MASK -DOUBLE_HEIGHT_MASK -DOUBLE_WIDTH_MASK -STRIKE_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): - """Disables only the given printing mode, while leaving any -other modes enabled.""" - - 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): - """Sends the configured printing mode to the printer.""" - - self.writeBytes(27, 33, self.printMode) - - def normal(self): - """Resets the print mode to the default 'nomal' mode.""" - - self.printMode = 0 - self.writePrintMode() - - def inverseOn(self): - """Helper method for enabling inverted printing.""" - - self.setPrintMode(self.INVERSE_MASK) - - def inverseOff(self): - """Helper method for disabling inverted printing.""" - - self.unsetPrintMode(self.INVERSE_MASK) - - def upsideDownOn(self): - """Helper method for enabling upsidedown printing.""" - - self.setPrintMode(self.UPDOWN_MASK) - - def upsideDownOff(self): - """Helper method for disabling upsidedown printing.""" - - self.unsetPrintMode(self.UPDOWN_MASK) - - def doubleHeightOn(self): - """Helper method for enabling double-height printing.""" - - self.setPrintMode(self.DOUBLE_HEIGHT_MASK) - - def doubleHeightOff(self): - """Helper method for disabling double-height printing.""" - - self.unsetPrintMode(self.DOUBLE_HEIGHT_MASK) - - def doubleWidthOn(self): - """Helper method for enabling double-width printing.""" - - self.setPrintMode(self.DOUBLE_WIDTH_MASK) - - def doubleWidthOff(self): - """Helper method for disabling double-width printing.""" - - self.unsetPrintMode(self.DOUBLE_WIDTH_MASK) - - def strikeOn(self): - """Helper method for enabling strike-through printing.""" - - self.setPrintMode(self.STRIKE_MASK) - - def strikeOff(self): - """Helper method for disabling strike-through printing.""" - - self.unsetPrintMode(self.STRIKE_MASK) - - def boldOn(self): - """Helper method for enabling bold printing.""" - - self.setPrintMode(self.BOLD_MASK) - - def boldOff(self): - """Helper method for disabling bold printing.""" - - self.unsetPrintMode(self.BOLD_MASK) - - # Underlines of different weights can be produced: - # 0 - no underline - # 1 - normal underline - # 2 - thick underline - def underlineOn(self, weight=1): - """Helper method for enabling underline printing.""" - - self.writeBytes(27, 45, weight) - - def underlineOff(self): - """Helper method for disabling underline printing.""" - - self.underlineOn(0) - - def justify(self, value='L'): - """Sets the text justication method. - -Accepted values are: -C: center justified -R: right justified -L: left justified - -The default is left justified.""" - - 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): - """Feeds the specified number of lines of paper.""" - - # The datasheet claims sending bytes 27, 100, will work, - # but it feeds much more than that. So it's done manually: - while x > 0: - self.write('\n') - x -= 1 - - # Feeds by the specified number of individual pixel rows - def feedRows(self, rows): - """Feeds the specified number of individual pixel rows.""" - - self.writeBytes(27, 74, rows) - self.timeoutSet(rows * dotFeedTime) - - def flush(self): - """Flushes the printer's buffer.""" - - self.writeBytes(12) - - - def setSize(self, value='S'): - """Sets the size of text printing. - -Accepted values are: -L: 'Large' (double height and double width) -M: 'Medium' (double height and normal width) -S: 'Small' (normal height and normal width) - -The default is 'S' for small/normal printing.""" - - 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, 10) - prevByte = '\n' # Setting the size adds a linefeed - - def printBitmap(self, w, h, bitmap, LaaT=False): - """Prints a bitmap image of width=w and height=h.""" - - rowBytes = (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): - super(Adafruit_Thermal, self).write( - chr(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, LaaT=False): - """Prints an image using PIL. - -The image is first cropped to 384pixels wide (if needed) and is -then converted to 1-bit with diffusion dithering. - -Any other image manipulation should be done on the image prior -passing it this method.""" - - from PIL import Image - - if image.mode != '1': - image = image.convert('1') - - width = image.size[0] - height = image.size[1] - if width > 384: - width = 384 - rowBytes = (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): - """Take the printer 'offline'. - -Print commands sent after this will be ignored until 'online()' -is called.""" - - self.writeBytes(27, 61, 0) - - - # Take the printer online. Subsequent print commands will be obeyed. - def online(self): - """Take the printer 'online'. - -This method does the opposite of 'offline' and all subsuquent -print commands will be obeyed.""" - - self.writeBytes(27, 61, 1) - - - # Put the printer into a low-energy state immediately. - def sleep(self): - """Immediately put the printer into a low-energy state.""" - - self.sleepAfter(1) - - - # Put the printer into a low-energy state after - # the given number of seconds. - def sleepAfter(self, seconds): - """Put the printer into a low-energy state after the given -number of seconds.""" - - self.writeBytes(27, 56, seconds) - - - def wake(self): - """Bring the printer out of a low-energy state back to -normal operation.""" - - self.timeoutSet(0); - self.writeBytes(255) - 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): - """Unimplemented method for compatibility with code ported from -Arduino.""" - - 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): - """Check for the presence of paper using the printer's own -reporting ability. - -Returns True if paper is present, and False in all other cases.""" - - self.writeBytes(27, 118, 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): - """Sets the height (in dots) of each line.""" - - 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) - - # Copied from Arduino lib for parity; is marked 'not working' there - def tab(self): - """Send a 'tab' character to the printer. - -This is a direct port from the Arduino Thermal Printer library -in which this method is marked as 'not working'.""" - - self.writeBytes(9) - - # Copied from Arduino lib for parity; is marked 'not working' there - def setCharSpacing(self, spacing): - """Sets the character spacing/kerning. - -This is a direct port from the Arduino Thermal Printer library -in which this method is marked as 'not working'.""" - - self.writeBytes(27, 32, 0, 10) - - # 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): - """Overloads print to allow for easier porting of Arduino -code.""" - - for arg in args: - self.write(str(arg)) - - # For Arduino code compatibility again - def println(self, *args, **kwargs): - """Overloads println to allow for easier porting of Aruduino -code.""" - - for arg in args: - self.write(str(arg)) - self.write('\n') + 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 = 120 + 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(chr(arg)) + else: + self.timeoutWait() + self.timeoutSet(len(args) * self.byteTime) + for arg in args: + super(Adafruit_Thermal, self).write(chr(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!") + 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)) + for i in range(n): + sys.stdout.write(text[i]) + else: + super(Adafruit_Thermal, self).write(chr(n)) + for i in range(n): + super(Adafruit_Thermal, + self).write(text[i]) + else: + # Older firmware: write string + NUL + if self.writeToStdout: + sys.stdout.write(text) + else: + super(Adafruit_Thermal, self).write(text) + 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, works, + # but it feeds much more than that. So, manually: + while x > 0: + self.write('\n') + 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 = (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( + chr(bitmap[i])) + else: + super(Adafruit_Thermal, + self).write(chr(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, LaaT=False): + from PIL import Image + + if image.mode != '1': + image = image.convert('1') + + width = image.size[0] + height = image.size[1] + if width > 384: + width = 384 + rowBytes = (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)) + + # For Arduino code compatibility again + def println(self, *args, **kwargs): + for arg in args: + self.write(str(arg)) + self.write('\n') diff --git a/examples/calibrate.py b/examples/calibrate.py index a06f7fa..e29585e 100755 --- a/examples/calibrate.py +++ b/examples/calibrate.py @@ -23,7 +23,7 @@ from __future__ import print_function from Adafruit_Thermal import * -printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) +printer = Adafruit_Thermal("/dev/serial0", 19200, timeout=5) for i in range(0,256,15): printer.begin(i) diff --git a/examples/forecast.py b/examples/forecast.py index d985497..3d9da85 100755 --- a/examples/forecast.py +++ b/examples/forecast.py @@ -1,7 +1,7 @@ #!/usr/bin/python # Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer. -# Retrieves data from Yahoo! weather, prints current conditions and +# Retrieves data from DarkSky.net's API, prints current conditions and # forecasts for next two days. See timetemp.py for a different # weather example using nice bitmaps. # Written by Adafruit Industries. MIT license. @@ -14,51 +14,52 @@ # http://www.adafruit.com/products/600 Printer starter pack from __future__ import print_function -import urllib, time from Adafruit_Thermal import * -from xml.dom.minidom import parseString +from datetime import date +from datetime import datetime +import calendar +import urllib, json -# WOEID indicates the geographic location for the forecast. It is -# not a ZIP code or other common indicator. Instead, it can be found -# by 'manually' visiting http://weather.yahoo.com, entering a location -# and requesting a forecast, then copy the number from the end of the -# current URL string and paste it here. -WOEID = '2459115' +API_KEY = "YOUR_API_KEY" + +LAT = "40.726019" +LONG = "-74.00536" # Dumps one forecast line to the printer def forecast(idx): - tag = 'yweather:forecast' - day = dom.getElementsByTagName(tag)[idx].getAttribute('day') - lo = dom.getElementsByTagName(tag)[idx].getAttribute('low') - hi = dom.getElementsByTagName(tag)[idx].getAttribute('high') - cond = dom.getElementsByTagName(tag)[idx].getAttribute('text') - printer.print(day + ': low ' + lo ) - printer.print(deg) - printer.print(' high ' + hi) - printer.print(deg) - printer.println(' ' + cond) -printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) + date = datetime.fromtimestamp(int(data['daily']['data'][idx]['time'])) + + day = calendar.day_name[date.weekday()] + lo = data['daily']['data'][idx]['temperatureMin'] + hi = data['daily']['data'][idx]['temperatureMax'] + cond = data['daily']['data'][idx]['summary'] + printer.print(day + ': low ' + str(lo) ) + printer.print(deg) + printer.print(' high ' + str(hi)) + printer.print(deg) + printer.println(' ' + cond.replace(u'\u2013', '-').encode('utf-8')) # take care of pesky unicode dash + +printer = Adafruit_Thermal("/dev/serial0", 19200, timeout=5) deg = chr(0xf8) # Degree symbol on thermal printer -# Fetch forecast data from Yahoo!, parse resulting XML -dom = parseString(urllib.urlopen( - 'http://weather.yahooapis.com/forecastrss?w=' + WOEID).read()) +url = "https://api.darksky.net/forecast/"+API_KEY+"/"+LAT+","+LONG+"?exclude=[alerts,minutely,hourly,flags]&units=us" +response = urllib.urlopen(url) +data = json.loads(response.read()) # Print heading printer.inverseOn() -printer.print('{:^32}'.format( - dom.getElementsByTagName('description')[0].firstChild.data)) +printer.print('{:^32}'.format("DarkSky.Net Forecast")) printer.inverseOff() # Print current conditions printer.boldOn() printer.print('{:^32}'.format('Current conditions:')) printer.boldOff() -printer.print('{:^32}'.format( - dom.getElementsByTagName('pubDate')[0].firstChild.data)) -temp = dom.getElementsByTagName('yweather:condition')[0].getAttribute('temp') -cond = dom.getElementsByTagName('yweather:condition')[0].getAttribute('text') + + +temp = data['currently']['temperature'] +cond = data['currently']['summary'] printer.print(temp) printer.print(deg) printer.println(' ' + cond) diff --git a/examples/iot_printer.py b/examples/iot_printer.py index 4af3456..9ebff71 100755 --- a/examples/iot_printer.py +++ b/examples/iot_printer.py @@ -16,7 +16,8 @@ from __future__ import print_function import RPi.GPIO as GPIO -import subprocess, time, Image, socket +import subprocess, time, socket +from PIL import Image from Adafruit_Thermal import * ledPin = 18 @@ -26,7 +27,7 @@ nextInterval = 0.0 # Time of next recurring operation dailyFlag = False # Set after daily trigger occurs lastId = '1' # State information passed to/from interval script -printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) +printer = Adafruit_Thermal("/dev/serial0", 19200, timeout=5) # Called when button is briefly tapped. Invokes time/temperature script. diff --git a/examples/printertest.py b/examples/printertest.py index 8a79279..2664884 100755 --- a/examples/printertest.py +++ b/examples/printertest.py @@ -2,7 +2,7 @@ from Adafruit_Thermal import * -printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) +printer = Adafruit_Thermal("/dev/serial0", 19200, timeout=5) # Test inverse on & off printer.inverseOn() @@ -61,7 +61,7 @@ import gfx.adaqrcode as adaqrcode printer.printBitmap(adaqrcode.width, adaqrcode.height, adaqrcode.data) printer.println("Adafruit!") -printer.feed(1) +printer.feed(2) printer.sleep() # Tell printer to sleep printer.wake() # Call wake() before printing again, even if reset diff --git a/examples/sudoku-gfx.py b/examples/sudoku-gfx.py index 91b5204..2a7ac12 100755 --- a/examples/sudoku-gfx.py +++ b/examples/sudoku-gfx.py @@ -23,9 +23,9 @@ from __future__ import print_function import sys, os, random, getopt, re from Adafruit_Thermal import * -import Image +from PIL import Image -printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) +printer = Adafruit_Thermal("/dev/serial0", 19200, timeout=5) bg = Image.new("1", [384, 426], "white") # Working 'background' image img = Image.open('gfx/sudoku.png') # Source bitmaps xcoord = [ 15, 55, 95, 139, 179, 219, 263, 303, 343 ] diff --git a/examples/sudoku-txt.py b/examples/sudoku-txt.py index 5e9a4db..5ae735c 100755 --- a/examples/sudoku-txt.py +++ b/examples/sudoku-txt.py @@ -22,7 +22,7 @@ import sys, os, random, getopt, re from Adafruit_Thermal import * -printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) +printer = Adafruit_Thermal("/dev/serial0", 19200, timeout=5) def main(): printer.setLineHeight(24) # So graphical chars fit together diff --git a/examples/timetemp.py b/examples/timetemp.py index 2835782..fcd16ba 100755 --- a/examples/timetemp.py +++ b/examples/timetemp.py @@ -1,7 +1,7 @@ #!/usr/bin/python # Current time and temperature display for Raspberry Pi w/Adafruit Mini -# Thermal Printer. Retrieves data from Yahoo! weather, prints current +# Thermal Printer. Retrieves data from DarkSky.net's API, prints current # conditions and time using large, friendly graphics. # See forecast.py for a different weather example that's all text-based. # Written by Adafruit Industries. MIT license. @@ -15,31 +15,32 @@ from __future__ import print_function from Adafruit_Thermal import * -from xml.dom.minidom import parseString -import Image, ImageDraw, time, urllib +import time, urllib, json +from PIL import Image, ImageDraw -# WOEID indicates the geographic location for the forecast. It is -# not a ZIP code or other common indicator. Instead, it can be found -# by 'manually' visiting http://weather.yahoo.com, entering a location -# and requesting a forecast, then copy the number from the end of the -# current URL string and paste it here. -WOEID = '2459115' +API_KEY = "YOUR_API_KEY" -# Fetch weather data from Yahoo!, parse resulting XML -dom = parseString(urllib.urlopen( - 'http://weather.yahooapis.com/forecastrss?w=' + WOEID).read()) +LAT = "40.726019" +LONG = "-74.00536" + +# Fetch weather data from DarkSky, parse resulting JSON +url = "https://api.darksky.net/forecast/"+API_KEY+"/"+LAT+","+LONG+"?exclude=[alerts,minutely,hourly,flags]&units=us" +response = urllib.urlopen(url) +data = json.loads(response.read()) # Extract values relating to current temperature, humidity, wind -temperature = int(dom.getElementsByTagName( - 'yweather:condition')[0].getAttribute('temp')) -humidity = int(dom.getElementsByTagName( - 'yweather:atmosphere')[0].getAttribute('humidity')) -windSpeed = int(dom.getElementsByTagName( - 'yweather:wind')[0].getAttribute('speed')) -windDir = int(dom.getElementsByTagName( - 'yweather:wind')[0].getAttribute('direction')) -windUnits = dom.getElementsByTagName( - 'yweather:units')[0].getAttribute('speed') + +temperature = int(data['currently']['temperature']) +humidity = int(data['currently']['humidity'] * 100); +windSpeed = int(data['currently']['windSpeed']) +windDir = data['currently']['windBearing'] +windUnits = "mph" + +# print(temperature) +# print(humidity) +# print(windSpeed) +# print(windDir) +# print(windUnits) # Although the Python Imaging Library does have nice font support, # I opted here to use a raster bitmap for all of the glyphs instead. @@ -62,11 +63,11 @@ # Generate a list of sub-image glyphs cropped from the symbols image def croplist(widths, x, y, height): - list = [] - for i in range(len(widths)): - list.append(symbols.crop( - [x, y+i*height, x+widths[i], y+(i+1)*height])) - return list + list = [] + for i in range(len(widths)): + list.append(symbols.crop( + [x, y+i*height, x+widths[i], y+(i+1)*height])) + return list # Crop glyph lists (digits, days of week, etc.) TimeDigit = croplist(TimeDigitWidth, 0, 0, 44) @@ -91,20 +92,20 @@ def croplist(widths, x, y, height): # Paste a series of glyphs (mostly numbers) from string to img def drawNums(str, x, y, list): - for i in range(len(str)): - d = ord(str[i]) - ord('0') - img.paste(list[d], (x, y)) - x += list[d].size[0] + 1 - return x + for i in range(len(str)): + d = ord(str[i]) - ord('0') + img.paste(list[d], (x, y)) + x += list[d].size[0] + 1 + return x # Determine total width of a series of glyphs in string def numWidth(str, list): - w = 0 # Cumulative width - for i in range(len(str)): - d = ord(str[i]) - ord('0') - if i > 0: w += 1 # Space between digits - w += list[d].size[0] # Digit width - return w + w = 0 # Cumulative width + for i in range(len(str)): + d = ord(str[i]) - ord('0') + if i > 0: w += 1 # Space between digits + w += list[d].size[0] # Digit width + return w # Render current time (always 24 hour XX:XX format) t = time.localtime() @@ -134,12 +135,13 @@ def numWidth(str, list): s2 = str(windSpeed) winDirNum = 0 # Wind direction glyph number if windSpeed > 0: - for winDirNum in range(len(DirAngle) - 1): - if windDir < DirAngle[winDirNum]: break + for winDirNum in range(len(DirAngle) - 1): + if windDir < DirAngle[winDirNum]: break +winDirNum+=1 w = Humidity.size[0] + 5 + numWidth(s, HumiDigit) w2 = Wind.size[0] + 5 + numWidth(s2, HumiDigit) if windSpeed > 0: - w2 += 3 + Dir[winDirNum].size[0] + w2 += 3 + Dir[winDirNum].size[0] if windUnits == 'kph': w2 += 3 + Kph.size[0] else: w2 += 3 + Mph.size[0] if w2 > w: w = w2 @@ -154,14 +156,15 @@ def numWidth(str, list): y += 23 # And advance to next line img.paste(Wind, (x, y)) x += Wind.size[0] + 5 + if windSpeed > 0: - img.paste(Dir[winDirNum], (x, y)) - x += Dir[winDirNum].size[0] + 3 + img.paste(Dir[winDirNum], (x, y)) + x += Dir[winDirNum].size[0] + 3 x = drawNums(s2, x, y, HumiDigit) + 3 if windUnits == 'kph': img.paste(Kph, (x, y)) else: img.paste(Mph, (x, y)) # Open connection to printer and print image -printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) +printer = Adafruit_Thermal("/dev/serial0", 19200, timeout=5) printer.printImage(img, True) printer.feed(3) diff --git a/examples/twitter.py b/examples/twitter.py index 662b1fd..9b96575 100755 --- a/examples/twitter.py +++ b/examples/twitter.py @@ -47,7 +47,7 @@ # Other globals. You probably won't need to change these. ----------------- -printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) +printer = Adafruit_Thermal("/dev/serial0", 19200, timeout=5) host = 'api.twitter.com' authUrl = '/oauth2/token' searchUrl = '/1.1/search/tweets.json?' diff --git a/setup.py b/setup.py index 3471145..3dfd39d 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def readme(): return f.read() setup(name='Adafruit_Thermal', - version='1.0.0', + version='1.1.0', description='Library for controlling Adafruit Thermal Printers', url='https://github.com/bareo/Python-Thermal-Printer', author='zach wick',