Blub
Blub

Reputation: 581

COM: Device only sends data when things are changing. How to deal with that?

So I want to connect to a device via Serial that only sends data when things are changing with the settings on the device (a measuring device). I use C# and .Net's SerialPort.

I am able to read data and it looks kind of good. But there are a few problems I encountered.

I realized my reading- method (ReadExistingData()) so that it will constantly use the SerialDataReceivedEventHandler when there's new data. Unfortunately, when I read it like that (probably because of varying package sizes) it will read very chaotically and thus I need to "catch" the first initiating byte (here it's 0xA5). That means, I always check whether the byte I just received is a 0xA5 and if it is, I read the rest.

But I feel like that way I am missing some commands my device sends me and thats why I cant display the data consistently right, only from time to time.

On a side note: The device sends the device time and a value. The value is delayed and kind of unaccurate, but the time is always right on spot. The other parameters it sends are always weirded out and dont seem to be recognized and thus changed at all.

To display data I use the console for testing purposes, and the whole construct seems to be very reactive to Console outputs.

Here's a little code snippet:

class Device
{
    private int stdMessageLengthInBytes = 5;
    public DeviceData processedData;
    byte[] dataBuffer;

    int receivedMessages = 0;

    public Device()
    {
        // Initialize BaseClass (SerialPort derivative)
        this.port = new System.IO.Ports.SerialPort();

        // Initialize Device
        this.data = new byte[stdMessageLengthInBytes];
        this.processedData = new P8005_Data();

        dataBuffer = new byte[stdMessageLengthInBytes];
    }

    // Is supposed to read the data from serial port
    protected override void ReadExistingData()
    {
        // Check whether buffer is empty -> Try to catch a 0xA5
        if (dataBuffer[0] == 0x00)
        {
            port.Read(dataBuffer, 0, 1);
        }

        // If no 0xA5 was catched, restart
        if (dataBuffer[0] != 0xA5)
        {
            dataBuffer = new byte[stdMessageLengthInBytes]; // Reset buffer
            return;
        }

        // Read next byte -> Command byte
        port.Read(dataBuffer, 1, 1);

        // If command was empty, restart
        if (dataBuffer[1] == 0x00)
        {
            dataBuffer = new byte[stdMessageLengthInBytes]; // Reset buffer
            return;
        }

        // If its time that is communicated: Read 3 bytes
        if (dataBuffer[1] == 0x06)
        {
            // 4 ms delay seems to be needed otherwise it wont function correctly somehow
            System.Threading.Thread.Sleep(5);
            port.Read(dataBuffer, 2, 3);
            // Write buffer to actual raw data byte array
            this.data = dataBuffer;
            dataBuffer = new byte[stdMessageLengthInBytes]; // Reset buffer
        }

        // Otherwise: Just read 2 bytes
        System.Threading.Thread.Sleep(5); // Needed delay
        port.Read(dataBuffer, 2, 2);

        // Write buffer to actual raw data byte array
        this.data = dataBuffer;
        dataBuffer = new byte[stdMessageLengthInBytes]; // Reset buffer
    }

    // Method called by SerialDataReceivedEventHandler
    protected override void DataReceivedOnComPort(object sender, SerialDataReceivedEventArgs e)
    {
        bool valid = false;

        ReadExistingData(); // Read data from COM- Port

        lock (processedData)
        {
            switch (data[1]) // Check command byte
            {
                // Time (3 btyes)
                case (0x06):
                    processedData.currentTime = String.Format("{0:D2}:{1:D2}:{2:D2}", DecodeBcd(data[2]), DecodeBcd(data[3]), DecodeBcd(data[4]));

                    valid = true;
                    receivedMessages++;
                    break;

                // Value (2 bytes)
                case (0x0D):
                    double val = 0;
                    val += DecodeBcd(data[2]) * 100;
                    val += DecodeBcd(data[3]);
                    val /= 10;
                    processedData.currentValue = val;

                    valid = true;
                    receivedMessages++;
                    break;

                // ... here are various other hex- code that represent a command from the device (2 btyes)

                default:
                    valid = false;
                    break;
            }
        }

        // only to check when 
        if (valid)
        {
            Console.WriteLine("Received Valid Messages: {0}", receivedMessages);
            ConsoleOutput();
        }
    }
}

On a note: The initialization of the port happens in another method from the base class and works fine.

Is there anything I am missing? How to deal with something like that? Are there any improvements that would help improving my performance? I thought about threading with locks, but I dont think that is the solution somehow... Or maybe everything is just a console problem?


EDIT:

I know changed my code (as @jdweng suggested) so that I put everything in a buffer (basically List<byte> mainBuffer. Then, I take all bytes in the buffer whenever its possbile and work with them, skimming it for 0xA5. When one is found, I read the command and determine how long the "message" has to be according to it (Time -> +3 bytes, Data -> +2 bytes, Other -> +1 byte). Then I can work off those messages (I put them into a List<byte[]>) and determine my output to my screen.

However, even after outsourcing the chopping up into messages and processing the messages, I still seem to either miss some messages, since some action are just not registered and have a big delay, or my processing is wrong. What I can think of is that because I lock my mainBuffer maybe some data isnt written to it.

Is this really this time critical? There is a software that comes with the device and it doesnt seem to have such big problems with delay and slightly wrong values...

Upvotes: 0

Views: 79

Answers (1)

Night94
Night94

Reputation: 975

Since you don't have the exact specs and/or an unreliable connection (which with serial data has to be expected) you need to sync to the 0xa5 at every message. I would just run every single byte you receive through a parser while keeping the state of the currently received message.

Make sure you validate your input since there are a bunch of things that can go wrong if you get messed up serial data. For example if there is an 0xa5 in the other message types, you might miss your next message. To prevent that I strongly recommend to either get to the specs if possible or code more logic based on data observations.

private const int MESSAGE_LENGTH = 5;
private const int VALUE_COMMAND = 0x0D;
private const int VALUE_SIZE = 4;
private const int TIME_COMMAND = 0x06;
private const int TIME_SIZE = 5;
private byte[] _message = new byte[MESSAGE_LENGTH];
private int _messagePos = 0;
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    var data = new byte[_serialPort.BytesToRead];
    _serialPort.Read(data, 0, data.Length);
    foreach (var b in data)
    {
        _message[_messagePos] = b;
        if (_messagePos == 0 && b != 0xa5)
            continue;
        ++_messagePos;
        if (_messagePos > 2)    // if command byte present, process command of any size
            ProcessCommand(_message[1]);
    }
}

private void ProcessCommand(byte command)
{
    if (_messagePos == VALUE_SIZE && command == VALUE_COMMAND)
    {
        // parse value...
        _messagePos = 0;
    }
    else if (_messagePos == TIME_SIZE && _message[1] == TIME_COMMAND)
    {
        // parse time...
        _messagePos = 0;
    }
}

Upvotes: 1

Related Questions