PyramidTechnologies / netPyramid-RS-232

Alpha release of the Pyramid C# .NET RS-232 API
MIT License
6 stars 6 forks source link

serial port trouble with .NET 3.5 in Unity #1

Closed animalelement865 closed 9 years ago

animalelement865 commented 9 years ago

I removed System.Threading.Tasks and set it to .NET 3.5 and it compiled the dll without any errors. I then took the code from MainWindow.xaml.cs and put it into Unity 5.1. The LED comes on for about 3 seconds and then go out and I see this error come up.

NullReferenceException: Object reference not set to an instance of an object System.IO.Ports.WinSerialStream.get_BytesToRead () System.IO.Ports.SerialPort.get_BytesToRead () (wrapper remoting-invoke-with-check) System.IO.Ports.SerialPort:get_BytesToRead () Apex7000_BillValidator.ApexValidator.Read () Apex7000_BillValidator.ApexValidator.Connect () gameManager.Start () (at Assets/gameManager.cs:72)

ghost commented 9 years ago

Thanks for posting this.

This to me looks like the serial port is closing in the middle of the read operation. This could happen if the connection is physically unstable or if there is other logic in your code that is attempting to connect or create multiple instances of the ApexValidator class.

Can you tell me a little bit more about your application so I can try to replicate for testing purposes?

ghost commented 9 years ago

I'm finding a whole bunch of discussions on the Unity and Stackoverflow boards about serial port issues with Unity that are pointing at Mono being the underlying issue. What .NET/Mono versions are you targeting with your build?

animalelement865 commented 9 years ago

I'm using the latest Unity 5.1. I'm using Visual Studio 2015 as the editor, not Mono although it might still be using Mono libraries. Unity supports .NET 2.0 in the build but I've heard it supports .NET 3.5 libraries but I can't confirm. Maybe it's an issue with events?

I have 4 other COM devices connected into Unity without any issue. 2 are Teensy 3.1 (arduino clone) and 2 motor controllers using AtMega chips.

I created a brand new scene (project essentially) with just the single script so I know it's not getting interference from anything else.

With my other com port connections I create a new thread with a while(true) loop in it. I'm basically doing this:

using System.IO; using System.IO.Ports;

