javl / InstaxBLE

MIT License
54 stars 6 forks source link

Different Size #2

Closed hermanneduard closed 1 year ago

hermanneduard commented 1 year ago

Hi, i just tried out the python script with the example.png and compared it to my swift code

when i start download action, i send [65, 98, 0, 17, 16, 0, 2, 0, 0, 0, 0, 0, 0, 0, 193, 92, 28] to the instax printer and your script is sending [b'Ab\x00\x0f\x10\x00\x02\x00\x00\x00\x00\x00\xc1P*']

that is pretty much the same except the size at the end, how did you get this size/characters? c1 is the same as my 193, but i think i am doing something wrong there and dont get it ;-(

javl commented 1 year ago

Hey @hermanneduard First of all: did the script work and print the image for you? Curious to know if it indeed works for others.

I don't know how you calculate those values for your command, but this is how I'm doing it: The size is the size of the image file, not just the color data or anything like that. We're sending over the actual, entire .jpg file:

image = open(imagePath, "rb")  # open as bytes
imgData = image.read() 

The the packet to send is constructed like this:

65 98  # header
xx xx  # packet length
00 02  # print image download start
02     # jpg image (though I don't think any other types are supported)
00 00 00 00 00  # some other options, haven't really looked into them yet
xx xx  # length of the imgData array (len(imgData) in python)
xx     # checksum

After this we're sending the image over in chunks of 900 bytes, where we pad the last chunk with zeroes to make it the right size.

caichunjian520 commented 1 year ago

I am facing the same issue of imageData array length.

In Instax SP-2, I resize image to 600 x 800, and read each pixel with rgb channel. So the length would and always be 600 x 800 * 3 = 1,440,000, it works fine in SP-2

But in Instax Mini, the length of the imgData array should be less than 0xFFFF

So does read each pixel to get image bytes the wrong way?

javl commented 1 year ago

@caichunjian520 Like I said: you don't read the pixels but the bytes of the actual .jpg file.

FF FF means 65535 bytes so your image can be up to ~65kb which should be ok for a slightly compressed 600x800 px image.

hermanneduard commented 1 year ago

i got to print custom images (600x800 - did not test any other scalings right now) over bluetooth on my Instax MiniLiPlay from my native ios app ;-)

Seems that the protocol is slightly different on the LiPlay, i captured the bluetooth data of the original LiPlay App on my iPhone (thx to PacketLogger from Apple Developers).

My working algorithm for the MiniLiPlay:

Packet size is 1819, payload size is 1808 The last payload is also filled with 0x00 (but after analysis of the original data, i think, this is not necessary)

BUT: The whole packet is send in a loop of 10 write commands (not write requests), each of a size of 182 byte

After each Packet, die InstaxMiniLiPlay sends a notification 6142 000C 1001 0000 0000 XXXX

After the notification, the next Packet is send...

Here is a screenshot: https://www.dropbox.com/s/ixtu0kzi386y3xe/dump.png?dl=0

I dont know yet, what the 003F, 013E,.. at the end of the Handle Value Notification after the sent packet means, but i gonna get it ;-)

@caichunjian520 as @javl said, i also just send the jpeg hex data, no rgb values

ps. please sorry for my bad english, i am from austria ;-)

hermanneduard commented 1 year ago

Ok, got it, the response from the MiniLiPlay (003f, 013e, 023d,...) are always 255 (023d-013e,....), seems to be an "ok" status code

javl commented 1 year ago

@hermanneduard great to hear you got it working!

