Serial Port Monitor

From Nutwiki
Revision as of 17:03, 27 October 2016 by Harald (Talk | contribs) (1 revision imported)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Test Environments

Hardware Comments
Ethernut 1.3 H
Ethernut 2.1 B
Ethernut 3.0 E
Compiler: ARM-GCC 4.2.2 ; AVR-GCC 4.3.0

Description

This application uses an Ethernut Board as an RS-232 sniffer. For example, it can be used to trace PPP connection problems or re-engineer an unknown protocol between two devices like a modem, programming adapter, PC etc. To keep it simple, all configuration is hard coded and nothing but the baudrate is configurable on the RS-232 interface.

Other embedded boards may be used as well, as long as they provide two UARTs. Each UART receiver is connected to one of the transmit data lines of the two devices to monitor. The transmitted data is sent to a network client, which typically runs on a PC. The simple client, which is presented here as well, will store the data in a file. Another program, which is not part of this application sample, may interpret this file.

Source Code

Nut/OS Firmware

Lines 19-21: You only need to modify these values for virgin boards running in LANs without DHCP. Typically Nut/OS will read the settings from non-volatile memory or retrieve them from the local DHCP server.

Lines 24-25: These values work fine for all Ethernuts. On 32-bit boards with more RAM they may be increased to provide larger buffers for high traffic protocols. Note that the value of READ_BUFFER_SIZE + 2 must fit into a single byte.

Line 42: This is the baudrate used for monitoring the RS-232 communication.

Lines 163-183: Two instances of this thread are running concurrently, one for each UART input.

Lines 207-232: The main thread basically contains two nested loops. The outer loop handles client connects and the inner loop transfers buffered data to a connected client.


<source line lang="c">

  1. include <cfg/os.h>
  2. include <dev/board.h>
  1. include <sys/thread.h>
  2. include <sys/timer.h>
  3. include <sys/event.h>
  4. include <sys/socket.h>
  1. include <stdlib.h>
  2. include <stdio.h>
  3. include <string.h>
  4. include <io.h>
  5. include <fcntl.h>
  1. include <arpa/inet.h>
  2. include <pro/dhcp.h>

/* Default values are used if non-volatile memory is empty. */

  1. define DFLT_MAC ((uint8_t *)"\x00\x06\x98\x29\x29\x29")
  2. define DFLT_IP "192.168.192.111"
  3. define DFLT_MSK "255.255.255.0"

/* Buffer sizes. The read size must be less than 254. */

  1. define DATA_BUFFER_SIZE 8192
  2. define READ_BUFFER_SIZE 196

/* Data buffer with number of free bytes and read/write index and queue. */ static uint8_t data_buffer[DATA_BUFFER_SIZE]; static uint_fast16_t data_avail; static uint_fast16_t data_rpos; static uint_fast16_t data_wpos; static HANDLE data_rdq; static HANDLE data_wrq;

/* Monitored device configuration structure. */ typedef struct _MONPARMS {

   uint32_t mdp_baud;

} MONPARMS;

/* UART settings, same for both interfaces. */ static MONPARMS monparms = {

   115200                  /* Baudrate. */

};

/* Monitored device structure. */ typedef struct _MONDEV {

   uint8_t mdv_id;         /* Unique ID. */
   char *mdv_name;         /* Device name. */
   MONPARMS *mdv_parms;    /* Device configuration. */

} MONDEV;

/* First UART device input to monitor. */ static MONDEV mondev0 = {

   0,
   DEV_UART0_NAME,
   &monparms

};

/* Second UART device input to monitor. */ static MONDEV mondev1 = {

   1,
   DEV_UART1_NAME,
   &monparms

};

/*

* Clear circular data buffer.
*/

static void ClearDataBuffer(void) {

   data_rpos = 0;
   data_wpos = 0;
   data_avail = DATA_BUFFER_SIZE;
   /* Signal availability of buffer space. */
   NutEventPost(&data_wrq);

}

/*

* Add block to circular data buffer.
*/

