EASession OutputStream HasSpaceAvailable always false

DeanCleaverDeanCleaver USMember ✭✭✭

This code had been working for about 6 months:

EASession session = new EASession(accessory, "com.bluebamboo.p25i");
session.OutputStream.Delegate = this;
session.OutputStream.Schedule(NSRunLoop.Current, NSRunLoop.NSDefaultRunLoopMode);
session.OutputStream.Open();
session.InputStream.Delegate = this;
session.InputStream.Schedule(NSRunLoop.Current, NSRunLoop.NSDefaultRunLoopMode);
session.InputStream.Open();

byte[] temp = new byte[data.Length + 5];
Array.Copy(data, 0, temp, 5, data.Length);

temp[0] = 0x55;
temp[1] = 0x66;
temp[2] = 0x77;
temp[3] = 0x88;
temp[4] = 0x44;

session.OutputStream.Write(temp, (uint) temp.Length);

Thread.Sleep(1000);

session.OutputStream.Close();
session.OutputStream.Unschedule(NSRunLoop.Current, "kCFRunLoopDefaultMode"); //NSRunLoop.NSDefaultRunLoopMode);
session.OutputStream.Delegate = null;
session.InputStream.Close();
session.InputStream.Unschedule(NSRunLoop.Current, "kCFRunLoopDefaultMode"); //NSRunLoop.NSDefaultRunLoopMode);
session.InputStream.Delegate = null;

session.Dispose();

accessory.Dispose();

But now, it outputs an error stating the output stream has no space available. Checking HasSpaceAvailable at any time in code shows it's false, and obviously nothing will print to the Bluetooth printer any more, and I have no idea what needs to be done to "make space".

Is this a Xamarin bug, or an iOS bug? It might only be latter versions of iOS, but I only have 6.1.2 and 6.1.3 available to me, and both have this same problem.