public class imuArduino : MonoBehaviour { public SerialPort SerialPort;

void Start() {

SerialPort = new SerialPort("COM5", 115200); SerialPort.ReadTimeout = 1000; SerialPort.WriteTimeout = 1000; SerialPort.Open();

then I launch a process called arduinoLoop(); }

void arduinoLoop() { while (true) if (SerialPort.IsOpen) { string rData = SerialPort.ReadLine(); // read the data here. } }

}

ghost commented 9 years ago

I was reading the same about the events but we're just polling on the background thread because the DataReceived event is so unreliable.

How are you launching your ArduinoLoop() in your test? Our read/write operations occur on a thread that is started like this:

 Thread ackThread = new Thread((fn) =>
            {
                while(true)
                {
                // Locks are recursive so any protected region within these functions are guaranteed to hold the lock
                lock (mutex)
                {
                    Write(Request.BaseMessage);
                    Read();
                }

                Thread.Sleep(pollRate);
            }
        });
        ackThread.IsBackground = true;
        ackThread.Start();

Perhaps there is an issue in way threads are implemented? I'm not sure of the internals on what the Start() routine does under the hood but it may have some implications on how manually created threads are handled.

animalelement865 commented 9 years ago

I was previously using the following but switched to a different library for simplicity but they work the same. Start() just runs automatically when the script loads. It's like main().

arduinoThread = new Thread (arduinoLoop) {Name = "arduinoLoop"}; arduinoThread.Start();

animalelement865 commented 9 years ago

Could have something to do with how public ApexValidator validator; is being used.

I'm still pretty new to all this but when I see the error Object reference not set to an instance of an object it's usually related to how public objects are being setup.

ghost commented 9 years ago

I'm worried that the thread running the acceptor is dying. If that happens, the thread death disposes of the ApexValidator object which contains a SerialPort object. If the SerialPort object is disposed of before the baseStream is closed this is exactly the exception that will be raised.

There is a bug in the .NET SerialPort class that does not properly handle the basestream under some conditions. We have workarounds in other projects but they are quite intensive and I'd like to avoid implementing them if possible.

Give me some time to test and I'll see if I can verify the root cause.

animalelement865 commented 9 years ago

http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport "The BytesToRead property (completely unreliable)". I'm not using that member in my script which is probably why I've had ok luck with system.io.ports.

Might be worth trying out http://sourceforge.net/projects/serialportnet/ and possibly including in this project.

ghost commented 9 years ago

Yes, that is exactly the article we built our workaround around. The serialportnet project does work on some machines but I've found portability issues that I could never fully resolve. Might be worth trying again, thanks.

ghost commented 9 years ago

I've pushed a branch call SerialPort_Alternate that uses the serialportnet class. Give that a shot and let me know what that does on Unity.

The state and event EventHandlers changed a little and are not completely tested. The credit event works fine though and can be trusted.

animalelement865 commented 9 years ago

I can't get that new serial port library to work (OpenNETCF.IO.Serial). When I try to open the project in VS 2015 I get errors about source control and can't get past them.

I created a new project and named it OpenNETCF.IO.Serial and imported the .cs files from OpenNETCF and compiled into a .dll. I add a reference to that .dll to your alternative serial project but instead of using OpenNETCF.IO.Ports it only gives me OpenNETCF.IO.Serial

So I put in:

private OpenNETCF.IO.Serial.Port port = null;

But that doesn't work and I get a bunch of errors about missing references. However I created the dll must be wrong. Not really sure what to do.

animalelement865 commented 9 years ago

The files were read only and that was causing some issues. Once I removed that I then get an error that it's not compatible with my version (2015) of Visual Studio.

animalelement865 commented 9 years ago

I was using the wrong library. I was using http://serial.codeplex.com/.

animalelement865 commented 9 years ago

When importing the into Unity I now get a bunch of errors I didn't before. I think I might just connect the Apex to an Arduino and read it that way.

Unhandled Exception: System.TypeLoadException: Could not load type 'Apex7000_BillValidator.ApexValidator' from assembly 'Apex7000_BillValidator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

at (wrapper managed-to-native) System.MonoType:GetMethodsByName (string,System.Reflection.BindingFlags,bool,System.Type)

at System.MonoType.GetMethods (BindingFlags bindingAttr) [0x00000] in :0

at Mono.CSharp.MemberCache.AddMethods (BindingFlags bf, System.Type type) [0x00000] in :0

at Mono.CSharp.MemberCache.AddMethods (System.Type type) [0x00000] in :0

at Mono.CSharp.MemberCache..ctor (IMemberContainer container) [0x00000] in :0

at Mono.CSharp.TypeHandle..ctor (System.Type type) [0x00000] in :0

at Mono.CSharp.TypeHandle.GetTypeHandle (System.Type t) [0x00000] in :0

at Mono.CSharp.TypeHandle.GetMemberCache (System.Type t) [0x00000] in :0

at Mono.CSharp.TypeManager.MemberLookup_FindMembers (System.Type t, MemberTypes mt, BindingFlags bf, System.String name, System.Boolean& used_cache) [0x00000] in :0

at Mono.CSharp.TypeManager.RealMemberLookup (System.Type invocation_type, System.Type qualifier_type, System.Type queried_type, MemberTypes mt, BindingFlags original_bf, System.String name, IList almost_match) [0x00000] in :0

at Mono.CSharp.TypeManager.MemberLookup (System.Type invocation_type, System.Type qualifier_type, System.Type queried_type, MemberTypes mt, BindingFlags original_bf, System.String name, IList almost_match) [0x00000] in :0

at Mono.CSharp.Expression.MemberLookup (Mono.CSharp.CompilerContext ctx, System.Type container_type, System.Type qualifier_type, System.Type queried_type, System.String name, MemberTypes mt, BindingFlags bf, Location loc) [0x00000] in :0

at Mono.CSharp.Expression.MemberLookupFinal (Mono.CSharp.ResolveContext ec, System.Type qualifier_type, System.Type queried_type, System.String name, MemberTypes mt, BindingFlags bf, Location loc) [0x00000] in :0

at Mono.CSharp.New.DoResolve (Mono.CSharp.ResolveContext ec) [0x00000] in :0

at Mono.CSharp.Expression.Resolve (Mono.CSharp.ResolveContext ec, ResolveFlags flags) [0x00000] in :0

at Mono.CSharp.Expression.Resolve (Mono.CSharp.ResolveContext ec) [0x00000] in :0

at Mono.CSharp.Assign.DoResolve (Mono.CSharp.ResolveContext ec) [0x00000] in :0

at Mono.CSharp.SimpleAssign.DoResolve (Mono.CSharp.ResolveContext ec) [0x00000] in :0

at Mono.CSharp.Expression.Resolve (Mono.CSharp.ResolveContext ec, ResolveFlags flags) [0x00000] in :0

at Mono.CSharp.Expression.Resolve (Mono.CSharp.ResolveContext ec) [0x00000] in :0

at Mono.CSharp.ExpressionStatement.ResolveStatement (Mono.CSharp.BlockContext ec) [0x00000] in :0

at Mono.CSharp.StatementExpression.Resolve (Mono.CSharp.BlockContext ec) [0x00000] in :0

at Mono.CSharp.Block.Resolve (Mono.CSharp.BlockContext ec) [0x00000] in :0

at Mono.CSharp.ToplevelBlock.Resolve (Mono.CSharp.FlowBranching parent, Mono.CSharp.BlockContext rc, Mono.CSharp.ParametersCompiled ip, IMethodData md) [0x00000] in :0

ghost commented 9 years ago

I'll be taking another look at this this afternoon. Would you be able to tell me a little more about your application?

animalelement865 commented 9 years ago

It's a boxing robot, you physically stand in front of it and fight it. https://www.youtube.com/watch?v=gAQlfu5Na7c

ghost commented 9 years ago

That is actually the coolest thing I've seen this week. I've made some API changes and implemented a fix for the SerialPort as we discussed. Let me know if that helps.

animalelement865 commented 9 years ago

Thanks! I've been working with the new example but I'm still relatively new to C# and there's a lot of code in that example I don't need. I'm trying to just make a basic connection over COM4. When I run this I get the following error. But when I hardcode that same line "COM4", false in the test program it works fine.

InvalidOperationException: Specified port is not open. System.IO.Ports.SerialPort.CheckOpen () System.IO.Ports.SerialPort.set_DiscardNull (Boolean value) (wrapper remoting-invoke-with-check) System.IO.Ports.SerialPort:set_DiscardNull (bool) PTI.Serial.StrongPort..ctor (System.String portName) Apex7000_BillValidator.ApexValidator.Connect () billAcceptor.Awake () (at Assets/billAcceptor.cs:251)

    config = new RS232Config("COM4", false);
    //config = new RS232Config("COM4", IsEscrowMode);
    //config.EscrowTimeoutSeconds = 12;
    validator = new ApexValidator(config);

    // Configure logging - see DebugData_Sample.cs
    /*validator.OnSerialData += config_OnSerialData;
    ConsoleLoggerMaster.ItemsSource = debugQueueMaster;
    ConsoleLoggerSlave.ItemsSource = debugQueueSlave;
    */
    // Configure events and state (All optional) - see StateAndEvents_Sample.cs
    validator.OnEvent += validator_OnEvent;
    validator.OnStateChanged += validator_OnStateChanged;
    validator.OnError += validator_OnError;
    validator.OnCashboxAttached += validator_CashboxAttached;

    // Required if you are in escrow mode - see CreditAndEscrow_Sample.cs
    //validator.OnEscrow += validator_OnEscrow;

    // Technically optional but you probably want this event - see CreditAndEscrow_Sample.cs
    validator.OnCredit += validator_OnCredit;

    // This starts the acceptor - REQUIRED!!
    validator.Connect();
ghost commented 9 years ago

Hmm. How/where are you declaring ApexValidator it in your applcation? Make sure it is a private field or property for your class.

e.g.

class MyApp 
{
     // _validator will not get disposed until MyApp is disposed
     private ApexValidator _validator;
     private RS232Config _config;
     ...
     public MyApp()
     {
         config = new RS232Config("COM4", false);
         validator = new ApexValidator(config);
         validator.Connect();
     }
}

Another option is to wrap the ApexValidator in a Singleton class. See my gist: https://gist.github.com/catodd/00facb401caea8bc6f98

animalelement865 commented 9 years ago

Where is PyramidNETRS232? I've been doing "using Apex7000_BillValidator" after I compiled that project into a dll.

ghost commented 9 years ago

Sorry, that was from the dev branch (we renamed the namespace).

Go ahead and use the "using" you have been using.

animalelement865 commented 9 years ago

DeviceFactory couldn't be found. Where do I find that?

ghost commented 9 years ago

That was a copy paste mistake. Check the gist again, that field should have been called SingleBillValidator. A singleton is just a glorified global variable. There can only ever be one of them (which makes sense in this application). I suggested the singleton because I suspect that Unity is disposing of your BillValidator object. Some folks call singletons an anti pattern but I think this is a reasonable application.

animalelement865 commented 9 years ago

I'm still getting a ton of errors with that code. Starting with:

private ApexValidator {get; set;}

Severity Code Description Project File Line Error CS1519 Invalid token '{' in class, struct, or interface member declaration UnityVS.Overthrow3.CSharp C:\Users\Public\Documents\Unity Projects\Overthrow3\Assets\SingleBillValidator.cs 16

private RS232Config { get; set; } Severity Code Description Project File Line Error CS0116 A namespace cannot directly contain members such as fields or methods UnityVS.Overthrow3.CSharp C:\Users\Public\Documents\Unity Projects\Overthrow3\Assets\SingleBillValidator.cs 17

ghost commented 9 years ago

Well it could be a few things. I put that singleton sample to use in another test app. See the latest release:

https://github.com/PyramidTechnologies/netPyramid-RS-232/releases/tag/v1.1

I recommend removing the old library from your project and using this one.

On Mon, Sep 21, 2015 at 10:26 AM, SaaSier.com notifications@github.com wrote:

I'm still getting a ton of errors with that code. Starting with:

private ApexValidator {get; set;}

Severity Code Description Project File Line Error CS1519 Invalid token '{' in class, struct, or interface member declaration UnityVS.Overthrow3.CSharp C:\Users\Public\Documents\Unity Projects\Overthrow3\Assets\SingleBillValidator.cs 16

private RS232Config { get; set; } Severity Code Description Project File Line Error CS0116 A namespace cannot directly contain members such as fields or methods UnityVS.Overthrow3.CSharp C:\Users\Public\Documents\Unity Projects\Overthrow3\Assets\SingleBillValidator.cs 17

— Reply to this email directly or view it on GitHub https://github.com/PyramidTechnologies/netPyramid-RS-232/issues/1#issuecomment-142049361 .

corytodd commented 9 years ago

@saasier1 see this commit: https://github.com/PyramidTechnologies/netPyramid-RS-232/commit/7fd1a127bfb0521a6e3f243a3afd27b3fc2a429f

Have any ideas on how to keep Unity from killing the background thread?

animalelement865 commented 9 years ago

I just launch a new thread and have the function run a while loop with a small delay at the end of it. When I tried events with the Arduino (serial) I had bad delays. With a thread and a while loop it ran much smoother.

animalelement865 commented 9 years ago

I've spent half the day trying to get this to work. I tried using System.IO.Ports and making a basic connection 9600, even parity, etc. I don't get an error but if (_serialPort.IsOpen) is never running so for some reason the serial port connection isn't happening.

I can't waste anymore time on this. I think I might just hook it up through an Arduino. It's ugly but there's some code out there for that. Baring that the Apex is going on ebay and I'll find something else.

ghost commented 9 years ago

Sadly I too can't seem to pin down the problem. Unity just does not want to leave the port open no matter what I try.

Arduino sounds like a good backup, best of luck. On Sep 23, 2015 22:33, "SaaSier.com" notifications@github.com wrote:

I've spent half the day trying to get this to work. I tried using System.IO.Ports and making a basic connection 9600, even parity, etc. I don't get an error but if (_serialPort.IsOpen) is never running so for some reason the serial port connection isn't happening.

I can't waste anymore time on this. I think I might just hook it up through an Arduino. It's ugly but there's some code out there for that. Baring that the Apex is going on ebay and I'll find something else.

— Reply to this email directly or view it on GitHub https://github.com/PyramidTechnologies/netPyramid-RS-232/issues/1#issuecomment-142815450 .

ghost commented 9 years ago

I'm marking Unity as a non-supported runtime. Further testing is needed to vet the viability of the Mono runtime with this library.

animalelement865 commented 9 years ago

I gave it one last shot today and I think the problem had to do with threading. I put in some print commands to see where things were getting hung up. I'm using OpenNETCF.IO.Ports. port.IsOpen is now responding and if I try to use your test program to connect to COM4 while this is running it hangs. That's what I would expect as this is now tying up COM4. The lights on the Apex don't come on. Am I using your other functions incorrectly? Debug.Log(rData); is displaying 255. Does that sound right?

I have these settings.

port = new SerialPort("COM4");
port.BaudRate = 9600;
port.Parity = Parity.Even;
port.StopBits = StopBits.One;
port.DataBits = 7;

// start thread with function apexLoop() // other functions were taken from Apex provided example.

void apexLoop() { Debug.Log("apexLoop Start"); if (port.IsOpen) { Debug.Log("port is open"); }

    while(true)
    {
        byte[] data;
        data = GenerateNormalMessage();

        port.Write(data, 0, 7);
        //Debug.Log(data);

        // Read serial data until a '\n' character is recieved
        byte rData = (byte)port.ReadByte();

        rawData = rData;
        Debug.Log(rData);
        System.Threading.Thread.Sleep(100);
    }
}

private byte[] GenerateNormalMessage()
{
    //     # basic message   0      1      2      3      4      5    6      7
    //                      start, len,  ack, bills,escrow,resv'd,end, checksum
    Debug.Log("GenerateNormalMessage function");
    var data = Request.BaseMessage;

    // Toggle message number (ack #) if last message was okay and not a re-send request.
    data[2] = (byte)(0x10 | Ack);

    // Not escrow mode, all notes are enabled by default
    data[3] = 0x7F;

    // Clear escrow mode bit
    data[4] = 0x00;

    // Set the checksum
    return Checksum(data);

}

private byte[] Checksum(byte[] msg)
{
    List<byte> tmp = new List<byte>(msg);
    byte checksum = (byte)(msg[1] ^ msg[2]);
    for (int i = 3; i < msg.Length - 1; i++)
    {
        checksum ^= msg[i];
    }

    tmp.Add(checksum);
    return tmp.ToArray();
}

internal struct Request
{
    //   basic message   0      1      2      3      4      5    6      7
    //                   start, len,  ack, bills,escrow,resv'd,end, checksum
    internal static readonly byte[] BaseMessage = { 0x02, 0x08, 0x10, 0x7F, 0x10, 0x00, 0x03 };

    internal static readonly byte[] ResetTarget = { 0x02, 0x08, 0x61, 0x7f, 0x7f, 0x7f, 0x03 };
}
ghost commented 9 years ago

Pulling out the library guts and putting them in your Unity loop is one way to do it. I agree that Unity is getting in the way so this is reasonable workaround :)

Two things to note if you are talking directly with the Apex, not through an Arduino:

1) I don't see where you are toggling the ACK #. That is required. 2) Your port read should take a whole message, not just a single byte. 3) The \n characters is never sent in RS-232. 4) Be explicit with your comm port settings. Handshake should be None and I don't trust that this is set by default. Read/Write timer/buffer size should also be set in case of weird default values. 5) Though not required, I suggest using the 1252 encoder for your serial port. If you at any point wil be reading strings, this will make your life easier.