A quick look at your screenshot seems to suggest you are using gatt commands, instead of a socket? I need to check if this will work for my printer as well, as it would remove the need for sockets which seems to be a problem on Windows (#3). Initially I wasn't able to get the printer to work using gatt, except for requesting some basic data like the serial number, but there is a good chance I was messing up something else.

hermanneduard commented 1 year ago

@javl yep, i don't use a socket, and yep, i also got the basic commands working, but not the printing. With the gatt commands it works.

Maybe, the Info is helpful for you, and thx so much for your code and inputs!

javl commented 1 year ago

Maybe, the Info is helpful for you Absolutely! Thanks

caichunjian520 commented 1 year ago

@hermanneduard I successfully print custom image in iOS app with swift language as you did. But there are more than 10 seconds delay before printing starts.

In fact, when send each 900 bytes data, the printer will send notify data such as 61 42 00 0C 10 01 00 00 00 00 00 3F. After all notify data sent, it starts to print, that makes the print starts after more than 10 seconds.

What's the delaying time of your iOS app after PRINT_IMAGE(0x10, 0x80) command sent?

javl commented 1 year ago

@hermanneduard It seems to work without the sockets, which is nice. Do you have any tricks for waiting for the response between sending image packets? At the moment I just send them all over and hope for the best but it would be good to wait for confirmation in between.

javl commented 1 year ago

@hermanneduard Also, as you've seem to have done a lot of testing: any idea what happens when you send an image in landscape orientation, or .png instead of .jpg? (no point in possibly wasting photo's if you already tested it ;) )

hermanneduard commented 1 year ago

@javl I store the start position of the image data in a global (hihi) variable, and increase this, after sending the packet. When i got notify, i simply run the sendpacket method with the new startposition…

as i said, quick and dirty (global variable…)

i did not test landscape/portrait, but testet different image sizes, seems to work if the ratio is the same

@caichunjian520 I made jpeg compression of 0.1 (10%) to reduce image size, does not seem to make a huge difference on the polariod, and scaled the image to 600x800… takes 3 seconds to transfer data and start printing

hermanneduard commented 1 year ago

@javl i did not test png, since the image quality is quiet irrelevant on the polariod. I convert each picture to jpg

javl commented 1 year ago

@hermanneduard Could you share your swift code somewhere? When using the gatt commands I can control the LED just fine, but as soon as I try to send a PRINT_IMAGE_DOWNLOAD_DATA packet I get an "Failed to initiate write" error. Maybe your code can shine some light on how to approach this. Though I'm not sure if the problem is with my code, or with using Linux and the IOS endpoint instead of Android (which only seems to work with the rfcomm socket).

edit: I do seem to get some more activity when I make my image chunks smaller. It seems it doesn't negotiate a proper mtu size or something like that. I think the Android app uses 990 so you can easily fit 900 bytes in each chunk, but I seem to be stuck at 240.

hermanneduard commented 1 year ago

@jawl i think the mtu size of ble (depents on the version of ble) is about 250 byte, therefore i split the 900 (in my case 1808) byte packet into 10 parts and send them one after the other. Then i wait for the notification and send the next packet (split into 10 parts)

hermanneduard commented 1 year ago

as i said, much quick and dirty, but working...

pretty sure some global variables missing in the code, but i think, you will get it:

