1

I am a noob at serial programming. I am trying to code a passive monitor in C that displays to screen whatever is written to or read from a COM port. Most of the code, that I have seen actually reads from or writes to the COM Port.

I have tried to read from a COM port that is transmitting and receive Modbus traffic but I get no readings. I am using a com0com serial port emulator. Only time the code works is if I actually read from the other port that the COM port is paired with.

I am trying to mimic the Serial Port Monitor application. So far it is not working. Kindly assist.

Thanks.

Below is the code for the COM read:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void setupPort(HANDLE * handle, char * portName);
void readFromPort(HANDLE * handle);

int main()
{
    HANDLE first_port;
    char * first_port_name = "COM3";
    setupPort(&first_port, first_port_name);
    readFromPort(&first_port);




    return 0;
}

void setupPort(HANDLE * handle, char * portName)
{
    BOOL status;
    *handle = CreateFile(portName,            //port name
                         GENERIC_READ | GENERIC_WRITE, //Read/Write
                         0,            // No Sharing
                         NULL,         // No Security
                         OPEN_EXISTING,// Open existing port only
                         0,            // Non Overlapped I/O
                         NULL);        // Null for Comm Devices


    if (handle == INVALID_HANDLE_VALUE)
    {
        printf("\n%s could not be opened\n", portName);
    }
    else
    {
        printf("\n%s successfully opened.\n", portName);
    }

    DCB dcbSerialParams = { 0 };                         // Initializing DCB structure
    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);

    status = GetCommState(*handle, &dcbSerialParams);      //retreives  the current settings

    if (status == FALSE)
        printf("\n    Error! in GetCommState()");

    dcbSerialParams.BaudRate = CBR_9600;      // Setting BaudRate = 9600
    dcbSerialParams.ByteSize = 8;             // Setting ByteSize = 8
    dcbSerialParams.StopBits = ONESTOPBIT;    // Setting StopBits = 1
    dcbSerialParams.Parity = NOPARITY;        // Setting Parity = None

    status = SetCommState(*handle, &dcbSerialParams);  //Configuring the port according to settings in DCB

    if (status == FALSE)
    {
        printf("\n    Error! in Setting DCB Structure");
    }
    else //If Successful display the contents of the DCB Structure
    {
        printf("\n\n    Setting DCB Structure Successful\n");
        printf("\n       Baudrate = %d", dcbSerialParams.BaudRate);
        printf("\n       ByteSize = %d", dcbSerialParams.ByteSize);
        printf("\n       StopBits = %d", dcbSerialParams.StopBits);
        printf("\n       Parity   = %d", dcbSerialParams.Parity);
    }

    /*------------------------------------ Setting Timeouts --------------------------------------------------*/

    COMMTIMEOUTS timeouts = { 0 };
    timeouts.ReadIntervalTimeout         = 50;
    timeouts.ReadTotalTimeoutConstant    = 50;
    timeouts.ReadTotalTimeoutMultiplier  = 10;
    timeouts.WriteTotalTimeoutConstant   = 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;

    if (SetCommTimeouts(*handle, &timeouts) == FALSE)
        printf("\n\n    Error! in Setting Time Outs");
    else
        printf("\n\n    Setting Serial Port Timeouts Successful");

    /*------------------------------------ Setting Receive Mask ----------------------------------------------*/

    status = SetCommMask(*handle, EV_RXCHAR); //Configure Windows to Monitor the serial device for Character Reception

    if (status == FALSE)
        printf("\n\n    Error! in Setting CommMask");
    else
        printf("\n\n    Setting CommMask successful");
}

