Skip to content

Commit

Permalink
first draft SP1 support
Browse files Browse the repository at this point in the history
  • Loading branch information
vitorio committed Nov 18, 2018
1 parent d5572c7 commit 36f9bcd
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 11 deletions.
2 changes: 2 additions & 0 deletions bin/instax-print
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ if __name__ == "__main__":

if args.version is 1:
logging.info("Attempting to print to an Instax SP-1 printer.")
myInstax = instax.SP1(ip=args.host, port=args.port, pinCode=args.pin,
timeout=args.timeout)
# 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.")
Expand Down
3 changes: 2 additions & 1 deletion instax/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .sp1 import SP1
from .sp2 import SP2
from .sp3 import SP3
from .testServer import TestServer
Expand All @@ -6,7 +7,7 @@
VersionCommand, PrintCountCommand, PrePrintCommand, \
ModelNameCommand, PrinterLockCommand, SendImageCommand, \
ResetCommand, PrepImageCommand, Type83Command, \
Type195Command, LockStateCommand
Type195Command, LockStateCommand, ShadingCommand


version = "0.5"
6 changes: 3 additions & 3 deletions instax/instaxImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class InstaxImage:
"""Image Utilities class."""

dimensions = {
1 : (600, 800),
1 : (480, 640),
2 : (600, 800),
3 : (800, 800)
}
Expand Down Expand Up @@ -58,7 +58,7 @@ def encodeImage(self):
return encodedBytes

def decodeImage(self, imageBytes):
"""Decode the byte array into an image."""
"""Decode the type=2 or type=3 byte array into an image."""
targetImg = []
# Packing the individual colours back together.
for h in range(self.printHeight):
Expand Down Expand Up @@ -98,7 +98,7 @@ def convertImage(self, crop_type='middle',
img = crop_rectangle(rotatedImage, maxSize, crop_type)

# Stip away any exif data.
newImage = Image.new(img.mode, img.size)
newImage = Image.new('RGBA', img.size)
newImage.putdata(img.getdata())
self.myImage = pure_pil_alpha_to_color_v2(newImage, (255,255,255))

Expand Down
51 changes: 51 additions & 0 deletions instax/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class PacketFactory(object):
MESSAGE_TYPE_MODEL_NAME = 194
MESSAGE_TYPE_195 = 195
MESSAGE_TYPE_PRE_PRINT = 196
MESSAGE_TYPE_SHADING = 64

MESSAGE_MODE_COMMAND = 36 # Command from Client
MESSAGE_MODE_RESPONSE = 42 # Response from Server
Expand Down Expand Up @@ -71,6 +72,8 @@ def decode(self, byteArray):
return(Type195Command(mode=self.mode, byteArray=byteArray))
elif pType == self.MESSAGE_TYPE_SET_LOCK_STATE:
return(LockStateCommand(mode=self.mode, byteArray=byteArray))
elif pType == self.MESSAGE_TYPE_SHADING:
return(ShadingCommand(mode=self.mode, byteArray=byteArray))
else:
logging.debug("Unknown Packet Type: " + str(pType))
logging.debug("Packet Bytes: [" + self.printRawByteArray(byteArray) + "]")
Expand All @@ -92,6 +95,7 @@ class Packet(object):
MESSAGE_TYPE_MODEL_NAME = 194
MESSAGE_TYPE_195 = 195
MESSAGE_TYPE_PRE_PRINT = 196
MESSAGE_TYPE_SHADING = 64

MESSAGE_MODE_COMMAND = 36 # Command from Client
MESSAGE_MODE_RESPONSE = 42 # Response from Server
Expand Down Expand Up @@ -1109,3 +1113,50 @@ def decodeRespPayload(self, byteArray):
'unknownFourByteInt': self.unknownFourByteInt
}
return self.payload

class ShadingCommand(Packet):
"""Shading Command."""

NAME = "Shading Data"
TYPE = Packet.MESSAGE_TYPE_SHADING

def __init__(self, mode, byteArray=None, unknownFourByteInt=None):
"""Initialise Shading Data Command Packet."""
super(ShadingCommand, self).__init__(mode)
self.payload = {}
self.mode = mode
if(byteArray is not None):
self.byteArray = byteArray
self.header = super(ShadingCommand, self).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):
self.decodedCommandPayload = self.decodeRespPayload(byteArray)
else:
self.mode = mode
self.unknownFourByteInt = unknownFourByteInt

def encodeComPayload(self):
"""Encode Command Payload."""
return bytearray()

def decodeComPayload(self, byteArray):
"""Decode the Command Payload."""
return {}

def encodeRespPayload(self):
"""Encode Response Payload."""
payload = bytearray(0)
payload = payload + self.encodeFourByteInt(self.unknownFourByteInt)
return payload

