javl / InstaxBLE

MIT License
54 stars 6 forks source link

Adaptation to Instax SQUARE Link #4

Closed geffaelden closed 1 year ago

geffaelden commented 1 year ago

I was wondering if the same code can be adapted to the SQUARE Link? https://instax.com/square_link/en/

javl commented 1 year ago

I have no way of testing this (I don't have that printer) but I expect it to work pretty much the same way, except with a larger image. The mini link uses a 600x800 image so I think the square would use 800x800?

Though I can also imagine the code being slightly different as the same filesize limit but a larger image would mean a lower quality image will be printed.

If you have an Android phone you could try to record the bluetooth interaction and we can see what happens. See this post for info on doing that: https://github.com/jpwsutton/instax_api/issues/21#issuecomment-751651250

javl commented 1 year ago

@geffaelden If you're on IOS and have a Mac you can also use their PacketLogger tool to record the data between your phone and the printer. Using those logs I should be able to see if / what needs to be changed in to code to support the SQUARE link.

I did this the other day and it's pretty straightforward. Basically the steps are:

  1. Download a profile file from Apple, install this on your IOS device
  2. Download Packet Logger on your Mac
  3. Connect your IOS device using the cable and in Packet Logger starts a new IOS trace
  4. Clear all data, print your image, stop the logging and save the result

Pretty clear steps can be found here: https://www.bluetooth.com/blog/a-new-way-to-debug-iosbluetooth-applications/

geffaelden commented 1 year ago

@javl I've done this but can't seem to figure out which log is for the image printing

javl commented 1 year ago

@geffaelden On what platform? If you used Packet Logger with IOS you can just start recording, do the printing and stop recording, and send that complete file. There might be some other stuff in there but that doesn't matter, I can filter out the printer part.

geffaelden commented 1 year ago

@javl I used the iOS PacketLogger method. Here's the log of the test print I did: https://drive.google.com/file/d/1OZD5cZ8FS1ZkrVpG3Rx0y92isoHqskKw/view?usp=share_link

javl commented 1 year ago

@geffaelden Thanks, I'll take a look as soon as I can 👍

javl commented 1 year ago

@geffaelden Took a very quick look and it seems it shouldn't be too hard to adapt my code to work, just need to find the time ;) Thanks for the file! You can take it offline if you want, I've got a local copy.

javl commented 1 year ago

@geffaelden Something is different compared to the way the Mini Link prints its images. Managed to get a 800x800 px image from your logs, but there are some glitches. Do you still have the image file you used for printing? If so I could compare it against what I get from to logs to see what is happening (the app sends over the actual image, so by comparing the original bytes with the logs I should be able to see what's changed).

geffaelden commented 1 year ago

Heres the test image I printed

New Project

javl commented 1 year ago

Ok, that might explain something: the other printer only supports .jpg but this is .png (though the header in the logs seems to suggest jpg).

Thanks for the file.

javl commented 1 year ago

It seems the square (and probably wide) apps allow you to select more image types (jpg, png and some newer formats used on IOS and Android) than the app for the Mini Link does (jpg only) but the app just converts the image to jpg before sending it to the printer. There was some error in my parser but now I'm able to extract your image from the logs, meaning I should be able to adapt my code to send your image to the printer as well. Stay tuned ;)

Not sure what would be the best approach: having the user set a device type (something like instax.set_model(square) or detect the model based on the image (600x800 is mini link, 800x800 is square, and the wide probably uses 800x600?)

javl commented 1 year ago

@geffaelden I think the fix to support the square is simple: can you try replacing chunkSize = 900 with chunkSize = 1808 and try printing again?

linssenste commented 1 year ago

I successfully printed an image on the Instax Square Link 🥳 Using a chunkSize of 1808 did the trick! Initially, I encountered an issue with the incorrect image size, which was caused by faulty compression (see image 🤦🏼‍♂️). However, using an 800x800 JPEG resolved the problem. I developed my own implementation in TypeScript, utilizing the Bluetooth Web API, and drew significant inspiration from your work, @javl. Thank you for your great work! print_results

In addition to this, I've been working on parsing other messages of the INSTAX Protocol, such as battery status, charging status, printed images, and more. I wanted to created an alternative UI to print images with an Instax printer via a web browser. I plan to upload my code to GitHub in the coming days.

Bildschirm­foto 2023-04-12 um 16 00 27
javl commented 1 year ago

@linssenste Wow, super cool! For my current project printing from Python is more useful, but being able to print straight from the browser is such a great idea! And way more accessible for people who don't know how to use Python! This is why I like to make my code open source!

Do post a link when your code is online, I'll add it to my readme.

javl commented 1 year ago

@linssenste By the way, I already have some of those features in some old code: Send a message with opcode EventType.XYZ_AXIS_INFO, InfoType.BATTERY_INFO or InfoType.PRINTER_FUNCTION_INFO and you can parse the results like this:

        header, length, op1, op2 = unpack_from('>HHBB', response)
        if (op1, op2) == EventType.XYZ_AXIS_INFO.value:
            x, y, z, o = unpack_from('<hhhB', response[6:-1])
            print(f'x: {x}, y: {y}, z: {z}, o: {o}')
            return
        elif (op1, op2) == EventType.SUPPORT_FUNCTION_INFO.value:
            infoType = InfoType(response[6])
            if infoType == InfoType.BATTERY_INFO:
                self.batteryState, self.batteryPercentage = unpack_from('>BB', response[8:10])
                print(f'battery state: {self.batteryState}, battery percentage: {self.batteryPercentage}')
                return
            elif infoType == InfoType.PRINTER_FUNCTION_INFO:
                dataByte = response[8]
                self.photosLeft = dataByte & 15
                self.isCharging = (1 << 7) & dataByte >= 1
                print(f'photos left: {self.photosLeft}, is charging: {self.isCharging}')
                return
linssenste commented 1 year ago

@javl Thanks!

I uploaded my source code in the repository and the project is accessible here: https://instax-link-web.vercel.app/

It’s currently only working with the SQUARE printer, but I have access to the mini and wide soon and will test and implement them. 🙃

I will work with your repository as well soon, as I want to programmatically control the printers as well, so we may also close this issue then.

Btw: if you want to scale images to test with the python code, you can just download the image, it’s already compressed and resized

javl commented 1 year ago

For the mini just use a size of 900 as in my current code.

So we know: Mini 600x800, 900 bytes Square 800x800, 1808 bytes

I thought the wide was just the mini but on landscape, but looking at them online they look more like even larger versions of the square at 800 × 1260. So, 1808 bytes but more packets?

javl commented 1 year ago

@linssenste Great readme by the way (and I love Jasper the dog)

javl commented 1 year ago

@linssenste was thinking about adding a setting to specify the printer you're using, but then I read this in your readme

Once you connect your printer via Bluetooth, the app fetches specific details like battery status, 
charging state, remaining prints, and allowed image dimensions (width and height). These 
updates roll in every 2.5 seconds.

I totally forgot you can just get the model type from the printer :) Way more user friendly. But could you point me towards where you get the print size from the device? All the sizes I see in your repo seem to be hard-coded?

linssenste commented 1 year ago

@javl yes, you are right. the adaption to the mini shall be pretty straight forward now :) I will add this functionality, and you may test it (you can abort before printing). I had a quick look at the wide-link protocol and there seems to be a variation how the chunks send, it seems to also have a chunk size of 900 but is using a different splitting of the chunks, but I have to look into that in more detail. I will share a record here as well :)

