3

I am very new to c#, and I'm creating a serial port class for a board I have designed. In which this class contains methods to open/close a serial port connected to the board. It should also read messages from the board and write messages from the UI to the board (I am using a forms application to input and display values).

I read the internal input buffer and place the bytes into my own software buffer, when a message is complete, this will prompt the form to analyse the message...

For this I have created an indexer to point to the array (from the form) and take the bytes that it desires.

    uint[] serialPortReceiveBuffer = new uint[3];
    public delegate void Del();
    Del promptFormAction = Form1.MsgReceived;

    public void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {   
        for (int i = 0; i <= 2; i++) 
        {
            serialPortReceiveBuffer[i] = (uint)serialPort1.ReadByte();
        }

        promptFormAction();  
    }

    public uint this[uint i]
    {
        get { return serialPortReceiveBuffer[i]; }
    }

this is the code within my pcbSerialPort class, and the code related to it in the Form1 class is as follows:

    public static void MsgReceived()
    {
        Form1 _frm = new Form1();
        _frm.analyzeIncomingMessage();
    }

    public void analyzeIncomingMessage()
    {
        if (PCB[0] == 63)
        {
            setBoardDesignator(PCB[1], PCB[2]);
        }
    }

My problem is that the when I use the indexer to access the serialPortReceiveBuffer, it doesn't see the changes that I made to it when placing received bytes into the same array. For example, when I receive a string of my own protocol --> "?10" the buffer is filled with [63][49][48]

Though when I try to access this buffer using the indexer I get [0][0][0]

Please can anyone help? Also, I'm aware there is probably a few other things I could have done better so if you have any general tips that would be great. Also in a language I may understand. I am just getting my head around many of the c# aspects, I have been doing embedded software for the past year but I wouldn't consider my self to be a competent programmer.

Thank you

Sender
  • 6,660
  • 12
  • 47
  • 66
  • Where is `PCB` defined? Are you sure you are looking at the same instance? Are you sure that the `DataRecieved` event is even being triggered? IIRC, there is a threshold (number of bytes) that has to be met before that event will fire. – Matt Burland Jul 22 '15 at 13:56
  • Hi thank you for responding, yes the DataReceived event is definitely being triggered in debug mode i can see that my PCB has successfully sent the message "?10" and that the correct values have been stored in the array. I put a marker at the side of promptFormAction(); so i would see the array in it's complete form. Then put a marker within the indexer also and pressed play only to see that the array was empty. – Jonathan Brown Jul 22 '15 at 13:58
  • PCB is defined within the Form1 class at the top like so: pcbSerialPort PCB = new pcbSerialPort(); – Jonathan Brown Jul 22 '15 at 14:03
  • 1
    @JonathanBrown And there you have it. By creating a new instance in your `MsgReceived` method you work with a plain new instance of the byte array. You should look into how to properly use events as in my answer, which will rid you of this problem. – Thorsten Dittmar Jul 22 '15 at 14:08
  • @ThorstenDittmar answer is good, and you should read it. At the absolute minimum, if you don't want to take Thorsten's advice and redesign it, you need to pass the instance of `pcbSerialPort` that received the message to your static `MsgReceieved` function and then pass it to the constructor of `Form1` so it can store the reference instead of creating a new (and separate) one. – Matt Burland Jul 22 '15 at 14:15
  • @MattBurland I highly suggest the redesign, as creating a new instance of a *form* may be very much overhead to just analyze some data. I wonder why he didn't put all that code into the form in the first place... – Thorsten Dittmar Jul 22 '15 at 14:21
  • @ThorstenDittmar: It really depends on the usage and the rate that data is sent. If the board only occasionally fires off a message, the overhead of instancing a form might be insignificant. Of course, if it's firing off multiple messages in the space of a few seconds, then it's absolutely a terrible idea and you'll be filling your screen with annoying popups. – Matt Burland Jul 22 '15 at 14:25
  • @MattBurland Well, I guess we agree that his solution *is* bad design, but you're right saying that `Form1` is a class like any other. He *will* run into trouble eventually depending on the other stuff his form does upon initialization. – Thorsten Dittmar Jul 22 '15 at 14:30
  • I am all up for a redesign!! like I said I am new to this and I'm trying to learn. The reasons for doing the certain things that I have, are not because i thought they were the best solution but simply because I know no other way and I am trying to get it to work. Thank you for your answers I will read them carefully. – Jonathan Brown Jul 22 '15 at 14:42
  • Thank you guys, the more i go through this, the more it all makes sense. The hardest part being that i didn't understand how to even create an event before. Just have to read and learn – Jonathan Brown Jul 22 '15 at 15:49

2 Answers2

1

From your code I'm not quite sure that the PCB object you're working with in your form is actually the one that receives the data. Might well be that you're working with two different instances, especially as you're creating a new instance of Form1 whenever data comes in!

(EDIT: From your comment to the question it is clear that this is exactly the problem. Follow these instructions to get closed to what you want).

I suggest that you redesign your code to pass the received message as an event to the existing form instance instead of how you do it now. Another problem you might run into will be that data you think you get will be overridden by the next message coming in, das the DataReceived event is asynchronous.

I'd declare an event that the form instance can subscribe to, passing the data to be analyzed into the event:

 public class MessageReceivedEventArgs: EventArgs
 {
     public MessageReceivedEventArgs(byte[] data) : base()
     {
         Data = data;
     }

     public byte[] Data
     {
         get;
         private set;
     }
}