void readFromPort(HANDLE * handle)
{
    BOOL status;
    DWORD dwEventMask;                     // Event mask to trigger
    char  TempChar;                        // Temporary Character
    char  SerialBuffer[256];               // Buffer Containing Rxed Data
    DWORD NoBytesRead;                     // Bytes read by ReadFile()
    int i = 0;

    /*------------------------------------ Setting WaitComm() Event   ----------------------------------------*/

    while(TRUE)
    {
        printf("\n\n    Waiting for Data Reception");

        status = TRUE; //Wait for the character to be received

        /*-------------------------- Program will Wait here till a Character is received ------------------------*/

        if (status == FALSE)
        {
            printf("\n    Error! in Setting WaitCommEvent()");
        }
        else //If  WaitCommEvent()==True Read the RXed data using ReadFile();
        {
            printf("\n\n    Characters Received\n");
            do
            {
                status = ReadFile(*handle, &TempChar, sizeof(TempChar), &NoBytesRead, NULL);
                SerialBuffer[i] = TempChar;
                i++;
            }
            while (NoBytesRead > 0);

            /*------------Printing the RXed String to Console----------------------*/

            printf("\n\n    ");
            int j =0;
            for (j = 0; j < i-1; j++)       // j < i-1 to remove the dupliated last character
            {
                printf("%02X", (unsigned int)(unsigned char)SerialBuffer[j]);
            }
            i=0;

        }

        //CloseHandle(*handle);//Closing the Serial Port
        printf("\n +==========================================+\n");
    }

}
kbb
  • 3,193
  • 3
  • 18
  • 15
  • 1
    If you want to monitor serial traffic that some other program is generating you will need to open the port in non-exclusive mode - that is with sharing allowed (and do so before the other program opens the port as it is very likely to open it with sharing disabled). Pass FILE_SHARE_READ|FILE_SHARE_WRITE for the dwShareMode parameter to CreateFile. – SoronelHaetir Jun 10 '20 at 19:43
  • 1
    com0com is accompanied with hub4com that can "route" traffic to multiple destinations. Several years ago I used this with multiple com0com devices to monitor serial communication. Start by drawing a diagram, so that you see what you need. – the busybee Jun 11 '20 at 06:35
  • @SoronelHaetir, thanks for the info. I tried that but it did not work. – kbb Jun 18 '20 at 03:28
  • @thebusybee, I am using com0com as a virtual port only because I have no hardware ports, however the code I am creating must be 'native' in nature so no libraries from com0com. – kbb Jun 18 '20 at 03:29

1 Answers1

1

Your code should work fine (EDIT: as long as you intend to use it together with com0com). As the busybee suggested in the comment above I think you are mixing up your ports or misunderstanding how com0com is supposed to work.

You might have two different scenarios:

1)You are using your Windows PC as a sniffer to monitor the Modbus transactions in between two other parties. For instance a PLC and remote Modbus sensor. In this scenario, you need two real hardware serial ports and a couple of virtual ports provided by com0com.

2)If something in your computer is acting as one of the parties in the Modbus transaction then you only need a hardware serial port and a couple of virtual ports.

Since you mention passive I guess you are on scenario number 1. If so you just need to choose your ports correctly. I wrote a complete example on how to do this very same, coincidentally for Modbus too using Termite and com0com, take a look here. You might also want to take a look to SerialPCAP, which in combination with Wireshark can even decode your Modbus messages.

If you prefer to reinvent the wheel, I guess you can just drop com0com and share the port as somebody else suggested in the comments. There are some interesting questions you might want to read if you decide to follow on this road, see here.

EDIT: You say you do want to reinvent the wheel. That's fine but I think you need to consider some things before you jump into writing code. I'm no expert serial port developer; much less on Windows, and even much less on recent Windows versions. But I did some research on this topic way back so I can give you my view:

-Most of us non-wheelreinventors would be more than happy to monitor our serial ports with the virtual serial port techniques explained above (I will repeat myself once more: for Modbus RTU traffic monitoring, look at Wireshark/SerialPCAP and you'll forget about anything else). My first impression was you wanted to do that (that's why you were talking about com0com). Reading your comments, I guess that's not good enough for you (I can understand that, I tend to prefer clean solutions to dirty tricks).

-Now, having that clear, is there something you can do? From userspace, I don't think you can share a serial port nowadays. The trick on the comment to your question that mentions dwShareMode might have worked back in the 90s, but I'm afraid it won't work anymore. For more details see here.

-If you go to driverland, you might have some chances. Read here. Other useful links: 1,2.

My conclusion is: there is no fix for your code, what you want to do is more involved than what you have.

Marcos G.
  • 3,371
  • 2
  • 8
  • 16
  • thanks for the info. I am trying to reinvent the wheel. I am looking at porting this to a device with an actual hardware after it is completed, to read what goes in and out of that port. So far, I am still unsuccesful with the suggestions. Many Thanks. – kbb Jun 18 '20 at 03:45
  • But... what is this other device? If it runs Windows, it will be OK to go your route. If not, you're on the wrong track. – the busybee Jun 18 '20 at 05:57
  • I'm not at all against reinventing the wheel, I guess sometimes some of us are paid to do that. Anyway, note that the question was obviously targeting Windows (it mentions com0com and there is a `windows.h` in the includes), that's why I linked specific solutions for Windows. The one using SerialPCAP, in particular, works perfectly well on Linux, and it would take several months for a small team to come up with something like that (decoding Modbus frames with all the info). No doubt you would be able to do the same, given the time and resources. – Marcos G. Jun 18 '20 at 06:28
  • I will take a look at my notes and library, I think I might have some useful links. For Linux, by the way, there are many more ways to solve this problem; see, for instance, [this one](https://stackoverflow.com/questions/57153141/cant-use-interceptty-or-slsnif-to-sniff-data-sent-via-serial-port/57157995#57157995) – Marcos G. Jun 18 '20 at 06:30