Yes, you can actually also get the type from the serial number (I guess square starts with 50, wide 20, mini ???) and there seems to be a readable printer type value in the DEVICE_INFO_SERVICE. But I worked with the image size, because I thought this was more convenient. It's actually not hardcoded - initially it is set to 800x800 and can be switched on the frontend. Once a printer is connected it the image size is read in the PrinterSettings.vue fetch interval, basically the size is provided in the SUPPORT_FUNCTION_INFO 0 as two two byte integers. I tested it with the wide and the square and both are providing the correct size. You can test it with your mini and connect it to the App - it will automatically lock the image size to 600x800 :)

javl commented 1 year ago

That works perfectly. You can also get the printer type from a code it sends: the Link Mini seems to be SP-4, but I guess you don't really need anything beside the size it allows.

vertgo commented 1 year ago

For the mini just use a size of 900 as in my current code.

So we know: Mini 600x800, 900 bytes Square 800x800, 1808 bytes

I thought the wide was just the mini but on landscape, but looking at them online they look more like even larger versions of the square at 800 × 1260. So, 1808 bytes but more packets?

Hi @javl, @linssenste,

Trying to make this work with a raspberry pi and an instax square link, so it's been a bit painful. Right now, my main problem is during the data packing when it creates packets for printing, with an 800x800 image and a chunksize of 1808.