public event EventHandler<MessageReceivedEventArgs> MessageReceived;

Then, I'd change your DataReceivedevent as follows:

public void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{   
    for (int i = 0; i <= 2; i++) 
    {
        serialPortReceiveBuffer[i] = (uint)serialPort1.ReadByte();
    }

    byte[] dataCopy = new byte[serialPortReceiveBuffer.Length];
    Array.Copy(serialPortReceiveBuffer, dataCopy, dataCopy.Length);

    promptFormAction(dataCopy);  
}

private void promptForAction(byte[] data)
{
    if (MessageReceived != null)
        MessageReceived(this, new MessageReceivedEventArgs(data));
}

Also I'd keep the serialPortReceiveBuffer totally private to that class, as I said, you may run into synchronization issues if you don't. That'y why I copy the array before passing it to the event.

This change allows any subscriber to register for notifications whenever you realize that new data came in.

To use this, Form1 should look like this (roughly);

public class Form1
{
    pcbSerialPort PCB; // The name of that class I don't know from your code

    public Form1()
    {
        PCB = new pcbSerialPort();
        PCB.MessageReceived += MessageReceived;
    } 

    private void MessageReceived(object sender, pcbSerialPort.MessageReceivedEventArgs e)
    {
        analyzeIncomingMessage(e.Data);
    }

    private void analyzeIncomingMessage(byte[] data)
    {
        if (data[0] == 63)
        {
            setBoardDesignator(data[1], data[2]);
        }
    }
}

Another piece of advice on how you handle serial data: You need to decide whether you read from a serial port in a loop or whether you rely on the DataReceived event. Putting a loop into the event is not a good idea, as the event may be called upon arriving data again while you're waiting.

What you need to do is create a buffer that takes all the information from the serial port that's available. If you don't have enough data, don't wait for it. Instead add to the buffer whenever DataReceived is called and handle a message when enough data is present.

Thorsten Dittmar
  • 55,956
  • 8
  • 91
  • 139
  • thanks @ThorstenDittmar trying to get my head around it, firstly I will try and implement it in my code. I am getting a problem with 'the byte[] dataCopy = serialPortReceiveBuffer.Copy();' statement it sais there is no overload for the copy method – Jonathan Brown Jul 22 '15 at 14:31
  • Ah, ok. I will do it differently and update my answer in a moment. – Thorsten Dittmar Jul 22 '15 at 14:39
  • Error 1 Inconsistent accessibility: parameter type 'WindowsFormsApplication1.pcbSerialPort.MessageReceivedEventArgs' is less accessible than method 'WindowsFormsApplication1.Form1.MessageReceived(object, WindowsFormsApplication1.pcbSerialPort.MessageReceivedEventArgs)' D:\Users\jbrown\Documents\Home work visual\New HMI Design\HMI_071-02\Form1.cs 153 21 HMI_071-02 – Jonathan Brown Jul 22 '15 at 14:52
  • i get that error with regards to the message received method in Form1 class – Jonathan Brown Jul 22 '15 at 14:53
  • I guess that the `pcbSerialPort` class is not public. You can safely change the access modifier of both `MessageReceived` and `analyzeIncomingMessage` to `private` as I will do in my code. Reason you get this error is that you can not pass something that has restricted accessibility to something that has public accessibility. – Thorsten Dittmar Jul 22 '15 at 14:54
0

I think Thorsten's answer is good and it would make a lot of sense to redesign it along those lines, but as an absolute minimum, if you want to have it such that you create a new Form1 instance for every received message, then you will need to pass the instance of pcbSerialPort to MessageReceived and then to the constructor of your Form1 class. Something like:

Action<pcbSerialPort> promptFormAction = Form1.MsgReceived;

public void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{   
    // as Thorsten noted, you need to rethink this loop anyway
    // what if there aren't at least three bytes to read?
    for (int i = 0; i <= 2; i++) 
    {
        serialPortReceiveBuffer[i] = (uint)serialPort1.ReadByte();
    }

    promptFormAction(this);  
}

And your static method:

public static void MsgReceived(pcbSerialPort pcb)
{
    Form1 _frm = new Form1(pcb);
    _frm.analyzeIncomingMessage();
}

And you constructor for Form1:

public Form1(pcbSerialPort pcb)
{
    PCB = pcb;
}
Matt Burland
  • 44,552
  • 18
  • 99
  • 171
  • I guess we agree on the fact that there are plenty of reasons **not** to do it his way, one of them being that the actually UI independent class suddenly has a connection to the UI as the needs to hardcode the `Form1.MsgReceived` method. – Thorsten Dittmar Jul 22 '15 at 14:28
  • @ThorstenDittmar: I assume (perhaps) that the OP's thinking was along the lines of being able to inject an `Action` to handle the data received. Of course, it's hard coded both here and in the OP's original code, but you could make `promptFormAction` a property, or set it with a constructor argument and you remove the hardcoded coupling to the UI. – Matt Burland Jul 22 '15 at 14:34
  • I don't want to have to create a new instance of Form1 every time a message is received. I was just doing that because I didn't know any other way. I am learning but It's reading about things is different to actually implementing them. Where I may be given an example, when I try it out it doesn't necessarily work – Jonathan Brown Jul 22 '15 at 14:35
  • @ThorstenDittmar: Thanks to your help it is now working, i understand what it is you have done now and it definitely makes sense! Regards – Jonathan Brown Jul 23 '15 at 09:16