From 1ec19704b945483c346bc05d90274db53e3efa40 Mon Sep 17 00:00:00 2001 From: James Sutton Date: Fri, 30 Dec 2022 21:01:13 +0000 Subject: [PATCH 1/7] feat: general code improvements and adding github workflow Signed-off-by: James Sutton --- .flake8 | 5 + .github/workflows/python-test.yml | 65 ++ .pre-commit-config.yaml | 39 + bin/instax-print | 136 ---- instax/__init__.py | 13 +- instax/comms.py | 64 +- instax/debugServer.py | 346 ++++---- instax/instaxImage.py | 211 +---- instax/packet.py | 527 ++++++------ instax/print.py | 132 +++ instax/sp2.py | 109 +-- instax/sp3.py | 113 +-- instax/tests/replay.json | 250 ++++++ instax/tests/test_basic.py | 669 +++++++++++++++ ...image_encoder.py => test_image_encoder.py} | 14 +- instax/tests/test_premade.py | 175 ++++ .../tests/test_replay.py | 57 +- .../tests/{tests_socket.py => test_socket.py} | 21 +- instax/tests/{tests_sp2.py => test_sp2.py} | 55 +- instax/tests/{tests_sp3.py => test_sp3.py} | 57 +- instax/tests/tests_basic.py | 637 --------------- instax/tests/tests_premade.py | 176 ---- poetry.lock | 759 ++++++++++++++++++ pyproject.toml | 26 + requirements.txt | 7 - setup.py | 39 +- testServer.py | 91 --- 27 files changed, 2844 insertions(+), 1949 deletions(-) create mode 100644 .flake8 create mode 100644 .github/workflows/python-test.yml create mode 100644 .pre-commit-config.yaml delete mode 100755 bin/instax-print create mode 100755 instax/print.py create mode 100644 instax/tests/replay.json create mode 100644 instax/tests/test_basic.py rename instax/tests/{tests_image_encoder.py => test_image_encoder.py} (73%) create mode 100644 instax/tests/test_premade.py rename tests_replay.py => instax/tests/test_replay.py (51%) rename instax/tests/{tests_socket.py => test_socket.py} (70%) rename instax/tests/{tests_sp2.py => test_sp2.py} (58%) rename instax/tests/{tests_sp3.py => test_sp3.py} (56%) delete mode 100644 instax/tests/tests_basic.py delete mode 100644 instax/tests/tests_premade.py create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100755 testServer.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..c953d01 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 120 + +select = C,E,F,W,B,B950 +extend-ignore = E501,E203,W503 diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml new file mode 100644 index 0000000..9f1d89f --- /dev/null +++ b/.github/workflows/python-test.yml @@ -0,0 +1,65 @@ +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + #---------------------------------------------- + # check-out repo and set-up python + #---------------------------------------------- + - name: Check out repository + uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + #---------------------------------------------- + # ----- install & configure poetry ----- + #---------------------------------------------- + - name: Cache poetry install + uses: actions/cache@v3 + with: + path: ~/.local # the path depends on OS + key: poetry-0 # increment to reset cache + - name: Install poetry + if: steps.cached-poetry.outputs.cache-hit != 'true' + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + #---------------------------------------------- + # load cached venv if cache exists + #---------------------------------------------- + - name: Load Cached venv + id: cached-poetry-dependencies + uses: actions/cache@v3 + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version}}-${{ hashFiles('**/poetry.lock') }} + + #---------------------------------------------- + # install dependencies if cache does not exist + #---------------------------------------------- + - name: Install dependencies + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-root + #---------------------------------------------- + # install your root project, if required + #---------------------------------------------- + - name: Install project + run: poetry install --no-interaction + #---------------------------------------------- + # run test suite + #---------------------------------------------- + - name: Run tests + run: | + source .venv/bin/activate + pytest instax --cov + coverage report diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..48e571d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,39 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: end-of-file-fixer + - id: check-added-large-files + - id: requirements-txt-fixer + - id: check-json + - id: check-merge-conflict +- repo: https://github.com/pycqa/flake8 + rev: 5.0.4 + hooks: + - id: flake8 +- repo: https://github.com/pycqa/isort + rev: 5.10.1 + hooks: + - id: isort + name: isort (python) + args: ["--profile", "black"] +- repo: https://github.com/asottile/pyupgrade + rev: v3.2.0 + hooks: + - id: pyupgrade + args: ['--py3-plus', '--py36-plus'] +- repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + args: + - --line-length=120 +- repo: https://github.com/jorisroovers/gitlint + rev: v0.17.0 + hooks: + - id: gitlint + name: gitlint + language: python + entry: gitlint + args: [--staged, --msg-filename] + stages: [commit-msg] diff --git a/bin/instax-print b/bin/instax-print deleted file mode 100755 index 6c3eba5..0000000 --- a/bin/instax-print +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -"""Instax SP Print Script. - -Author: James Sutton 2017 - jsutton.co.uk - -This can be used to print an image to a Fujifilm Instax SP-2 printer. -Parameters: - - JSON Log File (Default ddmmyy-hhmmss.json) - - Image to print - - Port (Default 8080) - - Host (Default 192.168.0.251) - -""" -import argparse -import datetime -import sys -import instax -try: - import instax -except: - # We are most likely in development mode, import from parent. - from .. import instax -import logging - - -def logAndPrint(message): - print(message) - logging.info(message) - - -def printPrinterInfo(info): - """ Log Printer information""" - logAndPrint("Model: %s" % info['model']) - logAndPrint("Firmware: %s" % info['version']['firmware']) - logAndPrint("Battery State: %s" % info['battery']) - logAndPrint("Prints Remaining: %d" % info['printCount']) - logAndPrint("Total Lifetime Prints: %d" % info['count']) - logAndPrint("") - -#https://gist.github.com/vladignatyev/06860ec2040cb497f0f3 -def printProgress(count, total, status=''): - logging.info(status) - bar_len = 60 - filled_len = int(round(bar_len * count / float(total))) - percents = round(100.0 * count / float(total), 1) - bar = '=' * filled_len + '-' * (bar_len - filled_len) - sys.stdout.write('[%s] %s%s ...%s\r' % (bar, percents, '%', status)) - sys.stdout.flush() # As suggested by Rom Ruben (see: http://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console/27871113#comment50529068_27871113) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-d", "--debug", action="store_true", default=False, - help=argparse.SUPPRESS) - parser.add_argument("-l", "--log", action="store_true", default=False, - help=argparse.SUPPRESS) - parser.add_argument("-o", "--host", default='192.168.0.251', - help=argparse.SUPPRESS) - parser.add_argument("-p", "--port", type=int, default=8080, - help=argparse.SUPPRESS) - parser.add_argument("-i", "--pin", type=int, default=1111, - help="The pin code to use, default: 1111.") - parser.add_argument("-e", "--preview", action="store_true", default=False, - help="Show a preview of the image before it is printed, then exit.") - parser.add_argument("-t", "--timeout", type=int, default=10, - help=argparse.SUPPRESS) - parser.add_argument("-v", "--version", type=int, default=2, - choices=[1,2,3], - help="The version of Instax Printer to use (1, 2 or 3). Default is 2 (SP-2).") - parser.add_argument("image", help="The location of the image to print.") - args = parser.parse_args() - - - # If Not specified, set the log file to a datestamp. - if args.log: - logFilename = '{0:%Y-%m-%d.%H-%M-%S.log}'.format(datetime.datetime.now()) - logLevel = logging.INFO - if args.debug: - logLevel = logging.DEBUG - logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', filename=logFilename, level=logLevel) - - - logAndPrint("--- Instax Printer Python Client ---") - logAndPrint("") - myInstax = None - - if args.version is 1: - logging.info("Attempting to print to an Instax SP-1 printer.") - # TODO - Need to find an SP-2 to test with. - elif args.version is 2: - logging.info("Attempting to print to an Instax SP-2 printer.") - myInstax = instax.SP2(ip=args.host, port=args.port, pinCode=args.pin, - timeout=args.timeout) - elif args.version is 3: - logging.info("Attempting to print to an Instax SP-3 printer.") - # Warning, this does not work in production yet. - myInstax = instax.SP3(ip=args.host, port=args.port, pinCode=args.pin, - timeout=args.timeout) - else: - logging.error("Invalid Instax printer version given") - exit(1) - - if args.preview is True: - # Going to preview the image as it will be printed - logAndPrint("Previewing Image") - instaxImage = instax.InstaxImage(type=args.version) - instaxImage.loadImage(args.image) - instaxImage.convertImage() - instaxImage.previewImage() - logAndPrint("Preview complete, exiting.") - exit(0) - else: - # Attempt print - logAndPrint("Connecting to Printer.") - info = myInstax.getPrinterInformation() - printPrinterInfo(info) - - logAndPrint("Printing Image: %s" % args.image) - # Initialize The Instax Image - instaxImage = instax.InstaxImage(type=args.version) - instaxImage.loadImage(args.image) - instaxImage.convertImage() - # Save a copy of the converted bitmap - #instaxImage.saveImage("test.bmp") - # Preview the image that is about to print - #instaxImage.previewImage() - encodedImage = instaxImage.encodeImage() - myInstax.printPhoto(encodedImage, printProgress) - logAndPrint("Thank you for using instax-print!") - logAndPrint(r""" - \ /\ - ) ( ') - ( / ) - \(___)|""") - - diff --git a/instax/__init__.py b/instax/__init__.py index b33de38..8675559 100644 --- a/instax/__init__.py +++ b/instax/__init__.py @@ -1,12 +1 @@ -from .sp2 import SP2 -from .sp3 import SP3 -from .debugServer import DebugServer -from .instaxImage import InstaxImage -from .packet import PacketFactory, Packet, SpecificationsCommand, \ - VersionCommand, PrintCountCommand, PrePrintCommand, \ - ModelNameCommand, PrinterLockCommand, SendImageCommand, \ - ResetCommand, PrepImageCommand, Type83Command, \ - Type195Command, LockStateCommand - - -version = "0.7" +version = "0.8.0" diff --git a/instax/comms.py b/instax/comms.py index 1745acd..e1692d2 100644 --- a/instax/comms.py +++ b/instax/comms.py @@ -1,17 +1,18 @@ +import queue import socket import threading -import queue class ClientCommand: - """ A command to the client thread. - Each command type has it's associated data: + """A command to the client thread. + Each command type has it's associated data: - CONNECT: (host, port) tuple - SEND: Data byte array - RECEIVE: None - CLOSE: None + CONNECT: (host, port) tuple + SEND: Data byte array + RECEIVE: None + CLOSE: None """ + CONNECT, SEND, RECEIVE, CLOSE = range(4) def __init__(self, type, data=None): @@ -19,14 +20,15 @@ def __init__(self, type, data=None): self.data = data -class ClientReply(object): - """ A reply from the client thread. - Each reply has it's associated data: +class ClientReply: + """A reply from the client thread. + Each reply has it's associated data: - ERROR: The error string. - SUCCESS: Depends on the command, For RECEIVE it's the received - data string, for others, None. + ERROR: The error string. + SUCCESS: Depends on the command, For RECEIVE it's the received + data string, for others, None. """ + ERROR, SUCCESS = range(2) def __init__(self, type, data=None): @@ -35,12 +37,13 @@ def __init__(self, type, data=None): class SocketClientThread(threading.Thread): - """ Implements the threading.Thread interface (start, join, etc..) and - can be controlled by the cmd_q Queue attribute. Replies are placed - in the reply_q Queue attribute. + """Implements the threading.Thread interface (start, join, etc..) and + can be controlled by the cmd_q Queue attribute. Replies are placed + in the reply_q Queue attribute. """ + def __init__(self, cmd_q=None, reply_q=None): - super(SocketClientThread, self).__init__() + super().__init__() self.cmd_q = cmd_q or queue.Queue() self.reply_q = reply_q or queue.Queue() self.alive = threading.Event() @@ -49,18 +52,18 @@ def __init__(self, cmd_q=None, reply_q=None): self.handlers = { ClientCommand.CONNECT: self._handle_CONNECT, - ClientCommand.CLOSE: self._handle_CLOSE, - ClientCommand.SEND: self._handle_SEND, + ClientCommand.CLOSE: self._handle_CLOSE, + ClientCommand.SEND: self._handle_SEND, ClientCommand.RECEIVE: self._handle_RECEIVE, } def run(self): - while self.alive.isSet(): + while self.alive.is_set(): try: # Queue.get with timeout to allow checking self.alive cmd = self.cmd_q.get(True, 0.1) self.handlers[cmd.type](cmd) - except queue.Empty as e: + except queue.Empty: continue def join(self, timeout=None): @@ -69,12 +72,11 @@ def join(self, timeout=None): def _handle_CONNECT(self, cmd): try: - self.socket = socket.socket( - socket.AF_INET, socket.SOCK_STREAM) + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.settimeout(5) self.socket.connect((cmd.data[0], cmd.data[1])) self.reply_q.put(self._success_reply()) - except IOError as e: + except OSError as e: self.reply_q.put(self._error_reply(str(e))) def _handle_CLOSE(self, cmd): @@ -87,31 +89,31 @@ def _handle_SEND(self, cmd): try: self.socket.sendall(cmd.data) self.reply_q.put(self._success_reply()) - except IOError as e: + except OSError as e: self.reply_q.put(self._error_reply(str(e))) def _handle_RECEIVE(self, cmd): try: header_data = self._recv_n_bytes(4) if len(header_data) == 4: - msg_len = ((header_data[2] & 0xFF) << 8 | (header_data[3] & 0xFF) << 0) + msg_len = (header_data[2] & 0xFF) << 8 | (header_data[3] & 0xFF) << 0 data = self._recv_n_bytes(msg_len - 4) payload = header_data + data if len(payload) == msg_len: self.reply_q.put(self._success_reply(payload)) return - self.reply_q.put(self._error_reply('Socket Closed Prematuerly')) - except IOError as e: + self.reply_q.put(self._error_reply("Socket Closed Prematuerly")) + except OSError as e: self.reply_q.put(self._error_reply(str(e))) def _recv_n_bytes(self, n): - """ Convenience method for receiving excactly n bytes from - self.socket (assuming it's open and connected). + """Convenience method for receiving excactly n bytes from + self.socket (assuming it's open and connected). """ data = bytearray() while len(data) < n: chunk = self.socket.recv(n - len(data)) - if chunk == b'': + if chunk == b"": break data += chunk return data diff --git a/instax/debugServer.py b/instax/debugServer.py index 9e78f6a..66e2dae 100644 --- a/instax/debugServer.py +++ b/instax/debugServer.py @@ -1,30 +1,58 @@ """ Fujifilm Instax-SP2 Server for testing. -James Sutton - 2017 - jsutton.co.uk +Author: James Sutton 2017 - jsutton.co.uk + +This wrapper can be used to start a test server implementation. +You can configure a number of useful parameters to use whist the server is +running. +Parameters: + - Verbose (Default False) + - JSON Log File (Default ddmmyy-hhmmss.json) + - Port (Default 8080) + - Photo Destination Directory: (Default: images) + - Battery Level: (Default 100%) + - Prints Remaining: (Default 10) + - Total Prints in History: (Default 20) + """ -import socket -from .packet import Packet, PacketFactory, SpecificationsCommand, \ - VersionCommand, PrintCountCommand, ModelNameCommand, PrePrintCommand, \ - PrinterLockCommand, ResetCommand, PrepImageCommand, SendImageCommand, \ - Type83Command, Type195Command, LockStateCommand -from .instaxImage import InstaxImage +import argparse +import datetime +import json +import logging import signal +import socket import sys -import time -import json import threading -import logging +import time +from loguru import logger + +from instax.instaxImage import InstaxImage +from instax.packet import ( + LockStateCommand, + ModelNameCommand, + Packet, + PacketFactory, + PrepImageCommand, + PrePrintCommand, + PrintCountCommand, + PrinterLockCommand, + ResetCommand, + SendImageCommand, + SpecificationsCommand, + Type83Command, + Type195Command, + VersionCommand, +) class DebugServer: """A Test Server for the Instax Library.""" - def __init__(self, host='0.0.0.0', port=8080, - dest="images", battery=2, remaining=10, total=20, version=2): + def __init__(self, host="0.0.0.0", port=8080, dest="images", battery=2, remaining=10, total=20, version=2): """Initialise Server.""" - self.logger = logging.getLogger('instax_server') + self.logger = logging.getLogger("instax_server") self.packetFactory = PacketFactory() self.host = host self.dest = dest @@ -32,7 +60,7 @@ def __init__(self, host='0.0.0.0', port=8080, self.backlog = 5 self.returnCode = Packet.RTN_E_RCV_FRAME self.ejecting = 0 - if version in [2,3]: + if version in [2, 3]: self.version = version else: self.logger.warning("Invalid Instax SP version, defaulting to SP-2") @@ -53,20 +81,19 @@ def __init__(self, host='0.0.0.0', port=8080, def start(self): """Start the Server.""" self.socket.listen(self.backlog) - self.logger.info(('Instax SP-%d Server Listening on %s port %s' % (self.version, self.host, self.port))) + self.logger.info("Instax SP-%d Server Listening on %s port %s" % (self.version, self.host, self.port)) while True: client, address = self.socket.accept() client.settimeout(60) - threading.Thread(target=self.listenToClient, - args=(client, address)).start() - + threading.Thread(target=self.listenToClient, args=(client, address)).start() + def getPort(self): self.logger.info(self.socket.getsockname()) return self.socket.getsockname()[1] def listenToClient(self, client, address): """Interact with client.""" - self.logger.info('New Client Connected') + self.logger.info("New Client Connected") length = None buffer = bytearray() while True: @@ -76,8 +103,7 @@ def listenToClient(self, client, address): buffer += data while True: if length is None: - length = ((buffer[2] & 0xFF) << 8 | - (buffer[3] & 0xFF) << 0) + length = (buffer[2] & 0xFF) << 8 | (buffer[3] & 0xFF) << 0 if len(buffer) < length: break @@ -86,16 +112,16 @@ def listenToClient(self, client, address): buffer = bytearray() length = None break - self.logger.info('Client Disconnected') + self.logger.info("Client Disconnected") def signal_handler(self, signal, frame): """Handle Ctrl+C events.""" - print('You pressed Ctrl+C! Saving Log and shutting down.') + print("You pressed Ctrl+C! Saving Log and shutting down.") self.logger.info("Shutting down Server") timestr = time.strftime("%Y%m%d-%H%M%S") filename = "instaxServer-" + timestr + ".json" self.logger.info("Saving Log to: %s" % filename) - with open(filename, 'w') as outfile: + with open(filename, "w") as outfile: json.dump(self.messageLog, outfile, indent=4) self.logger.info("Log file written, have a nice day!") sys.exit(0) @@ -119,11 +145,10 @@ def printByteArray(self, byteArray): Prints a Byte array in the following format: b1b2 b3b4... """ - hexString = ''.join('%02x' % i for i in byteArray) - data = ' '.join(hexString[i:i + 4] - for i in range(0, len(hexString), 4)) - info = (data[:80] + '..') if len(data) > 80 else data - return(info) + hexString = "".join("%02x" % i for i in byteArray) + data = " ".join(hexString[i : i + 4] for i in range(0, len(hexString), 4)) + info = (data[:80] + "..") if len(data) > 80 else data + return info def processIncomingMessage(self, payload): """Take an incoming message and return the response.""" @@ -134,32 +159,32 @@ def processIncomingMessage(self, payload): self.logger.info("Processing message type: %s" % decodedPacket.NAME) response = None - if(decodedPacket.TYPE == Packet.MESSAGE_TYPE_PRINTER_VERSION): + if decodedPacket.TYPE == Packet.MESSAGE_TYPE_PRINTER_VERSION: response = self.processVersionCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_SPECIFICATIONS): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_SPECIFICATIONS: response = self.processSpecificationsCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_MODEL_NAME): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_MODEL_NAME: response = self.processModelNameCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_PRINT_COUNT): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_PRINT_COUNT: response = self.processPrintCountCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_PRE_PRINT): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_PRE_PRINT: response = self.processPrePrintCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_LOCK_DEVICE): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_LOCK_DEVICE: response = self.processLockPrinterCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_RESET): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_RESET: response = self.processResetCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_PREP_IMAGE): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_PREP_IMAGE: response = self.processPrepImageCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_SEND_IMAGE): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_SEND_IMAGE: response = self.processSendImageCommand(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_83): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_83: response = self.processType83Command(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_195): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_195: response = self.processType195Command(decodedPacket) - elif(decodedPacket.TYPE == Packet.MESSAGE_TYPE_SET_LOCK_STATE): + elif decodedPacket.TYPE == Packet.MESSAGE_TYPE_SET_LOCK_STATE: response = self.processSetLockStateCommand(decodedPacket) else: - self.logger.info('Unknown Command. Failing!: ' + str(decodedPacket.TYPE)) + self.logger.info("Unknown Command. Failing!: " + str(decodedPacket.TYPE)) decodedResponsePacket = packetFactory.decode(response) self.messageLog.append(decodedResponsePacket.getPacketObject()) @@ -167,151 +192,123 @@ def processIncomingMessage(self, payload): def processVersionCommand(self, decodedPacket): """Process a version command.""" - sessionTime = decodedPacket.header['sessionTime'] - resPacket = VersionCommand(Packet.MESSAGE_MODE_RESPONSE, - unknown1=254, - firmware=275, - hardware=0) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + sessionTime = decodedPacket.header["sessionTime"] + resPacket = VersionCommand(Packet.MESSAGE_MODE_RESPONSE, unknown1=254, firmware=275, hardware=0) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processSpecificationsCommand(self, decodedPacket): """Process a specifications command.""" - sessionTime = decodedPacket.header['sessionTime'] - resPacket = SpecificationsCommand(Packet.MESSAGE_MODE_RESPONSE, - maxHeight=800, - maxWidth=600, - maxColours=256, - unknown1=10, - maxMsgSize=60000, - unknown2=16, - unknown3=0) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + sessionTime = decodedPacket.header["sessionTime"] + resPacket = SpecificationsCommand( + Packet.MESSAGE_MODE_RESPONSE, + maxHeight=800, + maxWidth=600, + maxColours=256, + unknown1=10, + maxMsgSize=60000, + unknown2=16, + unknown3=0, + ) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processModelNameCommand(self, decodedPacket): """Process a model name command.""" - sessionTime = decodedPacket.header['sessionTime'] - resPacket = ModelNameCommand(Packet.MESSAGE_MODE_RESPONSE, - modelName= ("SP-%d" % self.version)) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + sessionTime = decodedPacket.header["sessionTime"] + resPacket = ModelNameCommand(Packet.MESSAGE_MODE_RESPONSE, modelName=("SP-%d" % self.version)) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processPrintCountCommand(self, decodedPacket): """Process a Print Count command.""" - sessionTime = decodedPacket.header['sessionTime'] - resPacket = PrintCountCommand(Packet.MESSAGE_MODE_RESPONSE, - printHistory=20) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + sessionTime = decodedPacket.header["sessionTime"] + resPacket = PrintCountCommand(Packet.MESSAGE_MODE_RESPONSE, printHistory=20) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processPrePrintCommand(self, decodedPacket): """Process a Pre Print command.""" - cmdNumber = decodedPacket.payload['cmdNumber'] - if(cmdNumber in [6, 7, 8]): + cmdNumber = decodedPacket.payload["cmdNumber"] + if cmdNumber in [6, 7, 8]: respNumber = 0 - elif(cmdNumber in [4, 5]): + elif cmdNumber in [4, 5]: respNumber = 1 - elif(cmdNumber in [1, 2, 3]): + elif cmdNumber in [1, 2, 3]: respNumber = 2 else: self.logger.warning("Unknown cmdNumber") respNumber = 0 - sessionTime = decodedPacket.header['sessionTime'] - resPacket = PrePrintCommand(Packet.MESSAGE_MODE_RESPONSE, - cmdNumber=cmdNumber, - respNumber=respNumber) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + sessionTime = decodedPacket.header["sessionTime"] + resPacket = PrePrintCommand(Packet.MESSAGE_MODE_RESPONSE, cmdNumber=cmdNumber, respNumber=respNumber) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processLockPrinterCommand(self, decodedPacket): """Process a Lock Printer Command.""" - sessionTime = decodedPacket.header['sessionTime'] + sessionTime = decodedPacket.header["sessionTime"] resPacket = PrinterLockCommand(Packet.MESSAGE_MODE_RESPONSE) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processResetCommand(self, decodedPacket): """Process a Rest command.""" - sessionTime = decodedPacket.header['sessionTime'] + sessionTime = decodedPacket.header["sessionTime"] resPacket = ResetCommand(Packet.MESSAGE_MODE_RESPONSE) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processPrepImageCommand(self, decodedPacket): """Process a Prep Image Commnand.""" - sessionTime = decodedPacket.header['sessionTime'] - resPacket = PrepImageCommand(Packet.MESSAGE_MODE_RESPONSE, - maxLen=60000) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + sessionTime = decodedPacket.header["sessionTime"] + resPacket = PrepImageCommand(Packet.MESSAGE_MODE_RESPONSE, maxLen=60000) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processSendImageCommand(self, decodedPacket): """Process a Send Image Command.""" - sessionTime = decodedPacket.header['sessionTime'] - sequenceNumber = decodedPacket.payload['sequenceNumber'] - payloadBytes = decodedPacket.payload['payloadBytes'] - resPacket = SendImageCommand(Packet.MESSAGE_MODE_RESPONSE, - sequenceNumber=sequenceNumber) + sessionTime = decodedPacket.header["sessionTime"] + sequenceNumber = decodedPacket.payload["sequenceNumber"] + payloadBytes = decodedPacket.payload["payloadBytes"] + resPacket = SendImageCommand(Packet.MESSAGE_MODE_RESPONSE, sequenceNumber=sequenceNumber) if sessionTime not in self.imageMap: self.imageMap[sessionTime] = {} self.imageMap[sessionTime][sequenceNumber] = payloadBytes - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processType83Command(self, decodedPacket): """Process a Type 83 command.""" - sessionTime = decodedPacket.header['sessionTime'] + sessionTime = decodedPacket.header["sessionTime"] resPacket = Type83Command(Packet.MESSAGE_MODE_RESPONSE) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) # Start a thread to decode the image imageSegments = self.imageMap[sessionTime] - threading.Thread(target=self.decodeImage, - args=(imageSegments,)).start() + threading.Thread(target=self.decodeImage, args=(imageSegments,)).start() return encodedResponse def processType195Command(self, decodedPacket): - sessionTime = decodedPacket.header['sessionTime'] + sessionTime = decodedPacket.header["sessionTime"] returnCode = Packet.RTN_E_PRINTING if self.printingState == 100: returnCode = Packet.RTN_E_RCV_FRAME @@ -319,22 +316,77 @@ def processType195Command(self, decodedPacket): else: self.printingState += 25 resPacket = Type195Command(Packet.MESSAGE_MODE_RESPONSE) - encodedResponse = resPacket.encodeResponse(sessionTime, - returnCode, - self.ejecting, - self.battery, - self.printCount) + encodedResponse = resPacket.encodeResponse( + sessionTime, returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse def processSetLockStateCommand(self, decodedPacket): """Process a Lock State Command.""" unknownFourByteInt = 100 - sessionTime = decodedPacket.header['sessionTime'] - resPacket = LockStateCommand(Packet.MESSAGE_MODE_RESPONSE, - unknownFourByteInt=unknownFourByteInt) - encodedResponse = resPacket.encodeResponse(sessionTime, - self.returnCode, - self.ejecting, - self.battery, - self.printCount) + sessionTime = decodedPacket.header["sessionTime"] + resPacket = LockStateCommand(Packet.MESSAGE_MODE_RESPONSE, unknownFourByteInt=unknownFourByteInt) + encodedResponse = resPacket.encodeResponse( + sessionTime, self.returnCode, self.ejecting, self.battery, self.printCount + ) return encodedResponse + + +if __name__ == "__main__": + logger.info("---------- Instax SP-2 Test Server ---------- ") + + def remaining_type(x): + """Validate Remaining count is between 0 and 10.""" + x = int(x) + if x < 10 and x >= 0: + raise argparse.ArgumentTypeError("Remaining must be between 0 and 10.") + return x + + parser = argparse.ArgumentParser() + parser.add_argument( + "-v", "--verbose", action="store_true", default=False, help="Print Verbose log messages to console." + ) + parser.add_argument("-D", "--debug", action="store_true", default=False, help="Logs extra debug data to log.") + parser.add_argument( + "-l", "--log", action="store_true", default=False, help="Log information to log file ddmmyy-hhmmss-server.log" + ) + parser.add_argument("-o", "--host", default="0.0.0.0", help="The Host IP to expose the server on.") + parser.add_argument("-p", "--port", type=int, default=8080, help="The port to expose the server on.") + parser.add_argument( + "-d", "--dest", default="images", help="The Directory to save incoming photos," "default: 'images'" + ) + parser.add_argument( + "-b", + "--battery", + type=int, + choices=range(0, 4), + default=2, + help="The Battery level of the printer" " 0-4, default: 2", + ) + parser.add_argument( + "-r", "--remaining", type=remaining_type, default=10, help="The number of remaining prints 0-10, default: 10" + ) + parser.add_argument( + "-t", "--total", type=int, default=20, help="The total number of prints in the printers lifetime" ", default 20" + ) + parser.add_argument("-V", "--version", type=int, default=2, help="The Instax SP-* version, 2 or 3, default is 2") + args = parser.parse_args() + + # Create Log Formatter + formatter = logging.Formatter("%(asctime)s:%(name)s:%(levelname)s:%(message)s") + + # If Not specified, set the log file to a datestamp. + if args.log: + logFilename = f"{datetime.datetime.now():%Y-%m-%d.%H:%M:%S-server.log}" + logger.add(logFilename) + + testServer = DebugServer( + host=args.host, + port=args.port, + dest=args.dest, + battery=args.battery, + remaining=args.remaining, + total=args.total, + version=args.version, + ) + testServer.start() diff --git a/instax/instaxImage.py b/instax/instaxImage.py index a741af8..ea39d61 100644 --- a/instax/instaxImage.py +++ b/instax/instaxImage.py @@ -1,17 +1,12 @@ """Image transformation utilities.""" +from loguru import logger from PIL import Image, ImageOps -import logging class InstaxImage: """Image Utilities class.""" - dimensions = { - 1 : (600, 800), - 2 : (600, 800), - 3 : (800, 800) - } - + dimensions = {1: (600, 800), 2: (600, 800), 3: (800, 800)} def __init__(self, type=2): """Initialise the instax Image.""" @@ -25,33 +20,25 @@ def loadImage(self, imagePath): def encodeImage(self): """Encode the loaded Image.""" imgWidth, imgHeight = self.myImage.size - logging.info("Initial Image Size: W: %s, H: %s" % (imgWidth, imgHeight)) # Quick check that it's the right dimensions - if(imgWidth + imgHeight != (self.printHeight + self.printWidth)): + if imgWidth + imgHeight != (self.printHeight + self.printWidth): raise Exception("Image was not 800x600 or 600x800, it was : w:%d, h:%d" % (imgWidth, imgHeight)) - if(imgWidth != self.printWidth): + if imgWidth != self.printWidth: # Rotate the image - logging.info("Rotating") self.myImage = self.myImage.rotate(-90, expand=True) - if (self.printWidth == self.printHeight): + if self.printWidth == self.printHeight: # Square images are a bit tricky, we have to assume they are oriented correctly - logging.info("Rotating Square Image") + logger.info("Rotating Square Image") self.myImage = self.myImage.rotate(-90, expand=True) - logging.info("New Image Size: W: %s, H: %s" % (self.myImage.size)) imagePixels = self.myImage.getdata() - logging.info("Mode: %s" % (self.myImage.mode)) arrayLen = len(imagePixels) * 3 - logging.info("Encoded Array Length: %s" % arrayLen) encodedBytes = [None] * arrayLen for h in range(self.printHeight): for w in range(self.printWidth): - r, g, b= imagePixels[(h * self.printWidth) + w] - redTarget = (((w * self.printHeight) * 3) + - (self.printHeight * 0)) + h - greenTarget = (((w * self.printHeight) * 3) + - (self.printHeight * 1)) + h - blueTarget = (((w * self.printHeight) * 3) + - (self.printHeight * 2)) + h + r, g, b = imagePixels[(h * self.printWidth) + w] + redTarget = (((w * self.printHeight) * 3) + (self.printHeight * 0)) + h + greenTarget = (((w * self.printHeight) * 3) + (self.printHeight * 1)) + h + blueTarget = (((w * self.printHeight) * 3) + (self.printHeight * 2)) + h encodedBytes[redTarget] = int(r) encodedBytes[greenTarget] = int(g) encodedBytes[blueTarget] = int(b) @@ -63,44 +50,30 @@ def decodeImage(self, imageBytes): # Packing the individual colours back together. for h in range(self.printHeight): for w in range(self.printWidth): - redTarget = (((w * self.printHeight) * 3) + - (self.printHeight * 0)) + h - greenTarget = (((w * self.printHeight) * 3) + - (self.printHeight * 1)) + h - blueTarget = (((w * self.printHeight) * 3) + - (self.printHeight * 2)) + h + redTarget = (((w * self.printHeight) * 3) + (self.printHeight * 0)) + h + greenTarget = (((w * self.printHeight) * 3) + (self.printHeight * 1)) + h + blueTarget = (((w * self.printHeight) * 3) + (self.printHeight * 2)) + h targetImg.append(imageBytes[redTarget]) targetImg.append(imageBytes[greenTarget]) targetImg.append(imageBytes[blueTarget]) - preImage = Image.frombytes('RGB', - (self.printWidth, - self.printHeight), - bytes(targetImg)) + preImage = Image.frombytes("RGB", (self.printWidth, self.printHeight), bytes(targetImg)) self.myImage = preImage.rotate(90, expand=True) - def convertImage(self, crop_type='middle', - backgroundColour=(255, 255, 255, 0)): + def convertImage(self, crop_type="middle", backgroundColour=(255, 255, 255, 0)): """Rotate, Resize and Crop the image. Rotate, Resize and Crop the image, so that it is the correct dimensions for printing to the Instax SP-2. """ maxSize = self.printHeight, self.printWidth # The Max Image size - rotatedImage = rotate_image(self.sourceImage) - image_ratio = rotatedImage.size[0] / float(rotatedImage.size[1]) - if(rotatedImage.size[0] + rotatedImage.size[1] == (self.printHeight + self.printWidth)): - img = rotatedImage - else: - if image_ratio == 1.0: - img = crop_square(rotatedImage, maxSize, backgroundColour) - else: - img = crop_rectangle(rotatedImage, maxSize, crop_type) + # Strip Exif and rotate image correctly + rotatedImage = ImageOps.exif_transpose(self.sourceImage) + + # Fit the image to the required ratio + fittedImage = ImageOps.fit(rotatedImage, maxSize, bleed=0, centering=(0.5, 0.5)) - # Stip away any exif data. - newImage = Image.new(img.mode, img.size) - newImage.putdata(img.getdata()) - self.myImage = pure_pil_alpha_to_color_v2(newImage, (255,255,255)) + self.myImage = pure_pil_alpha_to_color_v2(fittedImage, (255, 255, 255)) def previewImage(self): """Preview the image.""" @@ -108,8 +81,8 @@ def previewImage(self): def saveImage(self, filename): """Save the image to the specified path.""" - logging.info(("Saving Image to: ", filename)) - self.myImage.save(filename, 'BMP', quality=100, optimise=True) + logger.info(("Saving Image to: ", filename)) + self.myImage.save(filename, "BMP", quality=100, optimise=True) def getBytes(self): """Get the Byte Array from the image.""" @@ -117,140 +90,6 @@ def getBytes(self): return myBytes -def rotate_image(source): - """Rotate the image. - - Rotates and/or flips an image to the correct orientation - for the instax printer. - - args: - source: The Source ImageOps - """ - exif_orientation = 274 - img_ratio = source.size[0] / float(source.size[1]) - if(source._getexif() is None): - return source - exif_data = dict(source._getexif().items()) - if img_ratio > 1: - # Image is landscape - if exif_orientation in exif_data: - orientation = exif_data[exif_orientation] - if orientation == 1: - tmpImage = source.rotate(270, expand=True) - elif orientation == 2: - tmpImage = ImageOps.mirror(source) - tmpImage = tmpImage.rotate(270, expand=True) - elif orientation == 3: - tmpImage = source.rotate(90, expand=True) - elif orientation == 4: - tmpImage = ImageOps.mirror(source) - tmpImage = tmpImage.rotate(90, expand=True) - elif orientation == 5: - tmpImage = ImageOps.flip(source) - tmpImage = tmpImage.rotate(270, expand=True) - elif orientation == 6: - tmpImage = source.rotate(270, expand=True) - elif orientation == 7: - tmpImage = ImageOps.flip(source) - tmpImage = tmpImage.rotate(90, expand=True) - elif orientation == 8: - tmpImage = source.rotate(90, expand=True) - else: - # Invalid Orientation, As it's landscape, just rotate 270 - tmpImage = source.rotate(270, expand=True) - else: - # No Orientation Exif data, As it's landscape, just rotate 270 - tmpImage = source.rotate(270, expand=True) - else: - if exif_orientation in exif_data: - orientation = exif_data[exif_orientation] - if orientation == 1: - tmpImage = source - elif orientation == 2: - tmpImage = ImageOps.mirror(source) - elif orientation == 3: - tmpImage = source.rotate(180, expand=True) - elif orientation == 4: - tmpImage = ImageOps.mirror(source) - tmpImage = tmpImage.rotate(180, expand=True) - elif orientation == 5: - tmpImage = ImageOps.flip(source) - tmpImage = tmpImage.rotate(180, expand=True) - elif orientation == 6: - tmpImage = source.rotate(180, expand=True) - elif orientation == 7: - tmpImage = ImageOps.flip(source) - elif orientation == 8: - tmpImage = source - else: - tmpImage = source - else: - tmpImage = source - return tmpImage - - -def crop_square(source, size, backgroundColour=(255, 255, 255, 0)): - """Crop the image to a square.""" - source.thumbnail(size, Image.ANTIALIAS) - offset_x = int(max((size[0] - source.size[0]) / 2, 0)) - offset_y = int(max((size[1] - source.size[1]) / 2, 0)) - offset_tuple = (offset_x, offset_y) - img = Image.new(mode='RGBA', size=size, color=backgroundColour) - img.paste(source, offset_tuple) - return img - - -def crop_rectangle(source, size, crop_type='top'): - """Crop the Image to a rectangle. - - args: - source: The source image - size: `(width, height)` tuple - crop_type: can be 'top', 'middle' or 'bottom', depending on this - value, the image will be cropped getting the 'top/left', 'middle' - or 'bottom/right' of the image to fid the size. - - raises: - ValueError: if an invalid `crop_type` is provided. - - """ - ratio = size[0] / float(size[1]) - new_ratio = source.size[0] / float(source.size[1]) - - if ratio > new_ratio: - # The image is scaled/cropped vertically or horizontally - targetSize = int(round(size[0] * source.size[1] / source.size[0])) - img = source.resize((size[0], targetSize), Image.ANTIALIAS) - # Crop in the top, middle or bottom - if crop_type == 'top': - box = (0, 0, img.size[0], size[1]) - elif crop_type == 'middle': - box = (0, int(round((img.size[1] - size[1]) / 2)), img.size[0], - int(round((img.size[1] + size[1]) / 2))) - elif crop_type == 'bottom': - box = (0, img.size[1] - size[1], img.size[0], img.size[1]) - else: - raise ValueError('ERROR: invalid value for crop_type: ', crop_type) - img = img.crop(box) - elif ratio < new_ratio: - targetSize = int(round(size[1] * source.size[0] / source.size[1])) - img = source.resize((targetSize, size[1]), Image.ANTIALIAS) - # Crop in the top, middle or bottom - if crop_type == 'top': - box = (0, 0, img.size[0], size[1]) - elif crop_type == 'middle': - box = (int(round((img.size[0] + size[0]) / 2)), 0, - int(round((img.size[0] + size[0]) / 2)), img.size[1]) - elif crop_type == 'bottom': - box = (img.size[0] - size[0], 0, img.size[0], img.size[1]) - else: - raise ValueError('ERROR: invalid value for crop_type: ', crop_type) - img = img.crop(box) - else: - img = source.resize((size[0], size[1]), Image.ANTIALIAS) - - return img - def pure_pil_alpha_to_color_v2(image, color=(255, 255, 255)): """Alpha composite an RGBA Image with a specified color. @@ -264,6 +103,6 @@ def pure_pil_alpha_to_color_v2(image, color=(255, 255, 255)): """ image.load() # needed for split() - background = Image.new('RGB', image.size, color) + background = Image.new("RGB", image.size, color) background.paste(image) # 3 is the alpha channel - return background \ No newline at end of file + return background diff --git a/instax/packet.py b/instax/packet.py index 8bb3638..894b9fa 100644 --- a/instax/packet.py +++ b/instax/packet.py @@ -4,9 +4,10 @@ or recieved from a Fujifilm Instax SP-2. It is designed to be used with the instax_api Python Library. """ -import logging +from loguru import logger -class PacketFactory(object): + +class PacketFactory: """Packet Factory. Used to generate new pakcets and to decode existing packets. @@ -35,9 +36,8 @@ def __init__(self): def printRawByteArray(self, byteArray): """Print a byte array fully.""" - hexString = ''.join('%02x' % i for i in byteArray) - return(' '.join(hexString[i:i + 4] for i in range( - 0, len(hexString), 4))) + hexString = "".join("%02x" % i for i in byteArray) + return " ".join(hexString[i : i + 4] for i in range(0, len(hexString), 4)) def decode(self, byteArray): """Decode a byte array into an instax Packet.""" @@ -48,35 +48,35 @@ def decode(self, byteArray): # Identify the type of packet and hand over to that packets class if pType == self.MESSAGE_TYPE_SPECIFICATIONS: - return(SpecificationsCommand(mode=self.mode, byteArray=byteArray)) + return SpecificationsCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_PRINTER_VERSION: - return(VersionCommand(mode=self.mode, byteArray=byteArray)) + return VersionCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_PRINT_COUNT: - return(PrintCountCommand(mode=self.mode, byteArray=byteArray)) + return PrintCountCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_RESET: - return(ResetCommand(mode=self.mode, byteArray=byteArray)) + return ResetCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_PREP_IMAGE: - return(PrepImageCommand(mode=self.mode, byteArray=byteArray)) + return PrepImageCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_SEND_IMAGE: - return(SendImageCommand(mode=self.mode, byteArray=byteArray)) + return SendImageCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_MODEL_NAME: - return(ModelNameCommand(mode=self.mode, byteArray=byteArray)) + return ModelNameCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_PRE_PRINT: - return(PrePrintCommand(mode=self.mode, byteArray=byteArray)) + return PrePrintCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_LOCK_DEVICE: - return(PrinterLockCommand(mode=self.mode, byteArray=byteArray)) + return PrinterLockCommand(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_83: - return(Type83Command(mode=self.mode, byteArray=byteArray)) + return Type83Command(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_195: - return(Type195Command(mode=self.mode, byteArray=byteArray)) + return Type195Command(mode=self.mode, byteArray=byteArray) elif pType == self.MESSAGE_TYPE_SET_LOCK_STATE: - return(LockStateCommand(mode=self.mode, byteArray=byteArray)) + return LockStateCommand(mode=self.mode, byteArray=byteArray) else: - logging.debug("Unknown Packet Type: " + str(pType)) - logging.debug("Packet Bytes: [" + self.printRawByteArray(byteArray) + "]") + logger.debug("Unknown Packet Type: " + str(pType)) + logger.debug("Packet Bytes: [" + self.printRawByteArray(byteArray) + "]") -class Packet(object): +class Packet: """Base Packet Class.""" MESSAGE_TYPE_SPECIFICATIONS = 79 @@ -116,10 +116,7 @@ class Packet(object): RTN_E_OTHER_USED = 160 RTN_ST_UPDATE = 127 # RET_HOLD - strings = { - MESSAGE_MODE_COMMAND: "Command", - MESSAGE_MODE_RESPONSE: "Response" - } + strings = {MESSAGE_MODE_COMMAND: "Command", MESSAGE_MODE_RESPONSE: "Response"} def __init__(self, mode=None): """Init for Packet.""" @@ -130,65 +127,63 @@ def printByteArray(self, byteArray): Prints a Byte array in the following format: b1b2 b3b4... """ - hexString = ''.join('%02x' % i for i in byteArray) - data = ' '.join(hexString[i:i + 4] - for i in range(0, len(hexString), 4)) - info = (data[:80] + '..') if len(data) > 80 else data - return(info) + hexString = "".join("%02x" % i for i in byteArray) + data = " ".join(hexString[i : i + 4] for i in range(0, len(hexString), 4)) + info = (data[:80] + "..") if len(data) > 80 else data + return info def printRawByteArray(self, byteArray): """Print a byte array fully.""" - hexString = ''.join('%02x' % i for i in byteArray) - return(' '.join(hexString[i:i + 4] for i in range( - 0, len(hexString), 4))) + hexString = "".join("%02x" % i for i in byteArray) + return " ".join(hexString[i : i + 4] for i in range(0, len(hexString), 4)) def printDebug(self): """Print Debug information about packet.""" - logging.debug("--------------------- Packet Debug Data --------------------") - logging.debug("Bytes: %s" % (self.printByteArray(self.byteArray))) - logging.debug("Mode: %s" % (self.strings[self.mode])) - logging.debug("Type: %s" % (self.NAME)) - logging.debug("Valid: %s" % (self.valid)) - logging.debug("Header:") - logging.debug(" Start Byte: %s" % (self.header['startByte'])) - logging.debug(" Command: %s" % (self.header['cmdByte'])) - logging.debug(" Packet Length: %s" % (self.header['packetLength'])) - logging.debug(" Session Time: %s" % (self.header['sessionTime'])) - if(self.mode == self.MESSAGE_MODE_COMMAND): - logging.debug(" Password: %s" % (self.header['password'])) - elif(self.mode == self.MESSAGE_MODE_RESPONSE): - logging.debug(" Return Code: %s" % (self.header['returnCode'])) - logging.debug(" Unknown 1: %s" % (self.header['unknown1'])) - logging.debug(" Ejecting: %s" % (self.header['ejecting'])) - logging.debug(" Battery: %s" % (self.header['battery'])) - logging.debug(" Prints Left: %s" % (self.header['printCount'])) + logger.debug("--------------------- Packet Debug Data --------------------") + logger.debug("Bytes: %s" % (self.printByteArray(self.byteArray))) + logger.debug("Mode: %s" % (self.strings[self.mode])) + logger.debug("Type: %s" % (self.NAME)) + logger.debug("Valid: %s" % (self.valid)) + logger.debug("Header:") + logger.debug(" Start Byte: %s" % (self.header["startByte"])) + logger.debug(" Command: %s" % (self.header["cmdByte"])) + logger.debug(" Packet Length: %s" % (self.header["packetLength"])) + logger.debug(" Session Time: %s" % (self.header["sessionTime"])) + if self.mode == self.MESSAGE_MODE_COMMAND: + logger.debug(" Password: %s" % (self.header["password"])) + elif self.mode == self.MESSAGE_MODE_RESPONSE: + logger.debug(" Return Code: %s" % (self.header["returnCode"])) + logger.debug(" Unknown 1: %s" % (self.header["unknown1"])) + logger.debug(" Ejecting: %s" % (self.header["ejecting"])) + logger.debug(" Battery: %s" % (self.header["battery"])) + logger.debug(" Prints Left: %s" % (self.header["printCount"])) if len(self.payload) == 0: - logging.debug("Payload: None") + logger.debug("Payload: None") else: - logging.debug("Payload:") + logger.debug("Payload:") for key in self.payload: - if(key == 'payloadBytes'): - logging.debug(" payloadBytes: (length: %s) : [%s]" % - (str(len(self.payload[key])), - self.printByteArray(self.payload[key]))) + if key == "payloadBytes": + logger.debug( + " payloadBytes: (length: %s) : [%s]" + % (str(len(self.payload[key])), self.printByteArray(self.payload[key])) + ) else: - logging.debug(" %s : %s" % (key, self.payload[key])) - logging.debug("------------------------------------------------------------") + logger.debug(f" {key} : {self.payload[key]}") + logger.debug("------------------------------------------------------------") def getPacketObject(self): """Return a simple object containing all packet details.""" packetObj = {} - packetObj['bytes'] = self.printByteArray(self.byteArray) - packetObj['header'] = self.header + packetObj["bytes"] = self.printByteArray(self.byteArray) + packetObj["header"] = self.header packetPayload = {} for key in self.payload: - if(key == 'payloadBytes'): - packetPayload['payloadBytes'] = self.printByteArray( - self.payload[key]) + if key == "payloadBytes": + packetPayload["payloadBytes"] = self.printByteArray(self.payload[key]) else: packetPayload[key] = self.payload[key] - packetObj['payload'] = packetPayload + packetObj["payload"] = packetPayload return packetObj def decodeHeader(self, mode, byteArray): @@ -197,23 +192,18 @@ def decodeHeader(self, mode, byteArray): cmdByte = self.getOneByteInt(1, byteArray) packetLength = self.getTwoByteInt(2, byteArray) responseTime = self.getFourByteInt(4, byteArray) - header = { - 'startByte': startByte, - 'cmdByte': cmdByte, - 'packetLength': packetLength, - 'sessionTime': responseTime - } + header = {"startByte": startByte, "cmdByte": cmdByte, "packetLength": packetLength, "sessionTime": responseTime} - if(mode == self.MESSAGE_MODE_COMMAND): + if mode == self.MESSAGE_MODE_COMMAND: # Command Specific Header Fields - header['password'] = self.getTwoByteInt(8, byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + header["password"] = self.getTwoByteInt(8, byteArray) + elif mode == self.MESSAGE_MODE_RESPONSE: # Payload Specific Header Fields - header['returnCode'] = self.getOneByteInt(12, byteArray) - header['unknown1'] = self.getOneByteInt(13, byteArray) - header['ejecting'] = self.getEjecting(14, byteArray) - header['battery'] = self.getBatteryLevel(byteArray) - header['printCount'] = self.getPrintCount(byteArray) + header["returnCode"] = self.getOneByteInt(12, byteArray) + header["unknown1"] = self.getOneByteInt(13, byteArray) + header["ejecting"] = self.getEjecting(14, byteArray) + header["battery"] = self.getBatteryLevel(byteArray) + header["printCount"] = self.getPrintCount(byteArray) self.header = header @@ -228,14 +218,13 @@ def validatePacket(self, byteArray, packetLength): try: checkSumIndex = 0 checkSum = 0 - while(checkSumIndex < (packetLength - 4)): - checkSum += (byteArray[checkSumIndex] & 0xFF) + while checkSumIndex < (packetLength - 4): + checkSum += byteArray[checkSumIndex] & 0xFF checkSumIndex += 1 - if ((byteArray[checkSumIndex + 2] == 13) and - (byteArray[checkSumIndex + 3] == 10)): - expectedCB = (checkSum + - (((byteArray[checkSumIndex] & 0xFF) << 8) - | ((byteArray[checkSumIndex + 1] & 0xFF) << 0))) + if (byteArray[checkSumIndex + 2] == 13) and (byteArray[checkSumIndex + 3] == 10): + expectedCB = checkSum + ( + ((byteArray[checkSumIndex] & 0xFF) << 8) | ((byteArray[checkSumIndex + 1] & 0xFF) << 0) + ) if (expectedCB & 65535) == 65535: return True else: @@ -243,12 +232,12 @@ def validatePacket(self, byteArray, packetLength): else: return False except Exception as ex: - logging.debug("Unexpected Error validating packet: " + str(type(ex))) - logging.debug(ex.args) - logging.debug(ex) - logging.debug("Expected: %s" % (packetLength)) - logging.debug("Actual: %s" % (str(len(byteArray)))) - logging.debug("Final 4 bytes: %s" % (self.printByteArray(byteArray[-4:]))) + logger.debug("Unexpected Error validating packet: " + str(type(ex))) + logger.debug(ex.args) + logger.debug(ex) + logger.debug("Expected: %s" % (packetLength)) + logger.debug("Actual: %s" % (str(len(byteArray)))) + logger.debug("Final 4 bytes: %s" % (self.printByteArray(byteArray[-4:]))) def generateCommand(self, mode, cmdType, sessionTime, payload, pinCode): """Generate a command. @@ -256,26 +245,23 @@ def generateCommand(self, mode, cmdType, sessionTime, payload, pinCode): Takes Command arguments and packs them into a byteArray to be sent to the Instax SP-2. """ - self.encodedSessionTime = self.getFourByteInt( - 0, - self.encodeFourByteInt(sessionTime)) + self.encodedSessionTime = self.getFourByteInt(0, self.encodeFourByteInt(sessionTime)) commandPayloadLength = 16 + len(payload) commandPayload = bytearray() commandPayload.append(mode & 0xFF) # Start of payload is 36 commandPayload.append(cmdType & 0xFF) # The Command bytes - commandPayload = commandPayload + \ - self.encodeTwoByteInt(commandPayloadLength) + commandPayload = commandPayload + self.encodeTwoByteInt(commandPayloadLength) commandPayload = commandPayload + self.encodeFourByteInt(sessionTime) commandPayload = commandPayload + self.encodeTwoByteInt(pinCode) commandPayload.append(0) # Nothing commandPayload.append(0) # Nothing - if(len(payload) > 0): + if len(payload) > 0: commandPayload = commandPayload + payload # Generating the Checksum & End of payload checkSumIndex = 0 checkSum = 0 - while(checkSumIndex < (commandPayloadLength - 4)): - checkSum += (commandPayload[checkSumIndex] & 0xFF) + while checkSumIndex < (commandPayloadLength - 4): + checkSum += commandPayload[checkSumIndex] & 0xFF checkSumIndex += 1 commandPayload.append(((checkSum ^ -1) >> 8) & 0xFF) commandPayload.append(((checkSum ^ -1) >> 0) & 0xFF) @@ -283,36 +269,32 @@ def generateCommand(self, mode, cmdType, sessionTime, payload, pinCode): commandPayload.append(10) return commandPayload - def generateResponse(self, mode, cmdType, sessionTime, payload, returnCode, - ejectState, battery, printCount): + def generateResponse(self, mode, cmdType, sessionTime, payload, returnCode, ejectState, battery, printCount): """Generate a response Byte Array. Takes Response arguments and packs them into a byteArray to be sent to the Instax-SP2. """ - self.encodedSessionTime = self.getFourByteInt( - 0, self.encodeFourByteInt(sessionTime)) + self.encodedSessionTime = self.getFourByteInt(0, self.encodeFourByteInt(sessionTime)) responsePayloadLength = 20 + len(payload) responsePayload = bytearray() responsePayload.append(mode & 0xFF) # Start of payload is 42 responsePayload.append(cmdType & 0xFF) # The Response type bytes - responsePayload = responsePayload + self.encodeTwoByteInt( - responsePayloadLength) + responsePayload = responsePayload + self.encodeTwoByteInt(responsePayloadLength) responsePayload = responsePayload + self.encodeFourByteInt(sessionTime) responsePayload = responsePayload + bytearray(4) responsePayload = responsePayload + self.encodeOneByteInt(returnCode) responsePayload.append(0) # Nothing responsePayload = responsePayload + self.encodeEjecting(0) - responsePayload = responsePayload + self.encodeBatteryAndPrintCount( - battery, printCount) + responsePayload = responsePayload + self.encodeBatteryAndPrintCount(battery, printCount) - if(len(payload) > 0): + if len(payload) > 0: responsePayload = responsePayload + payload # Generating the Checksum & End of payload checkSumIndex = 0 checkSum = 0 - while(checkSumIndex < (responsePayloadLength - 4)): - checkSum += (responsePayload[checkSumIndex] & 0xFF) + while checkSumIndex < (responsePayloadLength - 4): + checkSum += responsePayload[checkSumIndex] & 0xFF checkSumIndex += 1 responsePayload.append(((checkSum ^ -1) >> 8) & 0xFF) responsePayload.append(((checkSum ^ -1) >> 0) & 0xFF) @@ -323,29 +305,28 @@ def generateResponse(self, mode, cmdType, sessionTime, payload, returnCode, def encodeCommand(self, sessionTime, pinCode): """Encode a command packet into a byteArray.""" payload = self.encodeComPayload() - encodedPacket = self.generateCommand(self.mode, self.TYPE, - sessionTime, payload, pinCode) + encodedPacket = self.generateCommand(self.mode, self.TYPE, sessionTime, payload, pinCode) return encodedPacket - def encodeResponse(self, sessionTime, returnCode, ejectState, battery, - printCount): + def encodeResponse(self, sessionTime, returnCode, ejectState, battery, printCount): """Encode a response packet into a byteArray.""" payload = self.encodeRespPayload() - encodedPacket = self.generateResponse(self.mode, self.TYPE, - sessionTime, payload, - returnCode, ejectState, - battery, printCount) + encodedPacket = self.generateResponse( + self.mode, self.TYPE, sessionTime, payload, returnCode, ejectState, battery, printCount + ) return encodedPacket def getFourByteInt(self, offset, byteArray): """Decode a Four Byte Integer.""" - if(len(byteArray) < (offset + 4)): + if len(byteArray) < (offset + 4): return 0 else: - return (((byteArray[offset] & 0xFF) << 24) | ( - (byteArray[(offset) + 1] & 0xFF) << 16) | ( - (byteArray[(offset) + 2] & 0xFF) << 8) | ( - (byteArray[(offset) + 3] & 0xFF) << 0)) + return ( + ((byteArray[offset] & 0xFF) << 24) + | ((byteArray[(offset) + 1] & 0xFF) << 16) + | ((byteArray[(offset) + 2] & 0xFF) << 8) + | ((byteArray[(offset) + 3] & 0xFF) << 0) + ) def encodeFourByteInt(self, numberToEncode): """Encode a Four Byte Integer.""" @@ -358,11 +339,10 @@ def encodeFourByteInt(self, numberToEncode): def getTwoByteInt(self, offset, byteArray): """Decode a Two Byte Integer.""" - if(len(byteArray) < (offset + 2)): + if len(byteArray) < (offset + 2): return 0 else: - return (((byteArray[offset] & 0xFF) << 8) | ( - (byteArray[(offset) + 1] & 0xFF) << 0)) + return ((byteArray[offset] & 0xFF) << 8) | ((byteArray[(offset) + 1] & 0xFF) << 0) def encodeTwoByteInt(self, numberToEncode): """Encode a Two Byte Integer.""" @@ -373,10 +353,10 @@ def encodeTwoByteInt(self, numberToEncode): def getOneByteInt(self, offset, byteArray): """Decode a One Byte Integer.""" - if(len(byteArray) < (offset + 1)): + if len(byteArray) < (offset + 1): return 0 else: - return (byteArray[offset] & 0xFF) + return byteArray[offset] & 0xFF def encodeOneByteInt(self, numberToEncode): """Encode a One Byte Integer.""" @@ -386,10 +366,10 @@ def encodeOneByteInt(self, numberToEncode): def getEjecting(self, offset, byteArray): """Decode the Ejecting State.""" - if(len(byteArray) < (offset + 1)): + if len(byteArray) < (offset + 1): return 0 else: - return ((byteArray[offset] >> 2) & 0xFF) + return (byteArray[offset] >> 2) & 0xFF def encodeEjecting(self, eject): """Encode the Ejecting State.""" @@ -399,17 +379,17 @@ def encodeEjecting(self, eject): def getBatteryLevel(self, byteArray): """Decode the Battery Level.""" - if(len(byteArray) < 16): + if len(byteArray) < 16: return -1 else: - return ((byteArray[15] >> 4) & 7) + return (byteArray[15] >> 4) & 7 def getPrintCount(self, byteArray): """Decode the Print Count.""" - if(len(byteArray) < 16): + if len(byteArray) < 16: return -1 else: - return ((byteArray[15] >> 0) & 15) + return (byteArray[15] >> 0) & 15 def encodeBatteryAndPrintCount(self, battery, printCount): """Encode Battery Level and Print Count.""" @@ -420,8 +400,8 @@ def encodeBatteryAndPrintCount(self, battery, printCount): def formatVersionNumber(self, version): """Encode a Version Number.""" part2 = version & 0xFF - part1 = ((65280 & version) >> 8) - return('%s.%s' % ("%0.2X" % part1, "%0.2X" % part2)) + part1 = (65280 & version) >> 8 + return "{}.{}".format("%0.2X" % part1, "%0.2X" % part2) def encodeModelString(self, model): """Encode a Model String.""" @@ -429,14 +409,14 @@ def encodeModelString(self, model): def getPrinterModelString(self, offset, byteArray): """Decode a Model String.""" - if(len(byteArray) < (offset + 4)): - return '' + if len(byteArray) < (offset + 4): + return "" else: - return str(byteArray[offset: offset + 4], 'ascii') + return str(byteArray[offset : offset + 4], "ascii") def getPayloadBytes(self, offset, length, byteArray): """Return Payload Bytes.""" - return byteArray[offset:offset + length] + return byteArray[offset : offset + length] class SpecificationsCommand(Packet): @@ -445,23 +425,30 @@ class SpecificationsCommand(Packet): NAME = "Specifications" TYPE = Packet.MESSAGE_TYPE_SPECIFICATIONS - def __init__(self, mode, byteArray=None, maxHeight=800, maxWidth=600, - maxColours=256, unknown1=None, maxMsgSize=None, unknown2=None, - unknown3=None): + def __init__( + self, + mode, + byteArray=None, + maxHeight=800, + maxWidth=600, + maxColours=256, + unknown1=None, + maxMsgSize=None, + unknown2=None, + unknown3=None, + ): """Initialise the Packet.""" - super(SpecificationsCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if (byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(SpecificationsCommand, - self).decodeHeader(mode, byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.payload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -512,13 +499,13 @@ def decodeRespPayload(self, byteArray): self.unknown2 = self.getOneByteInt(30, byteArray) self.unknown3 = self.getFourByteInt(32, byteArray) self.payload = { - 'maxHeight': self.maxHeight, - 'maxWidth': self.maxWidth, - 'maxColours': self.maxColours, - 'unknown1': self.unknown1, - 'maxMsgSize': self.maxMsgSize, - 'unknown2': self.unknown2, - 'unknown3': self.unknown3 + "maxHeight": self.maxHeight, + "maxWidth": self.maxWidth, + "maxColours": self.maxColours, + "unknown1": self.unknown1, + "maxMsgSize": self.maxMsgSize, + "unknown2": self.unknown2, + "unknown3": self.unknown3, } return self.payload @@ -529,22 +516,19 @@ class VersionCommand(Packet): NAME = "Version" TYPE = Packet.MESSAGE_TYPE_PRINTER_VERSION - def __init__(self, mode, byteArray=None, unknown1=None, firmware=None, - hardware=None): + def __init__(self, mode, byteArray=None, unknown1=None, firmware=None, hardware=None): """Initialise the packet.""" - super(VersionCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if (byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(VersionCommand, - self).decodeHeader(mode, byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.payload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -578,15 +562,9 @@ def encodeRespPayload(self): def decodeRespPayload(self, byteArray): """Decode Response payload.""" self.unknown1 = self.getTwoByteInt(16, byteArray) - self.firmware = self.formatVersionNumber( - self.getTwoByteInt(18, byteArray)) - self.hardware = self.formatVersionNumber( - self.getTwoByteInt(20, byteArray)) - self.payload = { - 'unknown1': self.unknown1, - 'firmware': self.firmware, - 'hardware': self.hardware - } + self.firmware = self.formatVersionNumber(self.getTwoByteInt(18, byteArray)) + self.hardware = self.formatVersionNumber(self.getTwoByteInt(20, byteArray)) + self.payload = {"unknown1": self.unknown1, "firmware": self.firmware, "hardware": self.hardware} return self.payload @@ -598,19 +576,17 @@ class PrintCountCommand(Packet): def __init__(self, mode, byteArray=None, printHistory=None): """Initialise the packet.""" - super(PrintCountCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if (byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(PrintCountCommand, - self).decodeHeader(mode, byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.payload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -640,9 +616,7 @@ def encodeRespPayload(self): def decodeRespPayload(self, byteArray): """Decode Response payload.""" self.printHistory = self.getFourByteInt(16, byteArray) - self.payload = { - 'printHistory': self.printHistory - } + self.payload = {"printHistory": self.printHistory} return self.payload @@ -654,19 +628,17 @@ class ModelNameCommand(Packet): def __init__(self, mode, byteArray=None, modelName=None): """Initialise the packet.""" - super(ModelNameCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if (byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(ModelNameCommand, - self).decodeHeader(mode, byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.payload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -695,9 +667,7 @@ def encodeRespPayload(self): def decodeRespPayload(self, byteArray): """Decode Response payload.""" self.modelName = self.getPrinterModelString(16, byteArray) - self.payload = { - 'modelName': self.modelName - } + self.payload = {"modelName": self.modelName} return self.payload @@ -709,19 +679,17 @@ class PrePrintCommand(Packet): def __init__(self, mode, byteArray=None, cmdNumber=None, respNumber=None): """Initialise the packet.""" - super(PrePrintCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if(byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(PrePrintCommand, - self).decodeHeader(mode, byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.payload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -738,9 +706,7 @@ def encodeComPayload(self): def decodeComPayload(self, byteArray): """Decode the Command Payload.""" self.cmdNumber = self.getTwoByteInt(14, byteArray) - self.payload = { - 'cmdNumber': self.cmdNumber - } + self.payload = {"cmdNumber": self.cmdNumber} return self.payload def encodeRespPayload(self): @@ -754,10 +720,7 @@ def decodeRespPayload(self, byteArray): """Decode Response Payload.""" self.cmdNumber = self.getTwoByteInt(16, byteArray) self.respNumber = self.getTwoByteInt(18, byteArray) - self.payload = { - 'cmdNumber': self.cmdNumber, - 'respNumber': self.respNumber - } + self.payload = {"cmdNumber": self.cmdNumber, "respNumber": self.respNumber} return self.payload @@ -769,18 +732,16 @@ class PrinterLockCommand(Packet): def __init__(self, mode, lockState=None, byteArray=None): """Initialise Lock Printer Packet.""" - super(PrinterLockCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if (byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(PrinterLockCommand, - self).decodeHeader(mode, byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.decodedCommandPayload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -797,9 +758,7 @@ def encodeComPayload(self): def decodeComPayload(self, byteArray): """Decode the Command Payload.""" self.lockState = self.getOneByteInt(12, byteArray) - self.payload = { - 'lockState': self.lockState - } + self.payload = {"lockState": self.lockState} return self.payload def encodeRespPayload(self): @@ -819,18 +778,16 @@ class ResetCommand(Packet): def __init__(self, mode, byteArray=None): """Initialise Reset Command Packet.""" - super(ResetCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if (byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(ResetCommand, - self).decodeHeader(mode, byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.decodedCommandPayload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -858,21 +815,18 @@ class PrepImageCommand(Packet): NAME = "PrepImage" TYPE = Packet.MESSAGE_TYPE_PREP_IMAGE - def __init__(self, mode, byteArray=None, format=None, options=None, - imgLength=None, maxLen=None): + def __init__(self, mode, byteArray=None, format=None, options=None, imgLength=None, maxLen=None): """Initialise Prep Image Command Packet.""" - super(PrepImageCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if (byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(PrepImageCommand, - self).decodeHeader(mode, byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.decodedCommandPayload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -898,11 +852,7 @@ def decodeComPayload(self, byteArray): self.options = self.getOneByteInt(13, byteArray) self.imgLength = self.getFourByteInt(14, byteArray) - self.payload = { - 'format': self.format, - 'options': self.options, - 'imgLength': self.imgLength - } + self.payload = {"format": self.format, "options": self.options, "imgLength": self.imgLength} return self.payload def encodeRespPayload(self): @@ -914,9 +864,7 @@ def encodeRespPayload(self): def decodeRespPayload(self, byteArray): """Decode Response Payload.""" self.maxLen = self.getTwoByteInt(18, byteArray) - self.payload = { - 'maxLen': self.maxLen - } + self.payload = {"maxLen": self.maxLen} return self.payload @@ -926,21 +874,18 @@ class SendImageCommand(Packet): NAME = "Send Image" TYPE = Packet.MESSAGE_TYPE_SEND_IMAGE - def __init__(self, mode, byteArray=None, sequenceNumber=None, - payloadBytes=None): + def __init__(self, mode, byteArray=None, sequenceNumber=None, payloadBytes=None): """Initialise Send Image Command Packet.""" - super(SendImageCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if(byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(SendImageCommand, self).decodeHeader(mode, - byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.decodedCommandPayload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -957,13 +902,9 @@ def encodeComPayload(self): def decodeComPayload(self, byteArray): """Decode the Command Payload.""" self.sequenceNumber = self.getFourByteInt(12, byteArray) - payloadBytesLength = self.header['packetLength'] - 20 - self.payloadBytes = self.getPayloadBytes(16, payloadBytesLength, - byteArray) - self.payload = { - 'sequenceNumber': self.sequenceNumber, - 'payloadBytes': self.payloadBytes - } + payloadBytesLength = self.header["packetLength"] - 20 + self.payloadBytes = self.getPayloadBytes(16, payloadBytesLength, byteArray) + self.payload = {"sequenceNumber": self.sequenceNumber, "payloadBytes": self.payloadBytes} return self.payload def encodeRespPayload(self): @@ -975,9 +916,7 @@ def encodeRespPayload(self): def decodeRespPayload(self, byteArray): """Decode Response Payload.""" self.sequenceNumber = self.getOneByteInt(19, byteArray) - self.payload = { - 'sequenceNumber': self.sequenceNumber - } + self.payload = {"sequenceNumber": self.sequenceNumber} return self.payload @@ -989,18 +928,16 @@ class Type83Command(Packet): def __init__(self, mode, byteArray=None): """Initialise Type 83 Command Packet.""" - super(Type83Command, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if(byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(Type83Command, self).decodeHeader(mode, - byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.decodedCommandPayload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -1030,18 +967,16 @@ class Type195Command(Packet): def __init__(self, mode, byteArray=None): """Initialise Type 195 Command Packet.""" - super(Type195Command, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if(byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(Type195Command, self).decodeHeader(mode, - byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.decodedCommandPayload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -1071,18 +1006,16 @@ class LockStateCommand(Packet): def __init__(self, mode, byteArray=None, unknownFourByteInt=None): """Initialise Lock State Command Packet.""" - super(LockStateCommand, self).__init__(mode) + super().__init__(mode) self.payload = {} self.mode = mode - if(byteArray is not None): + if byteArray is not None: self.byteArray = byteArray - self.header = super(LockStateCommand, self).decodeHeader(mode, - byteArray) - self.valid = self.validatePacket(byteArray, - self.header['packetLength']) - if(mode == self.MESSAGE_MODE_COMMAND): + self.header = super().decodeHeader(mode, byteArray) + self.valid = self.validatePacket(byteArray, self.header["packetLength"]) + if mode == self.MESSAGE_MODE_COMMAND: self.decodedCommandPayload = self.decodeComPayload(byteArray) - elif(mode == self.MESSAGE_MODE_RESPONSE): + elif mode == self.MESSAGE_MODE_RESPONSE: self.decodedCommandPayload = self.decodeRespPayload(byteArray) else: self.mode = mode @@ -1105,7 +1038,5 @@ def encodeRespPayload(self): def decodeRespPayload(self, byteArray): """Decode Response Payload.""" self.unknownFourByteInt = self.getFourByteInt(16, byteArray) - self.payload = { - 'unknownFourByteInt': self.unknownFourByteInt - } + self.payload = {"unknownFourByteInt": self.unknownFourByteInt} return self.payload diff --git a/instax/print.py b/instax/print.py new file mode 100755 index 0000000..675f5b9 --- /dev/null +++ b/instax/print.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""Instax SP Print Script. + +Author: James Sutton 2017 - jsutton.co.uk + +This can be used to print an image to a Fujifilm Instax SP-2 printer. +Parameters: + - JSON Log File (Default ddmmyy-hhmmss.json) + - Image to print + - Port (Default 8080) + - Host (Default 192.168.0.251) + +""" +import argparse +import datetime +import sys + +from loguru import logger + +from instax.instaxImage import InstaxImage +from instax.sp2 import SP2 +from instax.sp3 import SP3 + + +def printPrinterInfo(info): + """Log Printer information""" + logger.info("Model: %s" % info["model"]) + logger.info("Firmware: %s" % info["version"]["firmware"]) + logger.info("Battery State: %s" % info["battery"]) + logger.info("Prints Remaining: %d" % info["printCount"]) + logger.info("Total Lifetime Prints: %d" % info["count"]) + logger.info("") + + +# https://gist.github.com/vladignatyev/06860ec2040cb497f0f3 +def printProgress(count, total, status=""): + # logger.info(status) + bar_len = 60 + filled_len = int(round(bar_len * count / float(total))) + percents = round(100.0 * count / float(total), 1) + bar = "=" * filled_len + "-" * (bar_len - filled_len) + sys.stdout.write("[{}] {}{} ...{}\r".format(bar, percents, "%", status)) + sys.stdout.flush() # As suggested by Rom Ruben (see: http://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console/27871113#comment50529068_27871113) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-d", "--debug", action="store_true", default=False, help=argparse.SUPPRESS) + parser.add_argument("-l", "--log", action="store_true", default=False, help=argparse.SUPPRESS) + parser.add_argument("-o", "--host", default="192.168.0.251", help=argparse.SUPPRESS) + parser.add_argument("-p", "--port", type=int, default=8080, help=argparse.SUPPRESS) + parser.add_argument("-i", "--pin", type=int, default=1111, help="The pin code to use, default: 1111.") + parser.add_argument( + "-e", + "--preview", + action="store_true", + default=False, + help="Show a preview of the image before it is printed, then exit.", + ) + parser.add_argument("-t", "--timeout", type=int, default=10, help=argparse.SUPPRESS) + parser.add_argument( + "-v", + "--version", + type=int, + default=2, + choices=[1, 2, 3], + help="The version of Instax Printer to use (1, 2 or 3). Default is 2 (SP-2).", + ) + parser.add_argument("image", help="The location of the image to print.") + args = parser.parse_args() + + if not args.debug: + logger.remove() + logger.add(sys.stderr, level="INFO") + + # If Not specified, set the log file to a datestamp. + if args.log: + logFilename = f"{datetime.datetime.now():%Y-%m-%d.%H-%M-%S.log}" + logger.add(logFilename) + + logger.info("--- Instax Printer Python Client ---") + logger.info("") + myInstax = None + + if args.version == 1: + logger.info("Attempting to print to an Instax SP-1 printer.") + # TODO - Need to find an SP-2 to test with. + elif args.version == 2: + logger.info("Attempting to print to an Instax SP-2 printer.") + myInstax = SP2(ip=args.host, port=args.port, pinCode=args.pin, timeout=args.timeout) + elif args.version == 3: + logger.info("Attempting to print to an Instax SP-3 printer.") + # Warning, this does not work in production yet. + myInstax = SP3(ip=args.host, port=args.port, pinCode=args.pin, timeout=args.timeout) + else: + logger.error("Invalid Instax printer version given") + exit(1) + + if args.preview is True: + # Going to preview the image as it will be printed + logger.info(f"Previewing Image: {args.image}") + instaxImage = InstaxImage(type=args.version) + instaxImage.loadImage(args.image) + instaxImage.convertImage() + instaxImage.previewImage() + logger.info("Preview complete, exiting.") + exit(0) + else: + # Attempt print + logger.info("Connecting to Printer.") + info = myInstax.getPrinterInformation() + printPrinterInfo(info) + + logger.info("Printing Image: %s" % args.image) + # Initialize The Instax Image + instaxImage = InstaxImage(type=args.version) + instaxImage.loadImage(args.image) + instaxImage.convertImage() + # Save a copy of the converted bitmap + # instaxImage.saveImage("test.bmp") + # Preview the image that is about to print + # instaxImage.previewImage() + encodedImage = instaxImage.encodeImage() + myInstax.printPhoto(encodedImage, printProgress) + logger.info("Thank you for using instax-print!") + logger.info( + r""" + \ /\ + ) ( ') + ( / ) + \(___)|""" + ) diff --git a/instax/sp2.py b/instax/sp2.py index b111c12..e8a4dab 100644 --- a/instax/sp2.py +++ b/instax/sp2.py @@ -1,22 +1,33 @@ """Main SP2 Interface Class.""" -from .comms import SocketClientThread, ClientCommand, ClientReply -import time -import queue -import sys import logging -from .exceptions import CommandTimedOutException, ConnectError -from .packet import PacketFactory, Packet, SpecificationsCommand, \ - VersionCommand, PrintCountCommand, ModelNameCommand, PrePrintCommand, \ - PrinterLockCommand, ResetCommand, PrepImageCommand, SendImageCommand, \ - Type83Command, Type195Command, LockStateCommand +import queue +import time + +from instax.comms import ClientCommand, ClientReply, SocketClientThread +from instax.exceptions import CommandTimedOutException, ConnectError +from instax.packet import ( + LockStateCommand, + ModelNameCommand, + Packet, + PacketFactory, + PrepImageCommand, + PrePrintCommand, + PrintCountCommand, + PrinterLockCommand, + ResetCommand, + SendImageCommand, + SpecificationsCommand, + Type83Command, + Type195Command, + VersionCommand, +) class SP2: """SP2 Client interface.""" - def __init__(self, ip='192.168.0.251', port=8080, - timeout=10, pinCode=1111): + def __init__(self, ip="192.168.0.251", port=8080, timeout=10, pinCode=1111): """Initialise the client.""" logging.debug("Initialising Instax SP-2 Class") self.currentTimeMillis = int(round(time.time() * 1000)) @@ -31,21 +42,20 @@ def connect(self): logging.debug("Connecting to Instax SP-2 with timeout of: %s" % self.timeout) self.comms = SocketClientThread() self.comms.start() - self.comms.cmd_q.put(ClientCommand(ClientCommand.CONNECT, - [self.ip, self.port])) + self.comms.cmd_q.put(ClientCommand(ClientCommand.CONNECT, [self.ip, self.port])) # Get current time start = int(time.time()) - while(int(time.time()) < (start + self.timeout)): + while int(time.time()) < (start + self.timeout): try: reply = self.comms.reply_q.get(False) if reply.type == ClientReply.SUCCESS: return else: - raise(ConnectError(reply.data)) + raise (ConnectError(reply.data)) except queue.Empty: time.sleep(0.1) pass - raise(CommandTimedOutException()) + raise (CommandTimedOutException()) def send_and_recieve(self, cmdBytes, timeout): """Send a command and waits for a response. @@ -58,23 +68,22 @@ def send_and_recieve(self, cmdBytes, timeout): # Get current time start = int(time.time()) - while(int(time.time()) < (start + timeout)): + while int(time.time()) < (start + timeout): try: reply = self.comms.reply_q.get(False) if reply.data is not None: if reply.type == ClientReply.SUCCESS: return reply else: - raise(ConnectError(reply.data)) + raise (ConnectError(reply.data)) except queue.Empty: time.sleep(0.1) pass - raise(CommandTimedOutException()) + raise (CommandTimedOutException()) def sendCommand(self, commandPacket): """Send a command packet and returns the response.""" - encodedPacket = commandPacket.encodeCommand(self.currentTimeMillis, - self.pinCode) + encodedPacket = commandPacket.encodeCommand(self.currentTimeMillis, self.pinCode) decodedCommand = self.packetFactory.decode(encodedPacket) decodedCommand.printDebug() reply = self.send_and_recieve(encodedPacket, 5) @@ -108,15 +117,13 @@ def getPrinterSpecifications(self): def sendPrePrintCommand(self, cmdNumber): """Send a PrePrint Command.""" - cmdPacket = PrePrintCommand(Packet.MESSAGE_MODE_COMMAND, - cmdNumber=cmdNumber) + cmdPacket = PrePrintCommand(Packet.MESSAGE_MODE_COMMAND, cmdNumber=cmdNumber) response = self.sendCommand(cmdPacket) return response def sendLockCommand(self, lockState): """Send a Lock State Commmand.""" - cmdPacket = PrinterLockCommand(Packet.MESSAGE_MODE_COMMAND, - lockState=lockState) + cmdPacket = PrinterLockCommand(Packet.MESSAGE_MODE_COMMAND, lockState=lockState) response = self.sendCommand(cmdPacket) return response @@ -128,18 +135,15 @@ def sendResetCommand(self): def sendPrepImageCommand(self, format, options, imgLength): """Send a Prep for Image Command.""" - cmdPacket = PrepImageCommand(Packet.MESSAGE_MODE_COMMAND, - format=format, - options=options, - imgLength=imgLength) + cmdPacket = PrepImageCommand(Packet.MESSAGE_MODE_COMMAND, format=format, options=options, imgLength=imgLength) response = self.sendCommand(cmdPacket) return response def sendSendImageCommand(self, sequenceNumber, payloadBytes): """Send an Image Segment Command.""" - cmdPacket = SendImageCommand(Packet.MESSAGE_MODE_COMMAND, - sequenceNumber=sequenceNumber, - payloadBytes=payloadBytes) + cmdPacket = SendImageCommand( + Packet.MESSAGE_MODE_COMMAND, sequenceNumber=sequenceNumber, payloadBytes=payloadBytes + ) response = self.sendCommand(cmdPacket) return response @@ -167,7 +171,7 @@ def close(self, timeout=10): self.comms.cmd_q.put(ClientCommand(ClientCommand.CLOSE)) # Get current time start = int(time.time()) - while(int(time.time()) < (start + timeout)): + while int(time.time()) < (start + timeout): try: reply = self.comms.reply_q.get(False) if reply.type == ClientReply.SUCCESS: @@ -175,14 +179,13 @@ def close(self, timeout=10): self.comms = None return else: - raise(ConnectError(reply.data)) + raise (ConnectError(reply.data)) except queue.Empty: time.sleep(0.1) pass self.comms.join() self.comms = None - raise(CommandTimedOutException()) - + raise (CommandTimedOutException()) def getPrinterInformation(self): """Primary function to get SP-2 information.""" @@ -192,12 +195,12 @@ def getPrinterInformation(self): printerSpecifications = self.getPrinterSpecifications() printCount = self.getPrintCount() printerInformation = { - 'version': printerVersion.payload, - 'model': printerModel.payload['modelName'], - 'battery': printerVersion.header['battery'], - 'printCount': printerVersion.header['printCount'], - 'specs': printerSpecifications.payload, - 'count': printCount.payload['printHistory'] + "version": printerVersion.payload, + "model": printerModel.payload["modelName"], + "battery": printerVersion.header["battery"], + "printCount": printerVersion.header["printCount"], + "specs": printerSpecifications.payload, + "count": printCount.payload["printHistory"], } self.close() return printerInformation @@ -205,10 +208,10 @@ def getPrinterInformation(self): def printPhoto(self, imageBytes, progress): """Print a Photo to the Printer.""" progressTotal = 100 - progress(0 , progressTotal, status='Connecting to instax Printer. ') + progress(0, progressTotal, status="Connecting to instax Printer. ") # Send Pre Print Commands self.connect() - progress(10, progressTotal, status='Connected! - Sending Pre Print Commands.') + progress(10, progressTotal, status="Connected! - Sending Pre Print Commands.") for x in range(1, 9): self.sendPrePrintCommand(x) self.close() @@ -216,50 +219,50 @@ def printPhoto(self, imageBytes, progress): # Lock The Printer time.sleep(1) self.connect() - progress(20, progressTotal, status='Locking Printer for Print. ') + progress(20, progressTotal, status="Locking Printer for Print. ") self.sendLockCommand(1) self.close() # Reset the Printer time.sleep(1) self.connect() - progress(30, progressTotal, status='Resetting Printer. ') + progress(30, progressTotal, status="Resetting Printer. ") self.sendResetCommand() self.close() # Send the Image time.sleep(1) self.connect() - progress(40, progressTotal, status='About to send Image. ') + progress(40, progressTotal, status="About to send Image. ") self.sendPrepImageCommand(16, 0, 1440000) for segment in range(24): start = segment * 60000 end = start + 60000 segmentBytes = imageBytes[start:end] self.sendSendImageCommand(segment, bytes(segmentBytes)) - progress(40 + segment, progressTotal, status=('Sent image segment %s. ' % segment)) + progress(40 + segment, progressTotal, status=("Sent image segment %s. " % segment)) self.sendT83Command() self.close() - progress(70, progressTotal, status='Image Print Started. ') + progress(70, progressTotal, status="Image Print Started. ") # Send Print State Req time.sleep(1) self.connect() self.sendLockStateCommand() self.getPrinterVersion() self.getPrinterModelName() - progress(90, progressTotal, status='Checking status of print. ') + progress(90, progressTotal, status="Checking status of print. ") printStatus = self.checkPrintStatus(30) if printStatus is True: - progress(100, progressTotal, status='Print is complete! \n') + progress(100, progressTotal, status="Print is complete! \n") else: - progress(100, progressTotal, status='Timed out waiting for print.. \n') + progress(100, progressTotal, status="Timed out waiting for print.. \n") self.close() def checkPrintStatus(self, timeout=30): """Check the status of a print.""" for _ in range(timeout): printStateCmd = self.sendT195Command() - if printStateCmd.header['returnCode'] is Packet.RTN_E_RCV_FRAME: + if printStateCmd.header["returnCode"] is Packet.RTN_E_RCV_FRAME: return True else: time.sleep(1) diff --git a/instax/sp3.py b/instax/sp3.py index 5f6ab7c..c20352d 100644 --- a/instax/sp3.py +++ b/instax/sp3.py @@ -1,22 +1,33 @@ """Main SP3 Interface Class.""" -from .comms import SocketClientThread, ClientCommand, ClientReply -import time -import queue -import sys -from .exceptions import CommandTimedOutException, ConnectError -from .packet import PacketFactory, Packet, SpecificationsCommand, \ - VersionCommand, PrintCountCommand, ModelNameCommand, PrePrintCommand, \ - PrinterLockCommand, ResetCommand, PrepImageCommand, SendImageCommand, \ - Type83Command, Type195Command, LockStateCommand import logging +import queue +import time + +from instax.comms import ClientCommand, ClientReply, SocketClientThread +from instax.exceptions import CommandTimedOutException, ConnectError +from instax.packet import ( + LockStateCommand, + ModelNameCommand, + Packet, + PacketFactory, + PrepImageCommand, + PrePrintCommand, + PrintCountCommand, + PrinterLockCommand, + ResetCommand, + SendImageCommand, + SpecificationsCommand, + Type83Command, + Type195Command, + VersionCommand, +) class SP3: """SP3 Client interface.""" - def __init__(self, ip='192.168.0.251', port=8080, - timeout=10, pinCode=1111): + def __init__(self, ip="192.168.0.251", port=8080, timeout=10, pinCode=1111): """Initialise the client.""" self.currentTimeMillis = int(round(time.time() * 1000)) self.ip = ip @@ -27,24 +38,25 @@ def __init__(self, ip='192.168.0.251', port=8080, def connect(self): """Connect to a printer.""" - logging.info("Connecting to Instax SP-3 with timeout of: %s on: tcp://%s:%d" % (self.timeout, self.ip, self.port)) + logging.info( + "Connecting to Instax SP-3 with timeout of: %s on: tcp://%s:%d" % (self.timeout, self.ip, self.port) + ) self.comms = SocketClientThread() self.comms.start() - self.comms.cmd_q.put(ClientCommand(ClientCommand.CONNECT, - [self.ip, self.port])) + self.comms.cmd_q.put(ClientCommand(ClientCommand.CONNECT, [self.ip, self.port])) # Get current time start = int(time.time()) - while(int(time.time()) < (start + self.timeout)): + while int(time.time()) < (start + self.timeout): try: reply = self.comms.reply_q.get(False) if reply.type == ClientReply.SUCCESS: return else: - raise(ConnectError(reply.data)) + raise (ConnectError(reply.data)) except queue.Empty: time.sleep(0.1) pass - raise(CommandTimedOutException()) + raise (CommandTimedOutException()) def send_and_recieve(self, cmdBytes, timeout): """Send a command and waits for a response. @@ -57,23 +69,22 @@ def send_and_recieve(self, cmdBytes, timeout): # Get current time start = int(time.time()) - while(int(time.time()) < (start + timeout)): + while int(time.time()) < (start + timeout): try: reply = self.comms.reply_q.get(False) if reply.data is not None: if reply.type == ClientReply.SUCCESS: return reply else: - raise(ConnectError(reply.data)) + raise (ConnectError(reply.data)) except queue.Empty: time.sleep(0.1) pass - raise(CommandTimedOutException()) + raise (CommandTimedOutException()) def sendCommand(self, commandPacket): """Send a command packet and returns the response.""" - encodedPacket = commandPacket.encodeCommand(self.currentTimeMillis, - self.pinCode) + encodedPacket = commandPacket.encodeCommand(self.currentTimeMillis, self.pinCode) decodedCommand = self.packetFactory.decode(encodedPacket) decodedCommand.printDebug() reply = self.send_and_recieve(encodedPacket, 5) @@ -107,15 +118,13 @@ def getPrinterSpecifications(self): def sendPrePrintCommand(self, cmdNumber): """Send a PrePrint Command.""" - cmdPacket = PrePrintCommand(Packet.MESSAGE_MODE_COMMAND, - cmdNumber=cmdNumber) + cmdPacket = PrePrintCommand(Packet.MESSAGE_MODE_COMMAND, cmdNumber=cmdNumber) response = self.sendCommand(cmdPacket) return response def sendLockCommand(self, lockState): """Send a Lock State Commmand.""" - cmdPacket = PrinterLockCommand(Packet.MESSAGE_MODE_COMMAND, - lockState=lockState) + cmdPacket = PrinterLockCommand(Packet.MESSAGE_MODE_COMMAND, lockState=lockState) response = self.sendCommand(cmdPacket) return response @@ -127,18 +136,15 @@ def sendResetCommand(self): def sendPrepImageCommand(self, format, options, imgLength): """Send a Prep for Image Command.""" - cmdPacket = PrepImageCommand(Packet.MESSAGE_MODE_COMMAND, - format=format, - options=options, - imgLength=imgLength) + cmdPacket = PrepImageCommand(Packet.MESSAGE_MODE_COMMAND, format=format, options=options, imgLength=imgLength) response = self.sendCommand(cmdPacket) return response def sendSendImageCommand(self, sequenceNumber, payloadBytes): """Send an Image Segment Command.""" - cmdPacket = SendImageCommand(Packet.MESSAGE_MODE_COMMAND, - sequenceNumber=sequenceNumber, - payloadBytes=payloadBytes) + cmdPacket = SendImageCommand( + Packet.MESSAGE_MODE_COMMAND, sequenceNumber=sequenceNumber, payloadBytes=payloadBytes + ) response = self.sendCommand(cmdPacket) return response @@ -166,7 +172,7 @@ def close(self, timeout=10): self.comms.cmd_q.put(ClientCommand(ClientCommand.CLOSE)) # Get current time start = int(time.time()) - while(int(time.time()) < (start + timeout)): + while int(time.time()) < (start + timeout): try: reply = self.comms.reply_q.get(False) if reply.type == ClientReply.SUCCESS: @@ -174,14 +180,13 @@ def close(self, timeout=10): self.comms = None return else: - raise(ConnectError(reply.data)) + raise (ConnectError(reply.data)) except queue.Empty: time.sleep(0.1) pass self.comms.join() self.comms = None - raise(CommandTimedOutException()) - + raise (CommandTimedOutException()) def getPrinterInformation(self): """Primary function to get SP-2 information.""" @@ -191,12 +196,12 @@ def getPrinterInformation(self): printerSpecifications = self.getPrinterSpecifications() printCount = self.getPrintCount() printerInformation = { - 'version': printerVersion.payload, - 'model': printerModel.payload['modelName'], - 'battery': printerVersion.header['battery'], - 'printCount': printerVersion.header['printCount'], - 'specs': printerSpecifications.payload, - 'count': printCount.payload['printHistory'] + "version": printerVersion.payload, + "model": printerModel.payload["modelName"], + "battery": printerVersion.header["battery"], + "printCount": printerVersion.header["printCount"], + "specs": printerSpecifications.payload, + "count": printCount.payload["printHistory"], } self.close() return printerInformation @@ -204,61 +209,61 @@ def getPrinterInformation(self): def printPhoto(self, imageBytes, progress): """Print a Photo to the Printer.""" progressTotal = 100 - progress(0 , progressTotal, status='Connecting to instax Printer. ') + progress(0, progressTotal, status="Connecting to instax Printer. ") # Send Pre Print Commands self.connect() - progress(10, progressTotal, status='Connected! - Sending Pre Print Commands.') + progress(10, progressTotal, status="Connected! - Sending Pre Print Commands.") for x in range(1, 9): resp = self.sendPrePrintCommand(x) self.close() # Lock The Printer time.sleep(1) self.connect() - progress(20, progressTotal, status='Locking Printer for Print. ') + progress(20, progressTotal, status="Locking Printer for Print. ") resp = self.sendLockCommand(1) self.close() # Reset the Printer time.sleep(1) self.connect() - progress(30, progressTotal, status='Resetting Printer. ') + progress(30, progressTotal, status="Resetting Printer. ") resp = self.sendResetCommand() self.close() # Send the Image time.sleep(1) self.connect() - progress(40, progressTotal, status='About to send Image. ') + progress(40, progressTotal, status="About to send Image. ") resp = self.sendPrepImageCommand(16, 0, 1920000) for segment in range(32): start = segment * 60000 end = start + 60000 segmentBytes = imageBytes[start:end] resp = self.sendSendImageCommand(segment, bytes(segmentBytes)) - progress(40 + segment, progressTotal, status=('Sent image segment %s. ' % segment)) + progress(40 + segment, progressTotal, status=("Sent image segment %s. " % segment)) resp = self.sendT83Command() resp.printDebug() self.close() - progress(70, progressTotal, status='Image Print Started. ') + progress(70, progressTotal, status="Image Print Started. ") # Send Print State Req time.sleep(1) self.connect() self.sendLockStateCommand() self.getPrinterVersion() self.getPrinterModelName() - progress(90, progressTotal, status='Checking status of print. ') + progress(90, progressTotal, status="Checking status of print. ") printStatus = self.checkPrintStatus(30) if printStatus is True: - progress(100, progressTotal, status='Print is complete! \n') + progress(100, progressTotal, status="Print is complete! \n") else: - progress(100, progressTotal, status='Timed out waiting for print.. \n') + progress(100, progressTotal, status="Timed out waiting for print.. \n") self.close() def checkPrintStatus(self, timeout=30): """Check the status of a print.""" for _ in range(timeout): printStateCmd = self.sendT195Command() - if printStateCmd.header['returnCode'] is Packet.RTN_E_RCV_FRAME: + if printStateCmd.header["returnCode"] is Packet.RTN_E_RCV_FRAME: return True else: time.sleep(1) diff --git a/instax/tests/replay.json b/instax/tests/replay.json new file mode 100644 index 0000000..06e5e88 --- /dev/null +++ b/instax/tests/replay.json @@ -0,0 +1,250 @@ +[ + { + "bytes": "24c4 0014 63fb 4a6b 0457 0000 0000 0001 fc94 0d0a", + "header": { + "startByte": 36, + "cmdByte": 196, + "packetLength": 20, + "sessionTime": 1677412971, + "password": 1111 + }, + "payload": { + "cmdNumber": 1 + } + }, + { + "bytes": "2ac4 0018 63fb 4a6b 0000 0000 0000 0034 0001 0002 fcaf 0d0a", + "header": { + "startByte": 42, + "cmdByte": 196, + "packetLength": 24, + "sessionTime": 1677412971, + "returnCode": 0, + "unknown1": 0, + "ejecting": 0, + "battery": 3, + "printCount": 4 + }, + "payload": { + "cmdNumber": 1, + "respNumber": 2 + } + }, + { + "bytes": "24c4 0014 63fb 4a6b 0457 0000 0000 0002 fc93 0d0a", + "header": { + "startByte": 36, + "cmdByte": 196, + "packetLength": 20, + "sessionTime": 1677412971, + "password": 1111 + }, + "payload": { + "cmdNumber": 2 + } + }, + { + "bytes": "2ac4 0018 63fb 4a6b 0000 0000 0000 0034 0002 0002 fcae 0d0a", + "header": { + "startByte": 42, + "cmdByte": 196, + "packetLength": 24, + "sessionTime": 1677412971, + "returnCode": 0, + "unknown1": 0, + "ejecting": 0, + "battery": 3, + "printCount": 4 + }, + "payload": { + "cmdNumber": 2, + "respNumber": 2 + } + }, + { + "bytes": "24c4 0014 63fb 4a6b 0457 0000 0000 0003 fc92 0d0a", + "header": { + "startByte": 36, + "cmdByte": 196, + "packetLength": 20, + "sessionTime": 1677412971, + "password": 1111 + }, + "payload": { + "cmdNumber": 3 + } + }, + { + "bytes": "2ac4 0018 63fb 4a6b 0000 0000 0000 0034 0003 0002 fcad 0d0a", + "header": { + "startByte": 42, + "cmdByte": 196, + "packetLength": 24, + "sessionTime": 1677412971, + "returnCode": 0, + "unknown1": 0, + "ejecting": 0, + "battery": 3, + "printCount": 4 + }, + "payload": { + "cmdNumber": 3, + "respNumber": 2 + } + }, + { + "bytes": "24c4 0014 63fb 4a6b 0457 0000 0000 0004 fc91 0d0a", + "header": { + "startByte": 36, + "cmdByte": 196, + "packetLength": 20, + "sessionTime": 1677412971, + "password": 1111 + }, + "payload": { + "cmdNumber": 4 + } + }, + { + "bytes": "2ac4 0018 63fb 4a6b 0000 0000 0000 0034 0004 0001 fcad 0d0a", + "header": { + "startByte": 42, + "cmdByte": 196, + "packetLength": 24, + "sessionTime": 1677412971, + "returnCode": 0, + "unknown1": 0, + "ejecting": 0, + "battery": 3, + "printCount": 4 + }, + "payload": { + "cmdNumber": 4, + "respNumber": 1 + } + }, + { + "bytes": "24c4 0014 63fb 4a6b 0457 0000 0000 0005 fc90 0d0a", + "header": { + "startByte": 36, + "cmdByte": 196, + "packetLength": 20, + "sessionTime": 1677412971, + "password": 1111 + }, + "payload": { + "cmdNumber": 5 + } + }, + { + "bytes": "2ac4 0018 63fb 4a6b 0000 0000 0000 0034 0005 0001 fcac 0d0a", + "header": { + "startByte": 42, + "cmdByte": 196, + "packetLength": 24, + "sessionTime": 1677412971, + "returnCode": 0, + "unknown1": 0, + "ejecting": 0, + "battery": 3, + "printCount": 4 + }, + "payload": { + "cmdNumber": 5, + "respNumber": 1 + } + }, + { + "bytes": "24c4 0014 63fb 4a6b 0457 0000 0000 0006 fc8f 0d0a", + "header": { + "startByte": 36, + "cmdByte": 196, + "packetLength": 20, + "sessionTime": 1677412971, + "password": 1111 + }, + "payload": { + "cmdNumber": 6 + } + }, + { + "bytes": "2ac4 0018 63fb 4a6b 0000 0000 0000 0034 0006 0000 fcac 0d0a", + "header": { + "startByte": 42, + "cmdByte": 196, + "packetLength": 24, + "sessionTime": 1677412971, + "returnCode": 0, + "unknown1": 0, + "ejecting": 0, + "battery": 3, + "printCount": 4 + }, + "payload": { + "cmdNumber": 6, + "respNumber": 0 + } + }, + { + "bytes": "24c4 0014 63fb 4a6b 0457 0000 0000 0007 fc8e 0d0a", + "header": { + "startByte": 36, + "cmdByte": 196, + "packetLength": 20, + "sessionTime": 1677412971, + "password": 1111 + }, + "payload": { + "cmdNumber": 7 + } + }, + { + "bytes": "2ac4 0018 63fb 4a6b 0000 0000 0000 0034 0007 0000 fcab 0d0a", + "header": { + "startByte": 42, + "cmdByte": 196, + "packetLength": 24, + "sessionTime": 1677412971, + "returnCode": 0, + "unknown1": 0, + "ejecting": 0, + "battery": 3, + "printCount": 4 + }, + "payload": { + "cmdNumber": 7, + "respNumber": 0 + } + }, + { + "bytes": "24c4 0014 63fb 4a6b 0457 0000 0000 0008 fc8d 0d0a", + "header": { + "startByte": 36, + "cmdByte": 196, + "packetLength": 20, + "sessionTime": 1677412971, + "password": 1111 + }, + "payload": { + "cmdNumber": 8 + } + }, + { + "bytes": "2ac4 0018 63fb 4a6b 0000 0000 0000 0034 0008 0000 fcaa 0d0a", + "header": { + "startByte": 42, + "cmdByte": 196, + "packetLength": 24, + "sessionTime": 1677412971, + "returnCode": 0, + "unknown1": 0, + "ejecting": 0, + "battery": 3, + "printCount": 4 + }, + "payload": { + "cmdNumber": 8, + "respNumber": 0 + } + } +] diff --git a/instax/tests/test_basic.py b/instax/tests/test_basic.py new file mode 100644 index 0000000..11ce617 --- /dev/null +++ b/instax/tests/test_basic.py @@ -0,0 +1,669 @@ +""" +Instax SP2 Test File. + +@jpwsutton 2016/17 +""" +import time +import unittest + +from instax.packet import ( + LockStateCommand, + ModelNameCommand, + Packet, + PacketFactory, + PrepImageCommand, + PrePrintCommand, + PrintCountCommand, + PrinterLockCommand, + ResetCommand, + SendImageCommand, + SpecificationsCommand, + Type83Command, + Type195Command, + VersionCommand, +) + + +class PacketTests(unittest.TestCase): + """ + Instax-SP2 Packet Test Class. + + A series of tests to verify that all commands and responses can be + correctly encoded and decoded. + """ + + def helper_verify_header( + self, + header, + direction, + type, + length, + time, + pin=None, + returnCode=None, + unknown1=None, + ejecting=None, + battery=None, + printCount=None, + ): + """Verify the Header of a packet.""" + self.assertEqual(header["startByte"], direction) + self.assertEqual(header["cmdByte"], type) + self.assertEqual(header["packetLength"], length) + self.assertEqual(header["sessionTime"], time) + if direction == Packet.MESSAGE_MODE_COMMAND: + self.assertEqual(header["password"], pin) + if direction == Packet.MESSAGE_MODE_RESPONSE: + self.assertEqual(header["returnCode"], returnCode) + # self.assertEqual(header['unknown1'], unknown1) + self.assertEqual(header["ejecting"], ejecting) + self.assertEqual(header["battery"], battery) + self.assertEqual(header["printCount"], printCount) + + def test_encode_cmd_specifications(self): + """Test the process of encoding a spcecifications command.""" + # Create Specifications Command Packet + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdPacket = SpecificationsCommand(Packet.MESSAGE_MODE_COMMAND) + # Encode the command to raw byte array + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + # Decode the command back into a packet object + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_SPECIFICATIONS, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + + def test_encode_resp_specifications(self): + """Test the process of encoding a specifications response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + resPacket = SpecificationsCommand( + Packet.MESSAGE_MODE_RESPONSE, + maxHeight=800, + maxWidth=600, + maxColours=256, + unknown1=10, + maxMsgSize=60000, + unknown2=16, + unknown3=0, + ) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_SPECIFICATIONS, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + # Verify Payload + # print(decodedPacket.payload) + self.assertEqual(decodedPacket.payload["maxHeight"], 800) + self.assertEqual(decodedPacket.payload["maxWidth"], 600) + self.assertEqual(decodedPacket.payload["maxColours"], 256) + self.assertEqual(decodedPacket.payload["unknown1"], 10) + self.assertEqual(decodedPacket.payload["maxMsgSize"], 60000) + self.assertEqual(decodedPacket.payload["unknown2"], 16) + self.assertEqual(decodedPacket.payload["unknown3"], 0) + + def test_encode_cmd_version(self): + """Test the process of encoding a version command.""" + # Create Specifications Command Packet + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdPacket = VersionCommand(Packet.MESSAGE_MODE_COMMAND) + # Encode the command to raw byte array + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + # Decode the command back into a packet object + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_PRINTER_VERSION, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + + def test_encode_resp_version(self): + """Test the process of encoding a version response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + resPacket = VersionCommand(Packet.MESSAGE_MODE_RESPONSE, unknown1=254, firmware=275, hardware=0) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_PRINTER_VERSION, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + # Verify Payload + self.assertEqual(decodedPacket.payload["unknown1"], 254) + self.assertEqual(decodedPacket.payload["firmware"], "01.13") + self.assertEqual(decodedPacket.payload["hardware"], "00.00") + + def test_encode_cmd_printCount(self): + """Test the process of encoding a print count command.""" + # Create Print Count Command Packet + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdPacket = PrintCountCommand(Packet.MESSAGE_MODE_COMMAND) + # Encode the command to raw byte array + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + # Decode the command back into a packet object + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_PRINT_COUNT, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + + def test_encode_resp_printCount(self): + """Test the process of encoding a print count response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + printHistory = 42 + resPacket = PrintCountCommand(Packet.MESSAGE_MODE_RESPONSE, printHistory=printHistory) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_PRINT_COUNT, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + # Verify Payload + self.assertEqual(decodedPacket.payload["printHistory"], printHistory) + + def test_encode_cmd_modelName(self): + """Test the process of encoding a model name command.""" + # Create Model Name Command Packet + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdPacket = ModelNameCommand(Packet.MESSAGE_MODE_COMMAND) + # Encodde the command to raw byte array + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + # Decode the command back into a packet object + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_MODEL_NAME, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + + def test_encode_resp_modelName(self): + """Test the process of encoding a model name response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + modelName = "SP-2" + resPacket = ModelNameCommand(Packet.MESSAGE_MODE_RESPONSE, modelName=modelName) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_MODEL_NAME, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + # Verify Payload + self.assertEqual(decodedPacket.payload["modelName"], modelName) + + def test_encode_cmd_prePrint(self): + """Test the process of encoding a prePrint command.""" + # Create Model Name Command Packet + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdNumber = 8 + cmdPacket = PrePrintCommand(Packet.MESSAGE_MODE_COMMAND, cmdNumber=cmdNumber) + # Encodde the command to raw byte array + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + # Decodee the command back into a packet object + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_PRE_PRINT, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + # Verify Payload + self.assertEqual(decodedPacket.payload["cmdNumber"], cmdNumber) + + def test_encode_resp_prePrint(self): + """Test the process of encoding a pre print response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + cmdNumber = 8 + respNumber = 1 + resPacket = PrePrintCommand(Packet.MESSAGE_MODE_RESPONSE, cmdNumber=cmdNumber, respNumber=respNumber) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_PRE_PRINT, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + # Verify Payload + self.assertEqual(decodedPacket.payload["cmdNumber"], cmdNumber) + self.assertEqual(decodedPacket.payload["respNumber"], respNumber) + + def test_encode_cmd_lock(self): + """Test encoding a Lock Printer Command.""" + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + lockState = 1 + cmdPacket = PrinterLockCommand(Packet.MESSAGE_MODE_COMMAND, lockState=lockState) + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_LOCK_DEVICE, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + # Verify Payload + self.assertEqual(decodedPacket.payload["lockState"], lockState) + + def test_encode_resp_lock(self): + """Test encoding a Lock Printer Response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + resPacket = PrinterLockCommand(Packet.MESSAGE_MODE_RESPONSE) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_LOCK_DEVICE, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + def test_encode_cmd_reset(self): + """Test encoding a Reset Command.""" + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdPacket = ResetCommand(Packet.MESSAGE_MODE_COMMAND) + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_RESET, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + + def test_encode_resp_reset(self): + """Test encoding a Reset Response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + resPacket = ResetCommand(Packet.MESSAGE_MODE_RESPONSE) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_RESET, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + def test_encode_cmd_prep(self): + """Test encoding a Prep Image Command.""" + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + format = 16 + options = 128 + imgLength = 1440000 + cmdPacket = PrepImageCommand(Packet.MESSAGE_MODE_COMMAND, format=format, options=options, imgLength=imgLength) + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_PREP_IMAGE, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + # Verify Payload + self.assertEqual(decodedPacket.payload["format"], format) + self.assertEqual(decodedPacket.payload["options"], options) + self.assertEqual(decodedPacket.payload["imgLength"], imgLength) + + def test_encode_resp_prep(self): + """Test encoding a Prep Image Response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + maxLen = 60000 + resPacket = PrepImageCommand(Packet.MESSAGE_MODE_RESPONSE, maxLen=maxLen) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_PREP_IMAGE, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + # Verify Payload + self.assertEqual(decodedPacket.payload["maxLen"], maxLen) + + def test_encode_cmd_send(self): + """Test encoding a Send Image Command.""" + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + sequenceNumber = 5 + payloadBytes = bytearray(10) + cmdPacket = SendImageCommand( + Packet.MESSAGE_MODE_COMMAND, sequenceNumber=sequenceNumber, payloadBytes=payloadBytes + ) + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_SEND_IMAGE, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + # Verify Payload + self.assertEqual(decodedPacket.payload["sequenceNumber"], sequenceNumber) + self.assertEqual(decodedPacket.payload["payloadBytes"], payloadBytes) + + def test_encode_resp_send(self): + """Test encoding a Send Image Response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + sequenceNumber = 5 + resPacket = SendImageCommand(Packet.MESSAGE_MODE_RESPONSE, sequenceNumber=sequenceNumber) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_SEND_IMAGE, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + # Verify Payload + self.assertEqual(decodedPacket.payload["sequenceNumber"], sequenceNumber) + + def test_encode_cmd_83(self): + """Test encoding a Type 83 Command.""" + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdPacket = Type83Command(Packet.MESSAGE_MODE_COMMAND) + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_83, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + + def test_encode_resp_83(self): + """Test encoding a Type 83 Response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + resPacket = Type83Command(Packet.MESSAGE_MODE_RESPONSE) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_83, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + def test_encode_cmd_195(self): + """Test encoding a Type 195 Command.""" + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdPacket = Type195Command(Packet.MESSAGE_MODE_COMMAND) + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_195, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + + def test_encode_resp_195(self): + """Test encoding a Type 195 Response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + resPacket = Type195Command(Packet.MESSAGE_MODE_RESPONSE) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_195, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + + def test_encode_cmd_lock_state(self): + """Test encoding a lock state Command.""" + sessionTime = int(round(time.time() * 1000)) + pinCode = 1111 + cmdPacket = LockStateCommand(Packet.MESSAGE_MODE_COMMAND) + encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedCommand) + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_COMMAND, + Packet.MESSAGE_TYPE_SET_LOCK_STATE, + len(encodedCommand), + cmdPacket.encodedSessionTime, + pinCode, + ) + + def test_encode_resp_lock_state(self): + """Test encoding a lock state Response.""" + sessionTime = int(round(time.time() * 1000)) + returnCode = Packet.RTN_E_RCV_FRAME + ejecting = 0 + battery = 2 + printCount = 7 + unknownFourByteInt = 100 + resPacket = LockStateCommand(Packet.MESSAGE_MODE_RESPONSE, unknownFourByteInt=unknownFourByteInt) + encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, ejecting, battery, printCount) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(encodedResponse) + # decodedPacket.printDebug() + postHeader = decodedPacket.header + self.helper_verify_header( + postHeader, + Packet.MESSAGE_MODE_RESPONSE, + Packet.MESSAGE_TYPE_SET_LOCK_STATE, + len(encodedResponse), + resPacket.encodedSessionTime, + returnCode=returnCode, + ejecting=ejecting, + battery=battery, + printCount=printCount, + ) + # Verify Payload + self.assertEqual(decodedPacket.payload["unknownFourByteInt"], unknownFourByteInt) + + +if __name__ == "__main__": + + unittest.main() diff --git a/instax/tests/tests_image_encoder.py b/instax/tests/test_image_encoder.py similarity index 73% rename from instax/tests/tests_image_encoder.py rename to instax/tests/test_image_encoder.py index b22c59a..6a7375c 100644 --- a/instax/tests/tests_image_encoder.py +++ b/instax/tests/test_image_encoder.py @@ -4,7 +4,8 @@ @jpwsutton 2016/17 """ import unittest -import instax + +from instax.instaxImage import InstaxImage class ImageTests(unittest.TestCase): @@ -14,13 +15,13 @@ def test_encode_and_decode_image(self): """Test Decoding and then Encoding a premade instax image.""" encodedImageFile = "instax/tests/testEncodedImage.instax" rawInstaxBytes = None - with open(encodedImageFile, 'rb') as infile: + with open(encodedImageFile, "rb") as infile: rawBytes = infile.read() rawInstaxBytes = bytearray(rawBytes) self.assertEqual(len(rawInstaxBytes), 1440000) # Initialize The Instax Image - instaxImage = instax.InstaxImage() + instaxImage = InstaxImage() # Decode the Image from the Instax Byte Array instaxImage.decodeImage(rawInstaxBytes) @@ -31,12 +32,11 @@ def test_encode_and_decode_image(self): self.assertEqual(len(encodedImage), 1440000) for x in range(1440000): - if(rawInstaxBytes[x] != encodedImage[x]): - message = ("Mismatch: Index: %s: %s != %s" % - (x, rawInstaxBytes[x], encodedImage[x])) + if rawInstaxBytes[x] != encodedImage[x]: + message = f"Mismatch: Index: {x}: {rawInstaxBytes[x]} != {encodedImage[x]}" self.fail(message, True) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/instax/tests/test_premade.py b/instax/tests/test_premade.py new file mode 100644 index 0000000..5256b83 --- /dev/null +++ b/instax/tests/test_premade.py @@ -0,0 +1,175 @@ +""" +Instax SP2 Test File. + +@jpwsutton 2016/17 +""" +import unittest + +from instax.packet import Packet, PacketFactory + + +class PacketTests(unittest.TestCase): + """ + Instax-SP2 Premade Packet Test Class. + + A series of tests to verify that existing commands and responses can be + correctly decoded. + """ + + def helper_verify_header( + self, + header, + direction, + type, + length, + time, + pin=None, + returnCode=None, + unknown1=None, + ejecting=None, + battery=None, + printCount=None, + ): + """Verify the Header of a packet.""" + self.assertEqual(header["startByte"], direction) + self.assertEqual(header["cmdByte"], type) + self.assertEqual(header["packetLength"], length) + self.assertEqual(header["sessionTime"], time) + if direction == Packet.MESSAGE_MODE_COMMAND: + self.assertEqual(header["password"], pin) + if direction == Packet.MESSAGE_MODE_RESPONSE: + self.assertEqual(header["returnCode"], returnCode) + # self.assertEqual(header['unknown1'], unknown1) + self.assertEqual(header["ejecting"], ejecting) + self.assertEqual(header["battery"], battery) + self.assertEqual(header["printCount"], printCount) + + def test_premade_resp_specifications(self): + """Test Decoding a Specifications Response with an existing payload.""" + msg = bytearray.fromhex( + "2a4f 0030 e759 eede 0000 0000 0000 0027 0258" + " 0320 0100 000a 0000 0000 ea60 1000 0000 0000" + " 0000 0000 0000 0000 fa41 0d0a" + ) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(msg) + self.assertEqual(decodedPacket.payload["maxHeight"], 800) + self.assertEqual(decodedPacket.payload["maxWidth"], 600) + self.assertEqual(decodedPacket.payload["maxColours"], 256) + self.assertEqual(decodedPacket.payload["unknown1"], 10) + self.assertEqual(decodedPacket.payload["maxMsgSize"], 60000) + self.assertEqual(decodedPacket.payload["unknown2"], 16) + self.assertEqual(decodedPacket.payload["unknown3"], 0) + + def test_premade_resp_version(self): + """Test Decoding a Version Response with an existing payload.""" + msg = bytearray.fromhex("2ac0 001c e759 eede 0000" " 0000 0000 0027 0101 0113" " 0000 0000 fbb0 0d0a") + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(msg) + self.assertEqual(decodedPacket.payload["unknown1"], 257) + self.assertEqual(decodedPacket.payload["firmware"], "01.13") + self.assertEqual(decodedPacket.payload["hardware"], "00.00") + + def test_premade_resp_printCount(self): + """Test Decoding a Print Count Response with an existing payload.""" + msg = bytearray.fromhex( + "2ac1 0024 e759 eede 0000" " 0000 0000 0027 0000 0003" " 00f3 c048 0000 1645 001e" " 0000 f946 0d0a" + ) + packetFactory = PacketFactory() + decodedPacket = packetFactory.decode(msg) + self.assertEqual(decodedPacket.payload["printHistory"], 3) + + def test_premade_cmd_modelName(self): + """Test Decoding a Model Name Command.""" + msg = bytearray.fromhex("24c2 0010 0b8d c2b4 0457 0000 fca0 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_cmd_prePrint(self): + """Test Decoding a Pre Print Command.""" + msg = bytearray.fromhex("24c4 0014 4e40 684c 0457" " 0000 0000 0008 fd5e 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_cmd_lock(self): + """Test Decoding a Lock Command.""" + msg = bytearray.fromhex("24b3 0014 9619 02df 0457" " 0000 0100 0000 fd28 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_resp_lock(self): + """Test Decoding a Lock Response.""" + msg = bytearray.fromhex("2ab3 0014 75b8 bd8e 0000" " 0000 0000 003a fc5c 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_cmd_reset(self): + """Test Decoding a Reset Command.""" + msg = bytearray.fromhex("2450 0010 96c9 aada 0457" " 0000 fc3d 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_resp_reset(self): + """Test Decoding a Reset Response.""" + msg = bytearray.fromhex("2a50 0014 75b8 bd8e 0000" " 0000 0000 003a fcbf 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_resp_prep(self): + """Test Decoding a Prep Response.""" + msg = bytearray.fromhex("2451 001c 9b60 d511 0457" " 0000 1000 0015 f900 0000" " 0000 0000 fc14 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_resp_send(self): + """Test decoding a send response.""" + msg = bytearray.fromhex("2a52 0018 75b8 bd8e 0000" " 0000 0000 003a 0000 0014 fca5 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_cmd_83(self): + """Test decoding a type 83 command.""" + msg = bytearray.fromhex("2453 0010 c9a9 b71e 0457 0000 fcd6 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_resp_83(self): + """Test decoding a type 83 response.""" + msg = bytearray.fromhex("2a53 0014 75b8 bd8e 0000" " 0000 0000 003a fcbc 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_cmd_lock_state(self): + """Test decoding a Lock state command.""" + msg = bytearray.fromhex("24b0 0010 f776 ecbe 0457 0000 fba9 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_resp_lock_state(self): + """Test decoding a Lock state response.""" + msg = bytearray.fromhex("2ab0 0018 f130 d7cc 0000 0000" " 0000 0036 0000 0064 fbaf 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_cmd_195(self): + """Test decoding a 195 command.""" + msg = bytearray.fromhex("24c3 0010 f130 d7cc 0457 0000 fbe9 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_resp_195_first(self): + """Test decoding a 195 first response.""" + msg = bytearray.fromhex("2ac3 0014 f130 d7cc 0000 0000" " 7f00 0436 fb81 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + def test_premade_resp_195_last(self): + """Test decoding a 195 last response.""" + msg = bytearray.fromhex("2ac3 0014 f130 d7cc 0000 0000" " 0000 0035 fc05 0d0a") + packetFactory = PacketFactory() + packetFactory.decode(msg) + + +if __name__ == "__main__": + + unittest.main() diff --git a/tests_replay.py b/instax/tests/test_replay.py similarity index 51% rename from tests_replay.py rename to instax/tests/test_replay.py index 0ebc288..f77cbca 100644 --- a/tests_replay.py +++ b/instax/tests/test_replay.py @@ -3,13 +3,12 @@ @jpwsutton 2016/17 """ -from instax import PacketFactory, Packet, SpecificationsCommand, \ - VersionCommand, PrintCountCommand, ModelNameCommand, PrePrintCommand -import time -import unittest import json +import unittest from pprint import pprint +from instax.packet import Packet, PacketFactory + class PacketTests(unittest.TestCase): """ @@ -19,33 +18,43 @@ class PacketTests(unittest.TestCase): correctly encoded and decoded. """ - def helper_verify_header(self, header, direction, type, length, time, - pin=None, returnCode=None, unknown1=None, - ejecting=None, battery=None, printCount=None): + def helper_verify_header( + self, + header, + direction, + type, + length, + time, + pin=None, + returnCode=None, + unknown1=None, + ejecting=None, + battery=None, + printCount=None, + ): """Verify the Header of a packet.""" - self.assertEqual(header['startByte'], direction) - self.assertEqual(header['cmdByte'], type) - self.assertEqual(header['packetLength'], length) - self.assertEqual(header['sessionTime'], time) + self.assertEqual(header["startByte"], direction) + self.assertEqual(header["cmdByte"], type) + self.assertEqual(header["packetLength"], length) + self.assertEqual(header["sessionTime"], time) if direction == Packet.MESSAGE_MODE_COMMAND: - self.assertEqual(header['password'], pin) + self.assertEqual(header["password"], pin) if direction == Packet.MESSAGE_MODE_RESPONSE: - self.assertEqual(header['returnCode'], returnCode) + self.assertEqual(header["returnCode"], returnCode) # self.assertEqual(header['unknown1'], unknown1) - self.assertEqual(header['ejecting'], ejecting) - self.assertEqual(header['battery'], battery) - self.assertEqual(header['printCount'], printCount) + self.assertEqual(header["ejecting"], ejecting) + self.assertEqual(header["battery"], battery) + self.assertEqual(header["printCount"], printCount) def test_process_log(self): """Import a json log and replay the messages.""" - filename = "priming.json" + filename = "instax/tests/replay.json" json_data = open(filename) data = json.load(json_data) json_data.close() - print() decodedPacketList = [] for packet in data: - readBytes = bytearray.fromhex(packet['bytes']) + readBytes = bytearray.fromhex(packet["bytes"]) packetFactory = PacketFactory() decodedPacket = packetFactory.decode(readBytes) # decodedPacket.printDebug() @@ -53,16 +62,10 @@ def test_process_log(self): decodedPacketList.append(packetObj) pprint(decodedPacketList) - with open('log2.json', 'w') as outfile: + with open("log2.json", "w") as outfile: json.dump(decodedPacketList, outfile, indent=4) - - - - - - -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/instax/tests/tests_socket.py b/instax/tests/test_socket.py similarity index 70% rename from instax/tests/tests_socket.py rename to instax/tests/test_socket.py index c42be57..98711c5 100644 --- a/instax/tests/tests_socket.py +++ b/instax/tests/test_socket.py @@ -3,12 +3,14 @@ James Sutton 2020 """ -from instax import DebugServer, SP2 -import time -import unittest import threading +import unittest + import pytest +from instax.debugServer import DebugServer +from instax.sp2 import SP2 + class SocketTests(unittest.TestCase): """ @@ -16,11 +18,11 @@ class SocketTests(unittest.TestCase): """ @pytest.fixture(autouse=True) - def test_server(self): - server = DebugServer(host='0.0.0.0', port=0) + def debug_server(self): + server = DebugServer(host="0.0.0.0", port=0) self.server_port = server.getPort() print(f"Server running on port {self.server_port}") - + thread = threading.Thread(target=server.start) thread.daemon = True thread.start() @@ -29,11 +31,12 @@ def test_server(self): def test_send_recieve_command(self): sp2 = SP2(ip="0.0.0.0", port=self.server_port) sp2.connect() - model_name = sp2.getPrinterModelName().payload['modelName'] + model_name = sp2.getPrinterModelName().payload["modelName"] print(f"Model name returned was: {model_name}") sp2.close() - self.assertEqual('SP-2', model_name) + self.assertEqual("SP-2", model_name) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/instax/tests/tests_sp2.py b/instax/tests/test_sp2.py similarity index 58% rename from instax/tests/tests_sp2.py rename to instax/tests/test_sp2.py index 96df0f5..ce27b16 100644 --- a/instax/tests/tests_sp2.py +++ b/instax/tests/test_sp2.py @@ -3,12 +3,15 @@ James Sutton 2020 """ -from instax import DebugServer, SP2, InstaxImage -import time -import unittest import threading +import unittest + import pytest +from instax.debugServer import DebugServer +from instax.instaxImage import InstaxImage +from instax.sp2 import SP2 + test_image = "instax/tests/test_image.png" server_batt = 2 server_remain = 10 @@ -16,27 +19,24 @@ progress_log = [] -def updateProgress(count, total, status=''): - progress_log.append({ - 'count': count, - 'total': total, - 'status' : status - }) + +def updateProgress(count, total, status=""): + progress_log.append({"count": count, "total": total, "status": status}) + class SP2Tests(unittest.TestCase): """ Tests on the SP2 class """ - - @pytest.fixture(autouse=True) - def test_server(self): - server = DebugServer(host='0.0.0.0', port=0, version=2, battery=server_batt, - remaining=server_remain, total=server_total) + def debug_server(self): + server = DebugServer( + host="0.0.0.0", port=0, version=2, battery=server_batt, remaining=server_remain, total=server_total + ) self.server_port = server.getPort() print(f"SP-2 Server running on port {self.server_port}") - + thread = threading.Thread(target=server.start) thread.daemon = True thread.start() @@ -46,32 +46,29 @@ def test_get_printer_info(self): # Getting Printer Information sp2 = SP2(ip="0.0.0.0", port=self.server_port) info = sp2.getPrinterInformation() - #print(info) - self.assertEqual(info['model'], 'SP-2') - self.assertEqual(info['battery'], 3) # Something odd here... - self.assertEqual(info['printCount'], 4) # Something odd here... - self.assertEqual(info['count'], server_total) - + # print(info) + self.assertEqual(info["model"], "SP-2") + self.assertEqual(info["battery"], 3) # Something odd here... + self.assertEqual(info["printCount"], 4) # Something odd here... + self.assertEqual(info["count"], server_total) def test_print_photo(self): sp2 = SP2(ip="0.0.0.0", port=self.server_port) - instaxImage = InstaxImage(type=2) instaxImage.loadImage(test_image) instaxImage.convertImage() # Save a copy of the converted bitmap - #instaxImage.saveImage("test.bmp") + # instaxImage.saveImage("test.bmp") # Preview the image that is about to print - #instaxImage.previewImage() + # instaxImage.previewImage() encodedImage = instaxImage.encodeImage() sp2.printPhoto(encodedImage, updateProgress) - #print(progress_log) - self.assertEqual(progress_log[-1]['count'], 100) - self.assertTrue("Print is complete!" in progress_log[-1]['status']) + # print(progress_log) + self.assertEqual(progress_log[-1]["count"], 100) + self.assertTrue("Print is complete!" in progress_log[-1]["status"]) - -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/instax/tests/tests_sp3.py b/instax/tests/test_sp3.py similarity index 56% rename from instax/tests/tests_sp3.py rename to instax/tests/test_sp3.py index 9bc41fa..f0060ef 100644 --- a/instax/tests/tests_sp3.py +++ b/instax/tests/test_sp3.py @@ -3,12 +3,15 @@ James Sutton 2020 """ -from instax import DebugServer, SP3, InstaxImage -import time -import unittest import threading +import unittest + import pytest +from instax.debugServer import DebugServer +from instax.instaxImage import InstaxImage +from instax.sp3 import SP3 + test_image = "instax/tests/test_image.png" server_batt = 2 server_remain = 10 @@ -16,27 +19,24 @@ progress_log = [] -def updateProgress(count, total, status=''): - progress_log.append({ - 'count': count, - 'total': total, - 'status' : status - }) + +def updateProgress(count, total, status=""): + progress_log.append({"count": count, "total": total, "status": status}) + class SP3Tests(unittest.TestCase): """ Tests on the SP2 class """ - - @pytest.fixture(autouse=True) - def test_server(self): - server = DebugServer(host='0.0.0.0', port=0, version=3, battery=server_batt, - remaining=server_remain, total=server_total) + def debug_server(self): + server = DebugServer( + host="0.0.0.0", port=0, version=3, battery=server_batt, remaining=server_remain, total=server_total + ) self.server_port = server.getPort() print(f"SP-2 Server running on port {self.server_port}") - + thread = threading.Thread(target=server.start) thread.daemon = True thread.start() @@ -46,32 +46,29 @@ def test_get_printer_info(self): # Getting Printer Information sp2 = SP3(ip="0.0.0.0", port=self.server_port) info = sp2.getPrinterInformation() - #print(info) - self.assertEqual(info['model'], 'SP-3') - self.assertEqual(info['battery'], 3) # Something odd here... - self.assertEqual(info['printCount'], 4) # Something odd here... - self.assertEqual(info['count'], server_total) - + # print(info) + self.assertEqual(info["model"], "SP-3") + self.assertEqual(info["battery"], 3) # Something odd here... + self.assertEqual(info["printCount"], 4) # Something odd here... + self.assertEqual(info["count"], server_total) def test_print_photo(self): sp2 = SP3(ip="0.0.0.0", port=self.server_port) - - instaxImage = InstaxImage(type=2) + instaxImage = InstaxImage(type=3) instaxImage.loadImage(test_image) instaxImage.convertImage() # Save a copy of the converted bitmap - #instaxImage.saveImage("test.bmp") + # instaxImage.saveImage("test.bmp") # Preview the image that is about to print - #instaxImage.previewImage() + # instaxImage.previewImage() encodedImage = instaxImage.encodeImage() sp2.printPhoto(encodedImage, updateProgress) - #print(progress_log) - self.assertEqual(progress_log[-1]['count'], 100) - self.assertTrue("Print is complete!" in progress_log[-1]['status']) + # print(progress_log) + self.assertEqual(progress_log[-1]["count"], 100) + self.assertTrue("Print is complete!" in progress_log[-1]["status"]) - -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/instax/tests/tests_basic.py b/instax/tests/tests_basic.py deleted file mode 100644 index ab8c4af..0000000 --- a/instax/tests/tests_basic.py +++ /dev/null @@ -1,637 +0,0 @@ -""" -Instax SP2 Test File. - -@jpwsutton 2016/17 -""" -from instax import PacketFactory, Packet, SpecificationsCommand, \ - VersionCommand, PrintCountCommand, ModelNameCommand, PrePrintCommand, \ - PrinterLockCommand, ResetCommand, PrepImageCommand, SendImageCommand, \ - Type83Command, Type195Command, LockStateCommand -import time -import unittest - - -class PacketTests(unittest.TestCase): - """ - Instax-SP2 Packet Test Class. - - A series of tests to verify that all commands and responses can be - correctly encoded and decoded. - """ - - def helper_verify_header(self, header, direction, type, length, time, - pin=None, returnCode=None, unknown1=None, - ejecting=None, battery=None, printCount=None): - """Verify the Header of a packet.""" - self.assertEqual(header['startByte'], direction) - self.assertEqual(header['cmdByte'], type) - self.assertEqual(header['packetLength'], length) - self.assertEqual(header['sessionTime'], time) - if direction == Packet.MESSAGE_MODE_COMMAND: - self.assertEqual(header['password'], pin) - if direction == Packet.MESSAGE_MODE_RESPONSE: - self.assertEqual(header['returnCode'], returnCode) - # self.assertEqual(header['unknown1'], unknown1) - self.assertEqual(header['ejecting'], ejecting) - self.assertEqual(header['battery'], battery) - self.assertEqual(header['printCount'], printCount) - - def test_encode_cmd_specifications(self): - """Test the process of encoding a spcecifications command.""" - # Create Specifications Command Packet - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdPacket = SpecificationsCommand(Packet.MESSAGE_MODE_COMMAND) - # Encode the command to raw byte array - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - # Decode the command back into a packet object - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_SPECIFICATIONS, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - - def test_encode_resp_specifications(self): - """Test the process of encoding a specifications response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - resPacket = SpecificationsCommand(Packet.MESSAGE_MODE_RESPONSE, - maxHeight=800, - maxWidth=600, - maxColours=256, - unknown1=10, - maxMsgSize=60000, - unknown2=16, - unknown3=0) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_SPECIFICATIONS, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - # Verify Payload - # print(decodedPacket.payload) - self.assertEqual(decodedPacket.payload['maxHeight'], 800) - self.assertEqual(decodedPacket.payload['maxWidth'], 600) - self.assertEqual(decodedPacket.payload['maxColours'], 256) - self.assertEqual(decodedPacket.payload['unknown1'], 10) - self.assertEqual(decodedPacket.payload['maxMsgSize'], 60000) - self.assertEqual(decodedPacket.payload['unknown2'], 16) - self.assertEqual(decodedPacket.payload['unknown3'], 0) - - def test_encode_cmd_version(self): - """Test the process of encoding a version command.""" - # Create Specifications Command Packet - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdPacket = VersionCommand(Packet.MESSAGE_MODE_COMMAND) - # Encode the command to raw byte array - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - # Decode the command back into a packet object - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_PRINTER_VERSION, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - - def test_encode_resp_version(self): - """Test the process of encoding a version response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - resPacket = VersionCommand(Packet.MESSAGE_MODE_RESPONSE, - unknown1=254, - firmware=275, - hardware=0) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_PRINTER_VERSION, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - # Verify Payload - self.assertEqual(decodedPacket.payload['unknown1'], 254) - self.assertEqual(decodedPacket.payload['firmware'], '01.13') - self.assertEqual(decodedPacket.payload['hardware'], '00.00') - - def test_encode_cmd_printCount(self): - """Test the process of encoding a print count command.""" - # Create Print Count Command Packet - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdPacket = PrintCountCommand(Packet.MESSAGE_MODE_COMMAND) - # Encode the command to raw byte array - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - # Decode the command back into a packet object - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_PRINT_COUNT, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - - def test_encode_resp_printCount(self): - """Test the process of encoding a print count response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - printHistory = 42 - resPacket = PrintCountCommand(Packet.MESSAGE_MODE_RESPONSE, - printHistory=printHistory) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_PRINT_COUNT, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - # Verify Payload - self.assertEqual(decodedPacket.payload['printHistory'], printHistory) - - def test_encode_cmd_modelName(self): - """Test the process of encoding a model name command.""" - # Create Model Name Command Packet - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdPacket = ModelNameCommand(Packet.MESSAGE_MODE_COMMAND) - # Encodde the command to raw byte array - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - # Decode the command back into a packet object - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_MODEL_NAME, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - - def test_encode_resp_modelName(self): - """Test the process of encoding a model name response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - modelName = 'SP-2' - resPacket = ModelNameCommand(Packet.MESSAGE_MODE_RESPONSE, - modelName=modelName) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_MODEL_NAME, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - # Verify Payload - self.assertEqual(decodedPacket.payload['modelName'], modelName) - - def test_encode_cmd_prePrint(self): - """Test the process of encoding a prePrint command.""" - # Create Model Name Command Packet - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdNumber = 8 - cmdPacket = PrePrintCommand(Packet.MESSAGE_MODE_COMMAND, - cmdNumber=cmdNumber) - # Encodde the command to raw byte array - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - # Decodee the command back into a packet object - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_PRE_PRINT, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - # Verify Payload - self.assertEqual(decodedPacket.payload['cmdNumber'], cmdNumber) - - def test_encode_resp_prePrint(self): - """Test the process of encoding a pre print response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - cmdNumber = 8 - respNumber = 1 - resPacket = PrePrintCommand(Packet.MESSAGE_MODE_RESPONSE, - cmdNumber=cmdNumber, - respNumber=respNumber) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_PRE_PRINT, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - # Verify Payload - self.assertEqual(decodedPacket.payload['cmdNumber'], cmdNumber) - self.assertEqual(decodedPacket.payload['respNumber'], respNumber) - - def test_encode_cmd_lock(self): - """Test encoding a Lock Printer Command.""" - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - lockState = 1 - cmdPacket = PrinterLockCommand(Packet.MESSAGE_MODE_COMMAND, - lockState=lockState) - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_LOCK_DEVICE, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - # Verify Payload - self.assertEqual(decodedPacket.payload['lockState'], lockState) - - def test_encode_resp_lock(self): - """Test encoding a Lock Printer Response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - resPacket = PrinterLockCommand(Packet.MESSAGE_MODE_RESPONSE) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_LOCK_DEVICE, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - def test_encode_cmd_reset(self): - """Test encoding a Reset Command.""" - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdPacket = ResetCommand(Packet.MESSAGE_MODE_COMMAND) - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_RESET, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - - def test_encode_resp_reset(self): - """Test encoding a Reset Response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - resPacket = ResetCommand(Packet.MESSAGE_MODE_RESPONSE) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_RESET, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - def test_encode_cmd_prep(self): - """Test encoding a Prep Image Command.""" - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - format = 16 - options = 128 - imgLength = 1440000 - cmdPacket = PrepImageCommand(Packet.MESSAGE_MODE_COMMAND, - format=format, - options=options, - imgLength=imgLength) - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_PREP_IMAGE, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - # Verify Payload - self.assertEqual(decodedPacket.payload['format'], format) - self.assertEqual(decodedPacket.payload['options'], options) - self.assertEqual(decodedPacket.payload['imgLength'], imgLength) - - def test_encode_resp_prep(self): - """Test encoding a Prep Image Response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - maxLen = 60000 - resPacket = PrepImageCommand(Packet.MESSAGE_MODE_RESPONSE, - maxLen=maxLen) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_PREP_IMAGE, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - # Verify Payload - self.assertEqual(decodedPacket.payload['maxLen'], maxLen) - - def test_encode_cmd_send(self): - """Test encoding a Send Image Command.""" - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - sequenceNumber = 5 - payloadBytes = bytearray(10) - cmdPacket = SendImageCommand(Packet.MESSAGE_MODE_COMMAND, - sequenceNumber=sequenceNumber, - payloadBytes=payloadBytes) - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_SEND_IMAGE, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - # Verify Payload - self.assertEqual(decodedPacket.payload['sequenceNumber'], - sequenceNumber) - self.assertEqual(decodedPacket.payload['payloadBytes'], payloadBytes) - - def test_encode_resp_send(self): - """Test encoding a Send Image Response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - sequenceNumber = 5 - resPacket = SendImageCommand(Packet.MESSAGE_MODE_RESPONSE, - sequenceNumber=sequenceNumber) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_SEND_IMAGE, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - # Verify Payload - self.assertEqual(decodedPacket.payload['sequenceNumber'], - sequenceNumber) - - def test_encode_cmd_83(self): - """Test encoding a Type 83 Command.""" - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdPacket = Type83Command(Packet.MESSAGE_MODE_COMMAND) - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_83, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - - def test_encode_resp_83(self): - """Test encoding a Type 83 Response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - resPacket = Type83Command(Packet.MESSAGE_MODE_RESPONSE) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_83, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - def test_encode_cmd_195(self): - """Test encoding a Type 195 Command.""" - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdPacket = Type195Command(Packet.MESSAGE_MODE_COMMAND) - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_195, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - - def test_encode_resp_195(self): - """Test encoding a Type 195 Response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - resPacket = Type195Command(Packet.MESSAGE_MODE_RESPONSE) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_195, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - - def test_encode_cmd_lock_state(self): - """Test encoding a lock state Command.""" - sessionTime = int(round(time.time() * 1000)) - pinCode = 1111 - cmdPacket = LockStateCommand(Packet.MESSAGE_MODE_COMMAND) - encodedCommand = cmdPacket.encodeCommand(sessionTime, pinCode) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedCommand) - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_COMMAND, - Packet.MESSAGE_TYPE_SET_LOCK_STATE, - len(encodedCommand), - cmdPacket.encodedSessionTime, - pinCode) - - def test_encode_resp_lock_state(self): - """Test encoding a lock state Response.""" - sessionTime = int(round(time.time() * 1000)) - returnCode = Packet.RTN_E_RCV_FRAME - ejecting = 0 - battery = 2 - printCount = 7 - unknownFourByteInt = 100 - resPacket = LockStateCommand(Packet.MESSAGE_MODE_RESPONSE, - unknownFourByteInt=unknownFourByteInt) - encodedResponse = resPacket.encodeResponse(sessionTime, returnCode, - ejecting, battery, - printCount) - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(encodedResponse) - # decodedPacket.printDebug() - postHeader = decodedPacket.header - self.helper_verify_header(postHeader, - Packet.MESSAGE_MODE_RESPONSE, - Packet.MESSAGE_TYPE_SET_LOCK_STATE, - len(encodedResponse), - resPacket.encodedSessionTime, - returnCode=returnCode, - ejecting=ejecting, - battery=battery, - printCount=printCount) - # Verify Payload - self.assertEqual(decodedPacket.payload['unknownFourByteInt'], - unknownFourByteInt) - - -if __name__ == '__main__': - - unittest.main() diff --git a/instax/tests/tests_premade.py b/instax/tests/tests_premade.py deleted file mode 100644 index 5c644f8..0000000 --- a/instax/tests/tests_premade.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -Instax SP2 Test File. - -@jpwsutton 2016/17 -""" -from instax import PacketFactory, Packet -import unittest - - -class PacketTests(unittest.TestCase): - """ - Instax-SP2 Premade Packet Test Class. - - A series of tests to verify that existing commands and responses can be - correctly decoded. - """ - - def helper_verify_header(self, header, direction, type, length, time, - pin=None, returnCode=None, unknown1=None, - ejecting=None, battery=None, printCount=None): - """Verify the Header of a packet.""" - self.assertEqual(header['startByte'], direction) - self.assertEqual(header['cmdByte'], type) - self.assertEqual(header['packetLength'], length) - self.assertEqual(header['sessionTime'], time) - if direction == Packet.MESSAGE_MODE_COMMAND: - self.assertEqual(header['password'], pin) - if direction == Packet.MESSAGE_MODE_RESPONSE: - self.assertEqual(header['returnCode'], returnCode) - # self.assertEqual(header['unknown1'], unknown1) - self.assertEqual(header['ejecting'], ejecting) - self.assertEqual(header['battery'], battery) - self.assertEqual(header['printCount'], printCount) - - def test_premade_resp_specifications(self): - """Test Decoding a Specifications Response with an existing payload.""" - msg = bytearray.fromhex('2a4f 0030 e759 eede 0000 0000 0000 0027 0258' - ' 0320 0100 000a 0000 0000 ea60 1000 0000 0000' - ' 0000 0000 0000 0000 fa41 0d0a') - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(msg) - self.assertEqual(decodedPacket.payload['maxHeight'], 800) - self.assertEqual(decodedPacket.payload['maxWidth'], 600) - self.assertEqual(decodedPacket.payload['maxColours'], 256) - self.assertEqual(decodedPacket.payload['unknown1'], 10) - self.assertEqual(decodedPacket.payload['maxMsgSize'], 60000) - self.assertEqual(decodedPacket.payload['unknown2'], 16) - self.assertEqual(decodedPacket.payload['unknown3'], 0) - - def test_premade_resp_version(self): - """Test Decoding a Version Response with an existing payload.""" - msg = bytearray.fromhex('2ac0 001c e759 eede 0000' - ' 0000 0000 0027 0101 0113' - ' 0000 0000 fbb0 0d0a') - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(msg) - self.assertEqual(decodedPacket.payload['unknown1'], 257) - self.assertEqual(decodedPacket.payload['firmware'], '01.13') - self.assertEqual(decodedPacket.payload['hardware'], '00.00') - - def test_premade_resp_printCount(self): - """Test Decoding a Print Count Response with an existing payload.""" - msg = bytearray.fromhex('2ac1 0024 e759 eede 0000' - ' 0000 0000 0027 0000 0003' - ' 00f3 c048 0000 1645 001e' - ' 0000 f946 0d0a') - packetFactory = PacketFactory() - decodedPacket = packetFactory.decode(msg) - self.assertEqual(decodedPacket.payload['printHistory'], 3) - - def test_premade_cmd_modelName(self): - """Test Decoding a Model Name Command.""" - msg = bytearray.fromhex('24c2 0010 0b8d c2b4 0457 0000 fca0 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_cmd_prePrint(self): - """Test Decoding a Pre Print Command.""" - msg = bytearray.fromhex('24c4 0014 4e40 684c 0457' - ' 0000 0000 0008 fd5e 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_cmd_lock(self): - """Test Decoding a Lock Command.""" - msg = bytearray.fromhex('24b3 0014 9619 02df 0457' - ' 0000 0100 0000 fd28 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_resp_lock(self): - """Test Decoding a Lock Response.""" - msg = bytearray.fromhex('2ab3 0014 75b8 bd8e 0000' - ' 0000 0000 003a fc5c 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_cmd_reset(self): - """Test Decoding a Reset Command.""" - msg = bytearray.fromhex('2450 0010 96c9 aada 0457' - ' 0000 fc3d 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_resp_reset(self): - """Test Decoding a Reset Response.""" - msg = bytearray.fromhex('2a50 0014 75b8 bd8e 0000' - ' 0000 0000 003a fcbf 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_resp_prep(self): - """Test Decoding a Prep Response.""" - msg = bytearray.fromhex('2451 001c 9b60 d511 0457' - ' 0000 1000 0015 f900 0000' - ' 0000 0000 fc14 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_resp_send(self): - """Test decoding a send response.""" - msg = bytearray.fromhex('2a52 0018 75b8 bd8e 0000' - ' 0000 0000 003a 0000 0014 fca5 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_cmd_83(self): - """Test decoding a type 83 command.""" - msg = bytearray.fromhex('2453 0010 c9a9 b71e 0457 0000 fcd6 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_resp_83(self): - """Test decoding a type 83 response.""" - msg = bytearray.fromhex('2a53 0014 75b8 bd8e 0000' - ' 0000 0000 003a fcbc 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_cmd_lock_state(self): - """Test decoding a Lock state command.""" - msg = bytearray.fromhex('24b0 0010 f776 ecbe 0457 0000 fba9 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_resp_lock_state(self): - """Test decoding a Lock state response.""" - msg = bytearray.fromhex('2ab0 0018 f130 d7cc 0000 0000' - ' 0000 0036 0000 0064 fbaf 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_cmd_195(self): - """Test decoding a 195 command.""" - msg = bytearray.fromhex('24c3 0010 f130 d7cc 0457 0000 fbe9 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_resp_195_first(self): - """Test decoding a 195 first response.""" - msg = bytearray.fromhex('2ac3 0014 f130 d7cc 0000 0000' - ' 7f00 0436 fb81 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - def test_premade_resp_195_last(self): - """Test decoding a 195 last response.""" - msg = bytearray.fromhex('2ac3 0014 f130 d7cc 0000 0000' - ' 0000 0035 fc05 0d0a') - packetFactory = PacketFactory() - packetFactory.decode(msg) - - -if __name__ == '__main__': - - unittest.main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..471f225 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,759 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "arrow" +version = "1.2.1" +description = "Better dates & times for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "arrow-1.2.1-py3-none-any.whl", hash = "sha256:6b2914ef3997d1fd7b37a71ce9dd61a6e329d09e1c7b44f4d3099ca4a5c0933e"}, + {file = "arrow-1.2.1.tar.gz", hash = "sha256:c2dde3c382d9f7e6922ce636bf0b318a7a853df40ecb383b29192e6c5cc82840"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "coveralls" +version = "3.3.1" +description = "Show coverage stats online via coveralls.io" +category = "dev" +optional = false +python-versions = ">= 3.5" +files = [ + {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, + {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, +] + +[package.dependencies] +coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" +docopt = ">=0.6.1" +requests = ">=1.0.0" + +[package.extras] +yaml = ["PyYAML (>=3.10)"] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] + +[[package]] +name = "filelock" +version = "3.9.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, + {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, +] + +[package.extras] +docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "gitlint" +version = "0.17.0" +description = "Git commit message linter written in python, checks your commit messages for style." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "gitlint-0.17.0-py2.py3-none-any.whl", hash = "sha256:46469d5db3f3bca72fa946c159d0733dc8c75211309477676295cf2d80d177b4"}, + {file = "gitlint-0.17.0.tar.gz", hash = "sha256:8c10c6b404d255b43ddc4a2f5f13bcb10284bc162adfb2c03b10708309009189"}, +] + +[package.dependencies] +gitlint-core = {version = "0.17.0", extras = ["trusted-deps"]} + +[[package]] +name = "gitlint-core" +version = "0.17.0" +description = "Git commit message linter written in python, checks your commit messages for style." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "gitlint-core-0.17.0.tar.gz", hash = "sha256:772dfd33effaa8515ca73e901466aa938c19ced894bec6783d19691f57429691"}, + {file = "gitlint_core-0.17.0-py2.py3-none-any.whl", hash = "sha256:cb99ccd736a698b910385211203bda94bf4ce29086d0c08f8f58a18c40a98377"}, +] + +[package.dependencies] +arrow = [ + {version = ">=1"}, + {version = "1.2.1", optional = true, markers = "extra == \"trusted-deps\""}, +] +Click = [ + {version = ">=8"}, + {version = "8.0.3", optional = true, markers = "extra == \"trusted-deps\""}, +] +sh = [ + {version = ">=1.13.0", markers = "sys_platform != \"win32\""}, + {version = "1.14.2", optional = true, markers = "sys_platform != \"win32\""}, +] + +[package.extras] +trusted-deps = ["Click (==8.0.3)", "arrow (==1.2.1)", "sh (==1.14.2)"] + +[[package]] +name = "identify" +version = "2.5.11" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.11-py2.py3-none-any.whl", hash = "sha256:e7db36b772b188099616aaf2accbee122949d1c6a1bac4f38196720d6f9f06db"}, + {file = "identify-2.5.11.tar.gz", hash = "sha256:14b7076b29c99b1b0b8b08e96d448c7b877a9b07683cd8cfda2ea06af85ffa1c"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] + +[[package]] +name = "loguru" +version = "0.6.0" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"}, + {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)"] + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "packaging" +version = "22.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, + {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, +] + +[[package]] +name = "pillow" +version = "9.3.0" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, + {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, + {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, + {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, + {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, + {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, + {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, + {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, + {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, + {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, + {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, + {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, + {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, + {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, + {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, + {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, + {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, + {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, + {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, + {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, + {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, + {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, + {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, + {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, + {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, + {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "platformdirs" +version = "2.6.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, +] + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, + {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, +] + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, + {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "sh" +version = "1.14.2" +description = "Python subprocess replacement" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "sh-1.14.2-py2.py3-none-any.whl", hash = "sha256:4921ac9c1a77ec8084bdfaf152fe14138e2b3557cc740002c1a97076321fce8a"}, + {file = "sh-1.14.2.tar.gz", hash = "sha256:9d7bd0334d494b2a4609fe521b2107438cdb21c0e469ffeeb191489883d6fe0d"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "urllib3" +version = "1.26.13" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.17.1" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, + {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, + {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, +] + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "e552654c9ba108f9319051e7b371a09b38aaa33b3c38e388b9b3158a459ec146" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fd40597 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "instax-api" +version = "0.8.0" +description = "A Python module and app to print photos on the Fujifim Instax Printers" +authors = ["James Sutton"] +license = "MIT" +readme = "README.md" +packages = [{include = "instax"}] + +[tool.poetry.dependencies] +python = "^3.10" +Pillow = "^9.3.0" +loguru = "^0.6.0" + +[tool.poetry.group.dev.dependencies] +coveralls = "^3.3.1" +gitlint = "0.17.0" +pre-commit = "2.20.0" +pytest = "7.1.2" +pytest-cov = "3.0.0" + + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 6b7aa25..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -Pillow==8.2.0 -pycodestyle==2.3.1 -pytest==3.2.1 -pytest-cache==1.0 -pytest-cov==2.5.1 -pytest-pep8==1.0.6 -coveralls==2.0.0 diff --git a/setup.py b/setup.py index 9720e25..e9ec55b 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,23 @@ -import sys -from setuptools import setup +from pathlib import Path +from setuptools import setup -with open('README.md', 'rb') as readme_file: - readme = readme_file.read().decode('utf-8') +this_directory = Path(__file__).parent +long_description = (this_directory / "README.md").read_text() -setup(name='instax_api', - version='0.7', - description='Fujifilm Instax SP2 & SP3 Library and CLI Utility', - long_description=readme, - url='https://github.com/jpwsutton/instax_api', - author='James Sutton', - author_email='james@jsutton.co.uk', - license='MIT', - keywords='instax', - packages=['instax'], - install_requires=[ - 'Pillow', - ], - scripts=['bin/instax-print'], - zip_safe=False) +setup( + name="instax_api", + version="0.8.0", + description="Fujifilm Instax SP2 & SP3 Library and CLI Utility", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/jpwsutton/instax_api", + author="James Sutton", + author_email="james@jsutton.co.uk", + license="MIT", + keywords="instax", + packages=["instax"], + install_requires=[ + "Pillow", + ], +) diff --git a/testServer.py b/testServer.py deleted file mode 100755 index a1b9556..0000000 --- a/testServer.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 -"""Instax SP-2 Test Server Wrapper. - -Author: James Sutton 2017 - jsutton.co.uk - -This wrapper can be used to start a test server implementation. -You can configure a number of useful parameters to use whist the server is -running. -Parameters: - - Verbose (Default False) - - JSON Log File (Default ddmmyy-hhmmss.json) - - Port (Default 8080) - - Photo Destination Directory: (Default: images) - - Battery Level: (Default 100%) - - Prints Remaining: (Default 10) - - Total Prints in History: (Default 20) - -""" -import argparse -import datetime -import logging -import instax - - -print("---------- Instax SP-2 Test Server ---------- ") - - -def remaining_type(x): - """Validate Remaining count is between 0 and 10.""" - x = int(x) - if x < 10 and x >= 0: - raise argparse.ArgumentTypeError("Remaining must be between 0 and 10.") - return x - - -parser = argparse.ArgumentParser() -parser.add_argument("-v", "--verbose", action="store_true", default=False, - help="Print Verbose log messages to console.") -parser.add_argument("-D", "--debug", action="store_true", default=False, - help="Logs extra debug data to log.") -parser.add_argument("-l", "--log", action="store_true", default=False, - help="Log information to log file ddmmyy-hhmmss-server.log") -parser.add_argument("-o", "--host", default='0.0.0.0', - help="The Host IP to expose the server on.") -parser.add_argument("-p", "--port", type=int, default=8080, - help="The port to expose the server on.") -parser.add_argument("-d", "--dest", default="images", - help="The Directory to save incoming photos," - "default: 'images'") -parser.add_argument("-b", "--battery", type=int, choices=range(0, 4), - default=2, help="The Battery level of the printer" - " 0-4, default: 2") -parser.add_argument("-r", "--remaining", type=remaining_type, default=10, - help="The number of remaining prints 0-10, default: 10") -parser.add_argument("-t", "--total", type=int, default=20, - help="The total number of prints in the printers lifetime" - ", default 20") -parser.add_argument("-V", "--version", type=int, default=2, - help="The Instax SP-* version, 2 or 3, default is 2") -args = parser.parse_args() - -logLevel = logging.INFO -if args.debug: - logLevel = logging.DEBUG - -logger = logging.getLogger('instax_server') -logger.setLevel(logLevel) - -# Create Log Formatter -formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s') - -# Create Console Handler -ch = logging.StreamHandler() -ch.setLevel(logLevel) -ch.setFormatter(formatter) -logger.addHandler(ch) - -# If Not specified, set the log file to a datestamp. -if args.log: - logFilename = '{0:%Y-%m-%d.%H:%M:%S-server.log}'.format(datetime.datetime.now()) - fh = logging.FileHandler(logFilename) - fh.setLevel(logLevel) - fh.setFormatter(formatter) - logger.addHandler(fh) - - -testServer = instax.DebugServer(host=args.host, - port=args.port, dest=args.dest, - battery=args.battery, remaining=args.remaining, - total=args.total, version=args.version) -testServer.start() From 1197ac03fbde5e97a172ee9e28743009720ef046 Mon Sep 17 00:00:00 2001 From: James Sutton Date: Fri, 30 Dec 2022 21:45:32 +0000 Subject: [PATCH 2/7] feat: adding coveralls Signed-off-by: James Sutton --- .github/workflows/python-test.yml | 7 ++++++- poetry.lock | 18 +++++++++--------- pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 9f1d89f..4e07af3 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -61,5 +61,10 @@ jobs: - name: Run tests run: | source .venv/bin/activate - pytest instax --cov + coverage run --source instax -m pytest instax --doctest-modules coverage report + + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/poetry.lock b/poetry.lock index 471f225..1c28c34 100644 --- a/poetry.lock +++ b/poetry.lock @@ -49,7 +49,7 @@ tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -73,7 +73,7 @@ files = [ name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" +category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -115,7 +115,7 @@ files = [ name = "coverage" version = "6.5.0" description = "Code coverage measurement for Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -181,7 +181,7 @@ toml = ["tomli"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" -category = "dev" +category = "main" optional = false python-versions = ">= 3.5" files = [ @@ -213,7 +213,7 @@ files = [ name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "dev" +category = "main" optional = false python-versions = "*" files = [ @@ -299,7 +299,7 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -617,7 +617,7 @@ files = [ name = "requests" version = "2.28.1" description = "Python HTTP for Humans." -category = "dev" +category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -704,7 +704,7 @@ files = [ name = "urllib3" version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -756,4 +756,4 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "e552654c9ba108f9319051e7b371a09b38aaa33b3c38e388b9b3158a459ec146" +content-hash = "1b8addf7d83faccfc1e6be0f4a791063c5d3e69aa4a2f88309324f3daa2e6db8" diff --git a/pyproject.toml b/pyproject.toml index fd40597..3367b4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ python = "^3.10" Pillow = "^9.3.0" loguru = "^0.6.0" + [tool.poetry.group.dev.dependencies] coveralls = "^3.3.1" gitlint = "0.17.0" @@ -20,7 +21,6 @@ pytest = "7.1.2" pytest-cov = "3.0.0" - [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From 1535aeb21156345f168fa951506c0fcae8f5acf4 Mon Sep 17 00:00:00 2001 From: James Sutton Date: Fri, 30 Dec 2022 21:49:54 +0000 Subject: [PATCH 3/7] fix: adding lcov output Signed-off-by: James Sutton --- .github/workflows/python-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 4e07af3..b0bba0d 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -61,7 +61,7 @@ jobs: - name: Run tests run: | source .venv/bin/activate - coverage run --source instax -m pytest instax --doctest-modules + coverage run --source instax -m pytest instax --doctest-modules --cov-report lcov coverage report - name: Coveralls From ad6e97f336eb09d0e5bac9b467224559bde606f4 Mon Sep 17 00:00:00 2001 From: James Sutton Date: Fri, 30 Dec 2022 22:11:54 +0000 Subject: [PATCH 4/7] fix: upgrading pytest-cov and fixing command Signed-off-by: James Sutton --- .github/workflows/python-test.yml | 2 +- poetry.lock | 106 ++++++++++++------------------ pyproject.toml | 9 +-- 3 files changed, 48 insertions(+), 69 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index b0bba0d..40506f3 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -61,7 +61,7 @@ jobs: - name: Run tests run: | source .venv/bin/activate - coverage run --source instax -m pytest instax --doctest-modules --cov-report lcov + pytest --cov-report=lcov:coverage/lcov.info --cov=instax coverage report - name: Coveralls diff --git a/poetry.lock b/poetry.lock index 1c28c34..2106b4f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,22 +15,11 @@ files = [ [package.dependencies] python-dateutil = ">=2.7.0" -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - [[package]] name = "attrs" version = "22.2.0" description = "Classes Without Boilerplate" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -49,7 +38,7 @@ tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -73,7 +62,7 @@ files = [ name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" +category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -181,7 +170,7 @@ toml = ["tomli"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" -category = "main" +category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -213,13 +202,28 @@ files = [ name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "main" +category = "dev" optional = false python-versions = "*" files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] +[[package]] +name = "exceptiongroup" +version = "1.1.0" +description = "Backport of PEP 654 (exception groups)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, + {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" version = "3.9.0" @@ -299,7 +303,7 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -311,7 +315,7 @@ files = [ name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" +category = "main" optional = false python-versions = "*" files = [ @@ -357,7 +361,7 @@ setuptools = "*" name = "packaging" version = "22.0" description = "Core utilities for Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -460,7 +464,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest- name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -474,14 +478,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.20.0" +version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, - {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, ] [package.dependencies] @@ -489,56 +493,42 @@ cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] +virtualenv = ">=20.10.0" [[package]] name = "pytest" -version = "7.1.2" +version = "7.2.0" description = "pytest: simple powerful testing with Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, - {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, ] [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-cov" -version = "3.0.0" +version = "4.0.0" description = "Pytest plugin for measuring coverage." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] [package.dependencies] @@ -617,7 +607,7 @@ files = [ name = "requests" version = "2.28.1" description = "Python HTTP for Humans." -category = "main" +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -676,23 +666,11 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -704,7 +682,7 @@ files = [ name = "urllib3" version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -756,4 +734,4 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "1b8addf7d83faccfc1e6be0f4a791063c5d3e69aa4a2f88309324f3daa2e6db8" +content-hash = "d4252e54a7c0bb3c43cf86d713633e55b77cc579737645f7eb11f785c070e560" diff --git a/pyproject.toml b/pyproject.toml index 3367b4b..b90e27b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,14 +11,15 @@ packages = [{include = "instax"}] python = "^3.10" Pillow = "^9.3.0" loguru = "^0.6.0" +pytest-cov = "4.0.0" [tool.poetry.group.dev.dependencies] coveralls = "^3.3.1" -gitlint = "0.17.0" -pre-commit = "2.20.0" -pytest = "7.1.2" -pytest-cov = "3.0.0" +gitlint = "^0.17.0" +pre-commit = "^2.20.0" +pytest = "^7.2.0" +pytest-cov = "^4.0.0" [build-system] From 662749066adfa9364ffa5818cd63dce4e66f7262 Mon Sep 17 00:00:00 2001 From: James Sutton Date: Sat, 31 Dec 2022 11:49:59 +0000 Subject: [PATCH 5/7] feat: adding updated instructions to readme and development files Signed-off-by: James Sutton --- .github/workflows/python-test.yml | 9 +++++++++ README.md | 16 +++++++++------- development.md | 18 +++++++++--------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 40506f3..0a920c3 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -1,3 +1,5 @@ +name: "Build, Lint and Test" + on: push: branches: [main] @@ -55,6 +57,13 @@ jobs: #---------------------------------------------- - name: Install project run: poetry install --no-interaction + + #---------------------------------------------- + # lint project + #---------------------------------------------- + - name: Lint Project + run: pre-commit run --all-files + #---------------------------------------------- # run test suite #---------------------------------------------- diff --git a/README.md b/README.md index 1457eb2..af5aad3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # instax_api - -[![Build Status](https://img.shields.io/travis/jpwsutton/instax_api/master.svg)](https://travis-ci.org/jpwsutton/instax_api) +[![.github/workflows/python-test.yml](https://github.com/jpwsutton/instax_api/actions/workflows/python-test.yml/badge.svg)](https://github.com/jpwsutton/instax_api/actions/workflows/python-test.yml) [![Coverage Status](https://img.shields.io/coveralls/jpwsutton/instax_api/master.svg)](https://coveralls.io/github/jpwsutton/instax_api?branch=master) This is a Python Module to interact and print photos to the Fujifilm Instax SP-2 and SP-3 printers. @@ -14,10 +13,13 @@ In order to use this library, you will need to be using Python 3 pip3 install instax-api ``` + ## Usage +**note** - From version 0.7.0 to 0.8.0, I moved away from adding a script to just calling the module from pyton using the `-m` argument. + ``` -$ instax-print --help +$ python3 -m instax.print --help usage: instax-print [-h] [-i PIN] [-v {1,2,3}] image positional arguments: @@ -33,16 +35,16 @@ optional arguments: ### Examples: - - Printing a Photo to an SP-2 printer: `instax-print myPhoto.jpg` - - Printing a Photo to an SP-3 printer: `instax-print myPhoto.jpg -v 3` - - Printing a Photo to a printer with a pin that is not the default (1111) `instax-print myPhoto.jpg -i 1234` + - Printing a Photo to an SP-2 printer: `python3 -m instax.print myPhoto.jpg` + - Printing a Photo to an SP-3 printer: `python3 -m instax.print myPhoto.jpg -v 3` + - Printing a Photo to a printer with a pin that is not the default (1111) `python3 -m instax.print myPhoto.jpg -i 1234` ### Hints and tips: - Make sure you are connected to the correct wifi network, once the printer is turned on, there will be an SSID / WiFi network available that starts with `INSTAX-` followed by 8 numbers. You'll need to connect to this. - If you have a static IP address set up on your computer, you'll need to turn on DHCP before attempting to print, the Instax printer will automatically assign you a new address once you connect. - Some Unix based operating systems may require you to use sudo in order to access the network. - The printer will automatically turn itself off after roughly 10 minutes of innactivity. -- The instax-print utility will attempt to automatically rotate the image so that it either is correctly printed in portrait, or landscape with the thick bottom edge of the print on the left. If you wish to print your photos in a specific orientation that differs from this, then it's reccomended that you orient your photo in a tool like GIMP first, then strip out the rotation metadata. Once the rotation metadata has been stripped, the photo will need to be in a portrait orientation relative to the finished print (e.g. thick edge at the bottom). +- The instax.print utility will attempt to automatically rotate the image so that it either is correctly printed in portrait, or landscape with the thick bottom edge of the print on the left. If you wish to print your photos in a specific orientation that differs from this, then it's reccomended that you orient your photo in a tool like GIMP first, then strip out the rotation metadata. Once the rotation metadata has been stripped, the photo will need to be in a portrait orientation relative to the finished print (e.g. thick edge at the bottom). ## Install Manually diff --git a/development.md b/development.md index 422c69b..8c4fa5d 100644 --- a/development.md +++ b/development.md @@ -1,13 +1,13 @@ # Development Guide -## Developing using the Virtualenv environment +## Developing using Poetry -* Make sure you have virtualenv installed : `pip3 install virtualenv`. -* If you haven't created it yet, create the virtual environment: `virtualenv env -p python3`. -* Activate the environment: `source env/bin/activate`. -* Install packages from requirements.txt: `pip3 install -r requirements.txt --upgrade`. -* When you are finished, just use `deactivate` to end your session. +* Install poetry on your development machine: https://python-poetry.org/docs/ +* Run `poetry shell` to launch the virtual environment. +* Run `poetry install` to download all main and dev dependencies. +* Run `pre-commit install` and then `pre-commit install --hook-type commit-msg` to set up the pre-commit checks. +* When you are finished, just use `exit` to end your session. ## Running the Test Server @@ -22,7 +22,7 @@ sudo hostapd /etc/hostapd/hostapd.conf (This will keep running so do it in anoth sudo ifconfig wlan0 192.168.0.251 ``` -Then run: `./bin/debugServer.py` +Then run: `python3 -m instax.debugServer` ## Hidden options in instax-print CLI client In order to make debugging and using the test server easier, there are some hidden options in the instax-print application that you can use, these include: @@ -36,12 +36,12 @@ In order to make debugging and using the test server easier, there are some hidd The timeout to use when communicating. ``` -For example, when using the test server, you can use the following command to print: `instax-print myImage.jpg -o localhost -l` +For example, when using the test server, you can use the following command to print: `python3 -m instax.print myImage.jpg -o localhost -l` The `-d / --debug` option will print a lot more data to the log file, specifically detailed dumps of every command / response sent or received by the client. This is handy when trying to identify issues in the packet library. ## Running tests -Simply run the command: `py.test instax/tests/*.py` +Simply run the command: `python3 -m pytest instax` ## Inspecting Packets in Wireshark From bc650e95a7c69148c46f7eed5640bdbe4d91e1de Mon Sep 17 00:00:00 2001 From: James Sutton Date: Sat, 31 Dec 2022 11:52:42 +0000 Subject: [PATCH 6/7] fix: just use the readymade precommit action Signed-off-by: James Sutton --- .github/workflows/python-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 0a920c3..428fb04 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -62,7 +62,7 @@ jobs: # lint project #---------------------------------------------- - name: Lint Project - run: pre-commit run --all-files + uses: pre-commit/action@v3.0.0 #---------------------------------------------- # run test suite From b8da01e8d3675ef1f6e69b55554376758d2300bc Mon Sep 17 00:00:00 2001 From: James Sutton Date: Sat, 31 Dec 2022 16:05:55 +0000 Subject: [PATCH 7/7] feat: adding publish workflow Signed-off-by: James Sutton --- .github/workflows/python-publish.yml | 72 ++++++++++++++++++++++++++++ pyproject.toml | 2 - 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..08141d9 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,72 @@ +name: Upload Instax Python Package + + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + environment: publish + + steps: + #---------------------------------------------- + # check-out repo and set-up python + #---------------------------------------------- + - name: Check out repository + uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + #---------------------------------------------- + # ----- install & configure poetry ----- + #---------------------------------------------- + - name: Cache poetry install + uses: actions/cache@v3 + with: + path: ~/.local # the path depends on OS + key: poetry-0 # increment to reset cache + - name: Install poetry + if: steps.cached-poetry.outputs.cache-hit != 'true' + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + #---------------------------------------------- + # load cached venv if cache exists + #---------------------------------------------- + - name: Load Cached venv + id: cached-poetry-dependencies + uses: actions/cache@v3 + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version}}-${{ hashFiles('**/poetry.lock') }} + + #---------------------------------------------- + # install dependencies if cache does not exist + #---------------------------------------------- + - name: Install dependencies + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-root + #---------------------------------------------- + # Build Distributables + #---------------------------------------------- + - name: Install project + run: poetry build + + #---------------------------------------------- + # build and publish to pypi + #---------------------------------------------- + - name: Publish project + run: poetry publish diff --git a/pyproject.toml b/pyproject.toml index b90e27b..db8ed86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,11 +11,9 @@ packages = [{include = "instax"}] python = "^3.10" Pillow = "^9.3.0" loguru = "^0.6.0" -pytest-cov = "4.0.0" [tool.poetry.group.dev.dependencies] -coveralls = "^3.3.1" gitlint = "^0.17.0" pre-commit = "^2.20.0" pytest = "^7.2.0"