def decodeRespPayload(self, byteArray):
"""Decode Response Payload."""
self.unknownFourByteInt = self.getFourByteInt(16, byteArray)
self.payload = {
'unknownFourByteInt': self.unknownFourByteInt
}
return self.payload
271 changes: 271 additions & 0 deletions instax/sp1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
"""Main SP1 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, ShadingCommand
import logging


class SP1:
"""SP1 Client interface."""

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
self.port = port
self.timeout = 10
self.pinCode = pinCode
self.packetFactory = PacketFactory()

def connect(self):
"""Connect to a printer."""
logging.info("Connecting to Instax SP-1 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]))
# Get current time
start = int(time.time())
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))
except queue.Empty:
time.sleep(0.1)
pass
raise(CommandTimedOutException())

def send_and_recieve(self, cmdBytes, timeout):
"""Send a command and waits for a response.
This is a blocking call and will not check that the response is
the correct command type for the command.
"""
self.comms.cmd_q.put(ClientCommand(ClientCommand.SEND, cmdBytes))
self.comms.cmd_q.put(ClientCommand(ClientCommand.RECEIVE))

# Get current time
start = int(time.time())
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))
except queue.Empty:
time.sleep(0.1)
pass
raise(CommandTimedOutException())

def sendCommand(self, commandPacket):
"""Send a command packet and returns the response."""
encodedPacket = commandPacket.encodeCommand(self.currentTimeMillis,
self.pinCode)
decodedCommand = self.packetFactory.decode(encodedPacket)
decodedCommand.printDebug()
reply = self.send_and_recieve(encodedPacket, 5)
decodedResponse = self.packetFactory.decode(reply.data)
decodedResponse.printDebug()
return decodedResponse

def getPrinterVersion(self):
"""Get the version of the Printer hardware."""
cmdPacket = VersionCommand(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def getPrinterModelName(self):
"""Get the Model Name of the Printer."""
cmdPacket = ModelNameCommand(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def getPrintCount(self):
"""Get the historical number of prints."""
cmdPacket = PrintCountCommand(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def getPrinterSpecifications(self):
"""Get the printer specifications."""
cmdPacket = SpecificationsCommand(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def sendPrePrintCommand(self, cmdNumber):
"""Send a PrePrint Command."""
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)
response = self.sendCommand(cmdPacket)
return response

def sendResetCommand(self):
"""Send a Reset Command."""
cmdPacket = ResetCommand(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def sendPrepImageCommand(self, format, options, imgLength):
"""Send a Prep for Image Command."""
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)
response = self.sendCommand(cmdPacket)
return response

def sendT83Command(self):
"""Send a Type 83 Command."""
cmdPacket = Type83Command(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def sendT195Command(self):
"""Send a Type 195 Command."""
cmdPacket = Type195Command(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def sendLockStateCommand(self):
"""Send a LockState Command."""
cmdPacket = LockStateCommand(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def sendShadingCommand(self):
"""Send a Shading Data Command."""
cmdPacket = ShadingCommand(Packet.MESSAGE_MODE_COMMAND)
response = self.sendCommand(cmdPacket)
return response

def close(self, timeout=10):
"""Close the connection to the Printer."""
logging.info("Closing connection to Instax SP1")
self.comms.cmd_q.put(ClientCommand(ClientCommand.CLOSE))
# Get current time
start = int(time.time())
while(int(time.time()) < (start + timeout)):
try:
reply = self.comms.reply_q.get(False)
if reply.type == ClientReply.SUCCESS:
self.comms.join()
self.comms = None
return
else:
raise(ConnectError(reply.data))
except queue.Empty:
time.sleep(0.1)
pass
self.comms.join()
self.comms = None
raise(CommandTimedOutException())


def getPrinterInformation(self):
"""Primary function to get SP-1 information."""
self.connect()
printerVersion = self.getPrinterVersion()
printerModel = self.getPrinterModelName()
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']
}
self.close()
return printerInformation

def printPhoto(self, imageBytes, progress):
"""Print a Photo to the Printer."""
progressTotal = 100
progress(0 , progressTotal, status='Connecting to instax Printer. ')
# Send Pre Print Commands
self.connect()
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. ')
resp = self.sendLockCommand(1)
self.close()

# Reset the Printer
time.sleep(1)
self.connect()
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. ')
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))
resp = self.sendT83Command()
resp.printDebug()
self.close()
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. ')
printStatus = self.checkPrintStatus(30)
if printStatus is True:
progress(100, progressTotal, status='Print is complete! \n')
else:
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:
return True
else:
time.sleep(1)
return False
Loading

2 comments on commit 36f9bcd

@vertgo
Copy link

@vertgo vertgo commented on 36f9bcd Feb 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you ever get this working? I started the same effort then found this branch. This doesn't seem to work for my instax sp1 printer

@vitorio
Copy link
Owner Author

@vitorio vitorio commented on 36f9bcd Feb 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vertgo Hi! You are correct, it does not work yet with a real printer (the Instax mobile app can successfully send the simulator a JPEG, though, and the client can also send a JPEG to the simulator). This was where I left off, and I haven't had a chance to get back to it yet. If you'd like to try continuing the work, my best current guess is that the client needs to send the "ShadingCommand" packet in the right place/with the right contents, and I'm not currently doing it right. The log in the upstream issue here might be of use: jpwsutton#10 (comment)

Please sign in to comment.