`

// instax printing
func initializePrintImage()
{
    print("Initialize Printing")
    printing = true
    generateImage()

    let data = finalImageData

    var subArray1 = Array(data!)

    var addon: [UInt8] = [0x00]
    let value = subArray1.count

    while(addon.count<=1808-(subArray1.count%1808))
    {
        addon+=[0x00]
    }
    subArray1+=addon

    let array = withUnsafeBytes(of: value.bigEndian, Array.init)

    //initialize printing
    var commandcode: [UInt8] = [0x10,0x00]
    var comminitsend: [UInt8] = [0x02,0x00,0x00,0x00,0x00,0x00]

    comminitsend.append(array[6])
    comminitsend.append(array[7])

    let initsend: [UInt8] = createPacket(command: commandcode, packet: comminitsend)
    let cmdinit = Data(initsend)

    peripheral_instax!.writeValue(cmdinit, for: instax_WriteCharacteristic!, type: .withoutResponse)

    printpictureNotify()
}

public func printpictureNotify()
{

    print("Printing...")
    printing = true

    let data = finalImageData

    var subArray1 = Array(data!)

    var addon: [UInt8] = [0x00]
    let value = subArray1.count

    while(addon.count<=1808-(subArray1.count%1808))
    {
        addon+=[0x00]
    }
    subArray1+=addon

    print("-----")
    var comm: [UInt8] = [0x10,0x01]
    var index: [UInt8] = [0x00,0x00,0x00,0x00]

    if(start+1808 <= subArray1.count && printing)
        {
            print("Send Packet...")
            self.connectedstatus.text="-- --- --"

            let subArray2 = subArray1[start..<start+1808]
            start+=1808

            let indexarray = withUnsafeBytes(of: indexreal.bigEndian, Array.init)

            index[2]=indexarray[6]
            index[3]=indexarray[7]

            let newArray = index+Array(subArray2)

            indexreal+=1

            let cmdBytes2: [UInt8] = self.createPacket(command: comm, packet: newArray)
            let cmd2 = Data(cmdBytes2)

            iterations=0
            while(iterations <= 8)
            {
                let subArrayiteration = cmdBytes2[182*iterations..<182*iterations+182]
                print("Send small Packet...")
                let subArrayiterationData = Data(subArrayiteration)
                self.peripheral_instax!.writeValue(subArrayiterationData, for: instax_WriteCharacteristic!, type: .withoutResponse)
                iterations+=1
            }
            let subArrayiteration = cmdBytes2[182*iterations..<cmdBytes2.count]

            let subArrayiterationData = Data(subArrayiteration)
            self.peripheral_instax!.writeValue(subArrayiterationData, for: instax_WriteCharacteristic!, type: .withoutResponse)

        }
        else
        {
            printing=false
            start=0
            indexreal=0
            self.printpictureend()
            self.printpictureprint()

        }

}

func printpictureend()
{

    let cmdBytes3: [UInt8] = createPacket(command: [0x10,0x02], packet: [])
    let cmd3 = Data(cmdBytes3)
    print("sending picture end code")
    print(cmdBytes3)
    peripheral_instax!.writeValue(cmd3, for: instax_WriteCharacteristic!, type: .withoutResponse)

}

func printpictureprint()
{
    self.connectedstatus.text="---------"
    let cmdBytes: [UInt8] = createPacket(command: [0x00,0x02], packet: [0x02])
    let cmd = Data(cmdBytes)

    print(cmdBytes)
    peripheral_instax!.writeValue(cmd, for: instax_WriteCharacteristic!, type: .withoutResponse)

    let cmdBytes3: [UInt8] = createPacket(command: [0x10,0x80], packet: [])
    let cmd3 = Data(cmdBytes3)
    print("sending picture print code")
    print(cmdBytes3)
    peripheral_instax!.writeValue(cmd3, for: instax_WriteCharacteristic!, type: .withoutResponse)
}

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {

    if characteristic.uuid == instaxnotifyUUID {
        //printpicture()
        print("data from printer")
        //print(characteristic.uuid)
        print(data.hexEncodedString())

        if(data.hexEncodedString().contains("6142000c100100000000") && printing)
        {
            print("wanna print")
            printpictureNotify()
        }

    }
}`
javl commented 1 year ago

@hermanneduard ah, thanks, especially your comment on the size is of great help.

I thought the MTU was too low and I wasn't able to set this to a higher value on Linux so I was kind of stuck. But what you're saying is that you can take your 900 byte messages, chop them into smaller parts and send those in sequence? I'll give that a try, thanks!

hermanneduard commented 1 year ago

yep, just split the 900 byte message (as i said, in my case 1808 + header,...) into smaller parts and just send them one after the other (just the first part has the header, all others just the bytes as they are)

javl commented 1 year ago

I was having trouble getting a response from the printer when sending image chunks. Turns out my packets need to be 20 bytes for my notification handler to get triggered by the device (I think the bluez bluetooth stack underneath uses an MTU of just 24 or so)