self.packetsForPrinting = [ self.create_packet(EventType.PRINT_IMAGE_DOWNLOAD_START, b'\x02\x00\x00\x00\x00\x00' + pack('>H', len(imgData))) ]

I get an error:

Error: 'H' format requires 0 <= number <= 65535

The length of imagedata at that point is 90110, so more than the H format.

javl commented 1 year ago

@vertgo

Your error is 'Error: 'H' format requires 0 <= number <= 65535' which seems to be correct if the length of your data is 90110, which is more than the max allowed 65535 from the error.

This suggests your image is about 90kb, try saving it at a lower quality so you end up around or below 65kb.

I've had trouble getting this to work with the Rpi so very curious to see your results. In my case it became VERY slow and I was thinking of moving back to a script that uses a bluetooth socket instead when running on a pi. Would love to get this working reliably.

javl commented 1 year ago

@vertgo I was about to do some more tests on a pi. I think I mixed up some settings before: originally I used a socket with a packet size of 900, as used by the android app. Sockets didn't work on mac/windows so I moved to simplepyble but I kept the size at 900. Maybe it will run fine on the pi at 1808. The main problem was the speed, took about a minute to send one image, but at 1808 it might be way faster.

vertgo commented 1 year ago

@javl I thought I posted last night, I got it working using 'i' instead of 'H'. It was a bit unreliable so there will have to be more tests. Would 'i' also work for the mini printer (I only have the square and the wide, about to try the wide). I could query the model and then decide which to use (square, mini or wide).

I tested it from mac and then got it working on the pi. Doing it from pi was a bit spotty, as the simplepyble required installing from piwheels and was only compatible with raspbian bullseye. It is slow, and speed is important to me, so if sockets are faster would love to try that.

self.packetsForPrinting = [
            self.create_packet(EventType.PRINT_IMAGE_DOWNLOAD_START, b'\x02\x00\x00\x00\x00\x00' + pack('>i', len(imgData)))
        ]
javl commented 1 year ago

@vertgo The H is an unsigned short of 2 bytes, while i is a 4-byte integer, so I can imagine this will give trouble at some point as it offsets the content of your packet by 2 bytes, which the code on the printer doesn't expect. The phone app will change the quality of the image to fit the size allowed, which I admit is not a lot: even for the 600x800 of the Link Mini 65kb is pretty low res. I think I'll add an option to do this automatically to the Python script later on (which basically just saves your image with lower and lower quality over and over until it finds a quality setting that gets the image below 65kb).

If you run your Pi with a desktop you could give the site @linssenste made a try (you need to enable bluetooth in your browser's settings). It already includes the resizing. https://instax-link-web.vercel.app/

I'd love to be able to use a single bluetooth backend for all systems, but if we can't find a way to speed up the transfer, adding another layer for the socket is needed I guess, as the Pi is also my main target device. I guess we could come up with some structure that allows the user to write a single script and let the module decide what backend to use automatically.

I never made a proper tag or branch with the old version, but if you want to give it a go I think the socket was still around in commit 5f78b1261d2773fe80254f52d2212a0b0f1d0ec1 on the main branch (or otherwise just check some other old commits).

vertgo commented 1 year ago

How did you come to the conclusion it was 1808? It seems so specific. I am trying to track down why printing from the app is SO much faster than using the library

javl commented 1 year ago

@vertgo If you log the data transfer you can see the size of the packets it is sending. Basically it is the size of the payload minus the header (2), length (1), event code (2), checksum (1) and packet index (4). See this post for an example: https://github.com/javl/InstaxBLE/issues/9#issuecomment-1670847484

Making it faster would be amazing, but I haven't figure out how yet :disappointed:

javl commented 1 year ago

I've created a separate issue for the speed problem, feel free to post any suggestions, test or remarks in there!

Yasston commented 1 year ago

Basically it is the size of the payload minus the header (2), length (1), event code (2), checksum (1) and packet index (4)

@javl , isn't the length two (2) bytes ?

javl commented 1 year ago

Basically it is the size of the payload minus the header (2), length (1), event code (2), checksum (1) and packet index (4)

@javl , isn't the length two (2) bytes ?

You're right, its a big-endian unsigned short to be precise https://github.com/javl/InstaxBLE/blob/f8c405108931ba4dadc74d7fed0ca309ef5f4f94/InstaxBLE.py#L303