Posts

  • RolfBjarneKvingeRolfBjarneKvinge USXamarin Team Xamurai

    Is anything written to the iOS Device Log when this happens?

    I assume your also already listing the external accessories you're using in your Info.plist?

  • StenSten USMember

    I'm getting the same issue when trying to connect to a Star Micronics TSP650II Bluetooth printer.

    StackOverflow comment on my question there directed me here. I could work around the issue by making a binding for the SDK Star provides for iOS but I'd much rather not increase the package size by 700K for that.

    http://stackoverflow.com/questions/16513037/star-micronics-tsp650ii-bluetooth-printer-cant-write-to-easession-outputstream

  • GustavoGalanGustavoGalan USMember ✭✭

    Were you able to solve this problem without using the SDK.
    I am working with a iMZ320 Zebra Bluetooth printer, and have the same issue. In fact I got the same error of "Not available space" on both, using the bounded SDK, and the EASession-EAAccesoryManager classes.
    Thanks

  • ignaciomachinignaciomachin USMember ✭✭

    Any update on this?

    Thank

  • Sten.PetrovSten.Petrov USMember
    edited September 2013

    This is still not working.
    And - yes, the protocol is listed in Info.plist

    And it seems it's a Xamarin bug because I looked at the source code of Star Micronic's printer and it seems to be doing the same as I'm doing through Xamarin but using the SDK through a binding works and using the EASession directly - doesn't

  • DeanCleaverDeanCleaver USMember ✭✭✭

    I put this code after the last Open call for the session streams:

                                    if (BaseViewController.IsIOS6OrBetter)
                                        NSRunLoop.Current.RunUntil(DateTime.Now.AddMilliseconds(100));
    

    And then my write code is this:

                                    if (BaseViewController.IsIOS6OrBetter)
                                    {
                                        result = this.WriteBytes(session, temp);
    
                                        NSRunLoop.Current.RunUntil(DateTime.Now.AddMilliseconds(100));
                                    }
                                    else
                                        result = session.OutputStream.Write(temp, (uint)temp.Length) == temp.Length;
    
  • KBSKBS AUMember
    edited March 2014

    I know this thread is a bit old but I was wondering if anyone can explain to me how to use .OutputStream.Delegate = this; ?
    I am not sure where in the code above did @Dino use the delegate?

    Also, can't quite understand the use of `BaseViewController, would appreciate if someone could refer me to a link.

    Thank you in advance

  • DeanCleaverDeanCleaver USMember ✭✭✭

    Sorry - the class that contains the first block of code is inherited from NSStreamDelegate, thus it can be the stream delegate for both input and output. And the BaseViewCOntroller is inherited from UIViewController, and merely contains helper fuctions (some static like the IsiOS7OrBetter).

  • KBSKBS AUMember

    Thanks @Dino‌ for the quick answer.

    I understood those lines were inherited from NSSteramDelegate but couldn't work out how is the delegate working in this case? Where does it implement the stream handleEvent delegate method, if at all?

    My problem is that I have a very similar code to you when it comes to establishing the connection. I can open both streams (In/Out) and even verify it using NSStreamStatus outstatus = outStream.Status; and yet HasSpaceAvailable(); is always zero and if I try to write using OutputStream.Write(Cmd,(uint)cmd_num_bytes) it will also always return zero (which is expected given the above).

    I was also baffled by:

    result = this.WriteBytes(session, temp);

    Doesn't WriteBytes belong to Android?

    Thanks again for your help

  • DeanCleaverDeanCleaver USMember ✭✭✭

    I only posted a snip of the entire class. The delegate is working because it has code I didn't paste in here. Likewise the WriteBytes is a function I wrote that I didn't paste in here. It's nothing to do with Android.

  • FabioMelendezFabioMelendez USMember, University

    quick question, I have implemented this code and runs without a problem, but seems like the output is getting queued in the printer and not getting printed, is there a command I need to send so the printing actually happens?

    I verified by running a different application that also prints and the text i send before get printed on top and then it prints whatever was sent by the second application.

  • MatzeMatze USMember ✭✭

    Hi guys,

    I recently had the same problem like stated in the first post from @DeanCleaver. The problem with run loops is that only the run loop for the main thread has been started automatically by the app itself. And because every thread has its own run loop, you have to start run loops, which belong to other threads than the main thread, by yourself.
    With NSRunLoop.Current you will always get the run loop for the current thread! So if you are not on the main thread, you have to either start the run loop by NSRunLoop.Current.Run(); or by NSRunLoop.Current.RunUntil(NSDateTime.Now.AddSeconds(0.1)); like @DeanCleaver did. I encountered that both statements were blocking the running thread. NSRunLoop.Current.Run(); indefinitely until someone calls NSRunLoop.Current.Stop(); or kills the running process and NSRunLoop.Current.RunUntil(NSDateTime.Now.AddSeconds(0.1)); for at least 0.1 seconds.

    So why not using NSRunLoop.Main as scheduler for your input and output streams? It solves any problems and all you have to do in the above code sample from @DeanCleaver is, replace NSRunLoop.Current with NSRunLoop.Main.

  • JeffGonzalesJeffGonzales USMember ✭✭

    I'm trying to use the "polling" method to write out data on the out stream but HasBytesAvailable() is always false. You don't need a run loop when polling, do you?

  • MatzeMatze USMember ✭✭

    @JeffGonzales, What do you mean with "polling" method? I think I have really similar problems.
    I only got it working with a timeout (about 250ms) between cyclic send and receive calls. But that's not acceptable!
    If you are transferring about 500 parameters from a device and you have to wait additionally 500 * 250ms...

  • JeffGonzalesJeffGonzales USMember ✭✭

    @Matze When I refer to polling I mean the option that doesn't use run-loop scheduling:

    https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/PollingVersusRunloop.html

    I cannot write anything as hasSpaceAvailable is always false unless I register a delegate that implements HandleEvent().

  • MatzeMatze USMember ✭✭
    edited March 2016

    @JeffGonzales
    I don't even got it working if i assign a delegate which implements "HandleEvent()". Only if i schedule it in a run-loop, the "HandleEvent()" method in my delegate is receiving events. But this is not what I want. I need the polling mode. This is my code for opening the streams:
    mEASession = new EASession(connectedEAAccessory, EAAccessoryProtocol); mNSInputStream = mEASession.InputStream; mNSInputStream.Delegate = new NSStreamDelegate(); //"NSStreamDelegate" is inherited from "NSObject" and implements "HandleEvent()" mNSInputStream.Open(); mNSOutputStream = mEASession.OutputStream; mNSOutputStream.Delegate = new NSStreamDelegate(); //"NSStreamDelegate" is inherited from "NSObject" and implements "HandleEvent()" mNSOutputStream.Open();

    In addition:

    public class NSStreamDelegate : NSObject, INSStreamDelegate { /// <summary> /// The event handler which is triggered when a stream (input/output) receives an event. /// </summary> /// <param name="stream"></param> /// <param name="streamEvent"></param> [Export("stream:handleEvent:")] public void HandleEvent(NSStream stream, NSStreamEvent streamEvent) { if (streamEvent == NSStreamEvent.HasBytesAvailable) { //OnBytesAvailable(); } } }

  • MatzeMatze USMember ✭✭
    edited April 2016

    @FranjoStipanovic.2218
    I finally got it working with a little "tweak". I still don't use the polling mode because I didn't got it working but I have a solution which is quite adequate for me. I adjusted my read and write method like so (Only write method as example here):

    public override Task<bool> WriteBytesAsync(byte[] writeBuffer)
    {
        int bytesToTransfer = writeBuffer.Length;
        int totalNumberOfBytesWritten = 0;
        int maxRetries = 50;
    
        while (totalNumberOfBytesWritten != bytesToTransfer && maxRetries != 0)
        {
            if (mNSOutputStream.HasSpaceAvailable())
            {
                nint numberOfBytesWritten = mNSOutputStream.Write(writeBuffer, totalNumberOfBytesWritten, (nuint)(writeBuffer.Length - totalNumberOfBytesWritten));
                totalNumberOfBytesWritten += (int)numberOfBytesWritten;
    
                if (numberOfBytesWritten > 0)
                {
                    maxRetries = 50;
                }
            }
            else
            {
                maxRetries--;
                Thread.Sleep(TimeSpan.FromMilliseconds(10));
            }
        }
    
        return Task.FromResult(maxRetries > 0);
    }
    

    Everything else is set up like in my other posts. But I am not reacting on any events of the stream. I am listening until I receive any bytes and I am waiting until I have space to write to the stream.

  • FrankMarkovich.6785FrankMarkovich.6785 USUniversity ✭✭

    @Matze said:
    Hi guys,

    I recently had the same problem like stated in the first post from @DeanCleaver. The problem with run loops is that only the run loop for the main thread has been started automatically by the app itself. And because every thread has its own run loop, you have to start run loops, which belong to other threads than the main thread, by yourself.
    With NSRunLoop.Current you will always get the run loop for the current thread! So if you are not on the main thread, you have to either start the run loop by NSRunLoop.Current.Run(); or by NSRunLoop.Current.RunUntil(NSDateTime.Now.AddSeconds(0.1)); like @DeanCleaver did. I encountered that both statements were blocking the running thread. NSRunLoop.Current.Run(); indefinitely until someone calls NSRunLoop.Current.Stop(); or kills the running process and NSRunLoop.Current.RunUntil(NSDateTime.Now.AddSeconds(0.1)); for at least 0.1 seconds.

    So why not using NSRunLoop.Main as scheduler for your input and output streams? It solves any problems and all you have to do in the above code sample from @DeanCleaver is, replace NSRunLoop.Current with NSRunLoop.Main.

    I've had the same bluetooth "library" in my app for over two years and suddenly with the 11.2.6 iOS update it started having the "HasSpaceAvailable is always false" issue. Changing from InputStream.Schedule(NSRunLoop.Current, NSRunLoop.NSDefaultRunLoopMode); to InputStream.Schedule(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode); and things work again.

  • AhmedHasanAhmedHasan USMember ✭✭
    edited January 10

    Hey guys..

    Thanks to everyone for this discussion, NSRunLoop.Current.RunUntil(NSDateTime.Now.AddSeconds(0.1)); it resolves my issue.

Sign In or Register to comment.