static void PutDataBuffer(CONST uint8_t *data) {

   uint_fast8_t i;
   uint_fast8_t len = data[1] + 2;
   /* Wait until enough buffer space is available. */
   while (len > data_avail) {
       NutEventWait(&data_wrq, 0);
   }
   /* Mover the data block to the buffer. */
   for (i = 0; i < len; i++) {
       data_buffer[data_wpos++] = *data++;
       if (data_wpos >= DATA_BUFFER_SIZE) {
           data_wpos = 0;
       }
   }
   /* Signal availability of data. */
   data_avail -= len;
   NutEventPost(&data_rdq);

}

/*

* Retrieve next block from circular data buffer.
*/

static int GetDataBuffer(uint8_t *data, uint32_t tmo) {

   uint_fast8_t rc;
   uint_fast16_t i;
   /* Wait until data is available. */
   while (data_avail >= DATA_BUFFER_SIZE) {
       if (NutEventWait(&data_rdq, tmo)) {
           return 0;
       }
   }
   /* First byte contains an ID. */
   *data++ = data_buffer[data_rpos++];
   if (data_rpos >= DATA_BUFFER_SIZE) {
       data_rpos = 0;
   }
   /* Second byte contains the number of data bytes in this block. */
   *data = data_buffer[data_rpos++];
   if (data_rpos >= DATA_BUFFER_SIZE) {
       data_rpos = 0;
   }
   rc = *data++;
   /* Move the data block to the caller's read buffer. */
   for (i = 0; i < rc; i++) {
       *data++ = data_buffer[data_rpos++];
       if (data_rpos >= DATA_BUFFER_SIZE) {
           data_rpos = 0;
       }
   }
   /* Signal availability of buffer space. */
   rc += 2;
   data_avail += rc;
   NutEventPost(&data_wrq);
   return rc;

}

/*

* Enable LAN interface.
*/

static int InitNetwork(CONST char *name, uint8_t *mac, uint32_t ip, uint32_t msk) {

   int rc = 0;
   if (NutDhcpIfConfig(name, 0, 60000)) {
       /* No valid EEPROM contents, use hard coded MAC. */
       if (NutDhcpIfConfig(name, mac, 60000)) {
           /* No DHCP server found, use hard coded IP address. */
           rc = NutNetIfConfig(name, mac, ip, msk);
       }
   }
   return rc;

}

/*

* This thread reads data from a UART device and adds it to a circular buffer.
*/

THREAD(RxUart, arg) {

   /* Creator passes a MONDEV structure. */
   MONDEV *mdv = (MONDEV *)arg;
   /* Open the UART device. */
   int fd = _open(mdv->mdv_name, _O_RDONLY | _O_BINARY);
   /* Allocate a local read buffer. */
   uint8_t *buff = malloc(READ_BUFFER_SIZE + 2);
   /* Configure the UART, for now we only support baudrate. */
   _ioctl(fd, UART_SETSPEED, &mdv->mdv_parms->mdp_baud);
   /* The first byte in each data block contains an ID. */
   buff[0] = mdv->mdv_id;
   /* Endless loop reads data from UART and passes it to the circular buffer. */
   for (;;) {
       buff[1] = (uint8_t)_read(fd, buff + 2, READ_BUFFER_SIZE);
       PutDataBuffer(buff);
   }

}

/*

* This Nut/OS application monitors two UART inputs and sends
* all incoming data to a TELNET client, if one is connected.
*/

int main(void) {

   /* Allocate a local read buffer. */
   uint8_t *buff = malloc(READ_BUFFER_SIZE);
   /* Register all devices which we will use. */
   NutRegisterDevice(&DEV_UART0, 0, 0);
   NutRegisterDevice(&DEV_UART1, 0, 0);
   NutRegisterDevice(&DEV_ETHER, 0, 0);
   /* Set up the network interface. */
   InitNetwork(DEV_ETHER_NAME, DFLT_MAC, inet_addr(DFLT_IP), inet_addr(DFLT_MSK));
   /* Create two concurrent threads for monitoring two UART inputs. */
   NutThreadCreate("rxu0", RxUart, &mondev0, NUT_THREAD_MAINSTACK);
   NutThreadCreate("rxu1", RxUart, &mondev1, NUT_THREAD_MAINSTACK);
   /* Outer loop waits for clients to connect, endlessly. */
   for(;;) {
       FILE *fsp;
       /* Create a socket and wait for a client. */
       TCPSOCKET *sock = NutTcpCreateSocket();
       NutTcpAccept(sock, 23);
       /* Client connected. Create an associated stream. */
       fsp = _fdopen((int) sock, "r+b");
       /* Start with a fresh buffer and pass new contents to the client. */
       ClearDataBuffer();
       for (;;) {
           int got = GetDataBuffer(buff, 100);
           if (got) {
               fwrite(buff, 1, got, fsp);
           } else if (fflush(fsp)) {
               break;
           }
       }
       /* Client disconnected. Clean up. */
       fclose(fsp);
       NutTcpCloseSocket(sock);
   }
   /* Never reached, but keeps compiler happy. */
   return 0;

} </source>