Anything larger and my notification handler doesn't get triggered (though a larger packet for LED control does work). MacOS seems to negotiate a larger MTU on connect, but I can't seem to find a way to do this on Linux.

Beside the packets being small, there is also quite some delay before the notification gets triggered. With my test image needing 55 packets it's going to take ~30 seconds to send the entire thing.

So at this point I can use my gatt setup, but this is extremely slow, or I can use my socket setup which is really fast but only works on the INSTAX-xxx (Android) device, which my Raspberry Pi 3B+ can't see for some reason (it only sees the IOS device)... Maybe I should check if it does see the device if I use a usb bluetooth dongle instead of the built-in bluetooth adapter, but I'd rather have this module be a one-size-fits-all one which works on all platforms and devices...

javl commented 1 year ago

@hermanneduard forgot to ask: why do you split the image into 1808 byte chunks instead of 900? Are you using a different model, does the IOS endpoint use this or is the 900 arbitrary (I took it from what the Android app does).

hermanneduard commented 1 year ago

Hmm, i use (as you see) the IOs app, i captured the bluetooth packets from the original App and simply did the same (thats the reason for the 1808 byte chunks)

I use the Instax MiniLiPlay, and i think, the LiPlay needs a different app, maybe they use also a different protocol.

As i said, on the iPhone with about 60 packets, i need about 3-6 Seconds to transfer the image, it gets really fast

javl commented 1 year ago

Yeah, it's that mtu thing where IOS/MacOS decide on a transfer speed together with the printer. If they both support a high throughput sending the image takes no time at all. Interesting how the Android app uses 900 bytes but IOS uses 1808. Then again, they do use different protocols (rfcomm vs gatt).

Thanks again for all your feedback. My old code works on (desktop) Linux, the updated code using gatt should also work on MacOS, but I'm still trying to figure out how to get this to work on my Raspberry Pi which is the actual platform I want to use. For some reason it doesn't see the IOS endpoint and I just don't know why...

javl commented 1 year ago

@hermanneduard I'm still struggling to update my code to make it cross platform. Looking at your code again I was wondering:

  1. Where did you get the 1808 bytes packet size from?
  2. Why do you chop your 1808 byte packets into 8 smaller packets?

Edit: Ok, never mind you already explained in a previous post.

javl commented 1 year ago

@hermanneduard This is so annoying! I keep getting corrupted prints, but when I look at the data I'm sending it looks fine. Would you be able to sent me the recording of your bluetooth communication (if you still have it). I could make a parser for that data and use it to check my own code against.

hermanneduard commented 1 year ago

Hi, can you post an image of the corrupted print? I have dozen’s 😂. My problem was the packet size AND that i was not waiting for the confirment of the printer that it correctly received the packet before i sent the next packet. The printer sends a notification after each packet, wait for it before you send the next…

javl commented 1 year ago

I'll take a photo of one later. It shows a bunch of bars which I think correspond with the packets being send. There are 10 bars, of which about 1/5th seems to be proper data, which might fit with me sending the image in 55 parts. There is something in there, but the colors are wrong and there is loads of noise. Feels to me it's clearly something with the smaller packets.

I went through your code a bunch of times to try and see how you did this: you just take the IMAGE_DATA packet with its header, checksum, etc. chop it up into smaller parts without adding any new headers or checksums and send those off, right? You mentioned using a write command for these, but don't you use those for all other packages as well?

I'll check if I'm waiting for the response properly. I think I do but wouldn't surprise me if I actually don't.

This is all very annoying as I had working code on Linux using the Android approach (sockets) but I want this thing to be cross platform, haha

javl commented 1 year ago

@hermanneduard Does this look familiar to you? ;) image

javl commented 1 year ago

It worked! In the end I recorded a print command from my iphone so I had something to compare against. Seems I was already doing the right thing (same your approach) but I guess there was some sort of timing error / overlapping / whatever. FINALLY! :D