private readonly System.Text.Encoding W1252 = System.Text.Encoding.GetEncoding("Windows-1252");

overthrowrobotics commented 6 years ago

I was the original poster on this. Got off the phone with tech support because of an error that the device is "Unknown USB Device". With no resolution the only suggestion I was given is to pay for a new USB cable because they have a 30 day warranty. I'm throwing this thing in the trash. I can't go into production and have hundreds of these out there if that's the support I'm getting. Good luck everyone else.

ghost commented 6 years ago

Hey there, sorry to hear that you're having trouble with the harness. I'm not sure how far you got with support but did you already try reinstalling the FTDI driver? I'm assuming you're talking about the RS-232 USB adapter on Windows. http://www.ftdichip.com/Drivers/VCP.htm

overthrowrobotics commented 6 years ago

I did, tried it on 2 different PCs also.

brnwedding commented 6 years ago

First and foremost let me apologize for any trouble you had experienced with our product. Here at Pyramid Technologies we strive to provide the best customer service and technical support above any other. While we can't claim to be perfect in all aspects of this we do make an effort to resolve any issues such as these and value your opinion and position on this.

In response to your post you stated you are having an error "Unknown USB Device". With that I can only assume you are using our RS232 to USB communication harness (05AA0023) correct?

overthrowrobotics commented 6 years ago

Yes, I'm using that harness. I opened it up and one of the wires to the usb was broken. I soldered a row of header pins on it and it showed up in Windows. I then ran the tool on your website and it couldn't find the Apex 7000 even after forcing Com10. I gave up and ripped the harness apart and am now attempting to talk over RS232 using a 32 bit Arduino (Teensy) but am quickly losing patience and may just look for a different dollar bill acceptor.

brnwedding commented 6 years ago

Thank you for your answer and your comments. We are sorry to see you go but respect your decision to look at a different manufacture for your dollar bill acceptor and wish you the best in your future developments.

If you wish you can always contact me directly by email at brian@pyramidacceptors.com or by phone (480) 507-0088 ext. 219 at anytime and I will be more than happy to help.