Client Software

So far the client had been tested on Windows only, but may also compile for Linux or OS X with minor or no modification.

Lines 45-57: Establish a connection to the Ethernut at TCP port 23.

Lines 60-75: Store all incoming TCP data in a file.


<source line lang="c">

  1. ifdef _WIN32

typedef int socklen_t;

  1. include <winsock.h>
  2. else
  3. include <netdb.h>
  4. include <sys/socket.h>
  5. include <arpa/inet.h>

typedef int SOCKET;

  1. define INVALID_SOCKET -1
  2. define SOCKET_ERROR -1
  3. define closesocket(s) close(s);
  4. endif
  1. include <stdio.h>
  1. define MY_VERSION "0.0"
  2. define TCP_RXBUFF_SIZE 8192

int main(int argc, char **argv) {

   int rc = 0;
   FILE *fp;
   unsigned long ip;
   unsigned short port = 23; 
   SOCKET sock;
   struct sockaddr_in server;
   unsigned char txbuf[TCP_RXBUFF_SIZE];
   int got;
   int total = 0;
  1. if defined WIN32
   /* Initialize winsock. */
   WSADATA wsa_data;
   WSAStartup(0x0101, &wsa_data);
  1. endif
   /* Print banner and check command line. */
   fputs("netsermon " MY_VERSION "\n", stderr);
   if (argc != 3 || (ip = inet_addr(argv[1])) == -1) {
       fprintf(stderr, "Usage: netsermon <target-ip> <file>\n");
       return 1;
   }
   /* Establish a TCP connection to the Ethernut node. */
   if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
       fprintf(stderr, "Error: Can't create tcp socket.\n");
       return -1;
   }
   memset(&server, 0, sizeof(server));
   server.sin_family = AF_INET;
   server.sin_port = htons(port);
   server.sin_addr.s_addr = ip;
   fprintf(stderr, "Receiving %s from %s:%u\n", argv[2], inet_ntoa(server.sin_addr), port);
   if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0) {
       fprintf(stderr, "Error: Connection failure.\n");
       return -1;
   }
   /* Transfer incoming TCP data to a file. */
   if ((fp = fopen(argv[2], "wb")) == NULL) {
       fprintf(stderr, "Error: Can't open file.\n");
       rc = -1;
   } else {
       for (;;) {
           got = recv(sock, txbuf, TCP_RXBUFF_SIZE, 0);
           if (got <= 0) {
               break;
           }
           fwrite(txbuf, 1, got, fp);
           fflush(fp);
           total += got;
           fprintf(stderr, "\r%d bytes", total);
       }
       fclose(fp);
   }
   /* Clean up. */
   closesocket(sock);
   fprintf(stderr, "\nConnection lost\n");
   return rc;

} </source>


To start the program, enter

 netsermon 192.168.192.111 proto.dlog

on a command line, replacing 192.168.192.111 by the IP address of the Ethernut Board and proto.dlog by the name of the file you want your collected data being stored.

To stop the program, simply press Ctrl-C.

Analyzing Software

No analyzing software is presented here, because it will heavily depend on your requirements. Anyway, it shouldn't take too much effort to create something simple that would fit your needs.

The contents of the file written by the client is divided into blocks. In turn, each block consists of three parts. The first byte contains an ID, 0 for data retrieved at UART0 and 1 for data retrieved at UART1. The next byte contains the number of data bytes in this block and the following bytes contain the raw data. Here is an example of a data block:

0x01 0x05 0x48 0x45 0x4C 0x4C 0x4F

This block contains 5 bytes retrieved from UART1.

See also

External Links


Template:Languages