This application will buffer and send lines from up to three serial devices and USB host when connected to via TCP, with telnet or netcat. Written, 02/10/2011-2/14/2011 by Graham Cantin & Special Guest Appearance from Sasha Jevtic (mostly Sasha)

Dependencies:   EthernetNetIf MODDMA MODGPS MODSERIAL NTPClient mbed

main.cpp

Committer:
kamilion
Date:
2014-11-06
Revision:
1:29f6c660d174
Parent:
0:5d5265391846

File content as of revision 1:29f6c660d174:

// Telnet UART MUX server
// This application will buffer and send lines from up to 
// three serial devices and USB host when connected to via TCP.
// Written, 02/10/2011-2/14/2011 by Graham Cantin and
// Special Guest Appearance from Sasha Jevtic (Patron Diety of Queues)

/*
"Have you looked on the underside of the mBed?"
There's a chip marked "mBed Interface" which is an 8-bit
microcontroller in it's own right and an AT45DB161 16Mbit flash device. 
It's the "backend" of the mBed system you are seeing, 
not the target LPC1768 device on the top side of the mBed.
This interface exposes the flash device as a FAT partition over USB, 
and also handles resetting the LPC1768 when a serial break occurs.
*/

// 02/14/2011: Added a hard fault handler. Please improve if you've got a bit of time.

// ------ Defines ------

// DEBUG LOGGING
#define DEBUG

// DHCP OR STATIC IP?
#define DHCP

// What port do we listen on?
#define TCP_LISTENING_PORT 3001

// 9600, 14400, 19200, 38400, 57600, 115200, 230400, 460800 (too fast), 921600 (way too fast)
#define HOST_BAUD 115200
#define MODSERIAL_DEFAULT_RX_BUFFER_SIZE 2048
#define MODSERIAL_DEFAULT_TX_BUFFER_SIZE 384 
#define MESSAGE_BUFFER_SIZE 256
#define TS_PAD 10
#define TX_CHUNK 30
#define MAX_LINES_PER_ITER 10
#define Q_ALERT_THRESH 70

// ------ Includes ------

#include "mbed.h"
#include "line_util.h"
#include "EthernetNetIf.h"
#include "TCPSocket.h"
#include "NTPClient.h"
#include "MODDMA.h"
#include "MODSERIAL.h"
#include "GPS.h"

// ------ Constants ------

const char BannerText[] = "**** Software Build Date : "__DATE__" "__TIME__" ****\r\n";
const char REC_SEP[] = "\r\n";

// ------ Reset & Crash ------

extern "C" void mbed_reset(); // Ask for soft-reset (like pressing the button)
extern "C" void NVIC_SystemReset(); // Force a hard reset by calling the low level raw function.

// Handles a hard fault with a reset. SEE: http://mbed.org/forum/mbed/post/2843/
extern "C" void HardFault_Handler() { printf("HARD FAULT!\r\n"); NVIC_SystemReset(); }
// SEE: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337g/Babcefea.html
// Could be handled better.
// MBED WATCHDOGS: http://mbed.org/forum/mbed/post/3276/


// ------ Timers -----
// Note that timers are based on 32-bit int microsecond (us) counters, 
// so can only time up to a maximum of 2^31-1 microseconds i.e. 30 minutes.
Timer highres; // for reading

// ------ LEDS -----

PwmOut led1(LED1, "led1");
PwmOut led2(LED2, "led2");
PwmOut led3(LED3, "led3");
PwmOut led4(LED4, "led4");

// ------ Buttons -----

// Define some interrupts for buttons.
InterruptIn P21(p21);
InterruptIn P22(p22);
InterruptIn P23(p23);
InterruptIn P24(p24);

#define BUTTON_MESSAGE_SEND(x,y) \
    printf("$MBEDE,%d,BUTTON,%02d,%d*FF\r\n",time(NULL),x,y);

void pin21Rise(void) { BUTTON_MESSAGE_SEND(21, 1); }
void pin21Fall(void) { BUTTON_MESSAGE_SEND(21, 0); }
void pin22Rise(void) { BUTTON_MESSAGE_SEND(22, 1); }
void pin22Fall(void) { BUTTON_MESSAGE_SEND(22, 0); }
void pin23Rise(void) { BUTTON_MESSAGE_SEND(23, 1); }
void pin23Fall(void) { BUTTON_MESSAGE_SEND(23, 0); }
void pin24Rise(void) { BUTTON_MESSAGE_SEND(24, 1); }
void pin24Fall(void) { BUTTON_MESSAGE_SEND(24, 0); }


void buttonSetup() {
  // Enable pullup resistors on pins.
  P21.mode(PullUp); P22.mode(PullUp); P23.mode(PullUp); P24.mode(PullUp);
    
  // Fix Mbed library bug, see http://mbed.org/forum/bugs-suggestions/topic/1498
  // Prevents pullup interrupts from firing once on below attachment during startup.
  LPC_GPIOINT->IO2IntClr = (1UL << 5) | (1UL << 4) | (1UL << 3) | (1UL << 2); 
    
  // Attach InterruptIn pin callbacks.
  P21.rise(&pin21Rise); P21.fall(&pin21Fall);
  P22.rise(&pin22Rise); P22.fall(&pin22Fall);
  P23.rise(&pin23Rise); P23.fall(&pin23Fall);
  P24.rise(&pin24Rise); P24.fall(&pin24Fall);
}

// ------ Ethernet -----

#ifdef DHCP
EthernetNetIf eth; // Use DHCP from router
#else
EthernetNetIf eth(
  IpAddr(10,4,0,212), //IP Address
  IpAddr(255,255,255,0), //Network Mask
  IpAddr(10,4,0,1), //Gateway
  IpAddr(10,0,0,7)  //DNS
); // Define a static IP to run with for early crossover cable debugging.
#endif

NTPClient ntp; // Object for the ntpclient

void rtcSetup() {
  Host server(IpAddr(), 123, "0.us.pool.ntp.org");
  ntp.setTime(server);
  time_t ctTime;
  ctTime = time(NULL);  
  printf("$MBEDE,%d,TIME_SET_NTP,%s",time(NULL), ctime(&ctTime)); 
}

Host client; // a Host (IP address) object named client; the one connecting to us.
TCPSocket ListeningSock; // Actual listening socket
TCPSocket* pConnectedSock; // pointer for ConnectedSock
TCPSocketErr err; // Error slot for TCP Socket

bool clientExists = 0;

void onConnectedTCPSocketEvent(TCPSocketEvent e) {
  switch(e) {
    case TCPSOCKET_WRITEABLE:
//      printf("$MBEDE,%d,IP,CONNECTED,TCP_SOCKET_SENDING_DATA*FF\r\n",time(NULL));
      led1 = 1.0; // Blink an xmit
      led1 = 0.10;
      break;
    case TCPSOCKET_READABLE:
      printf("$MBEDE,%d,IP,CONNECTED,TCP_SOCKET_READABLE*FF\r\n",time(NULL));
      led2 = 1.0; // Blink a recv
      led2 = 0.10;
      break;
    // The following cases generally should not happen often, but are explicitly covered anyway
    case TCPSOCKET_CONTIMEOUT:
    case TCPSOCKET_CONRST:
    case TCPSOCKET_CONABRT:
    case TCPSOCKET_ERROR:
    case TCPSOCKET_DISCONNECTED:
      printf("$MBEDE,%d,IP,CONNECTED,TCP_SOCKET_DISCONNECTED*FF\r\n",time(NULL)); 
      pConnectedSock->close();
      clientExists = 0;
      SLine_put_control(false);
      SLine_clear();
      led4 = 0.0; // Shut off LED4 (Connected)
      led1 = 0.0; // Shut off LED1 (Xmit)
      break;
    default:
      break;
  }
}


void onListeningTCPSocketEvent(TCPSocketEvent e) {
  switch(e) {
    case TCPSOCKET_ACCEPT:
      printf("$MBEDE,%d,IP,LISTENING,TCP_SOCKET_ACCEPTED*FF\r\n",time(NULL));
      // Accepts connection from client and gets connected socket. 
      err=ListeningSock.accept(&client, &pConnectedSock);
      if (err) {
          printf("$MBEDE,%d,IP,onListeningTcpSocketEvent,Could not accept connection*FF\r\n",time(NULL));
          return; //Error in accept, discard connection (nmap/netcat -z portscanning us?)
      }
      // Setup the new socket events
      pConnectedSock->setOnEvent(&onConnectedTCPSocketEvent);
      // We can find out where the connection is coming by looking at the
      // Host parameter of the accept() method
      IpAddr clientIp = client.getIp();
      printf("$MBEDE,%d,IP,LISTENING,TCP_CONNECT_FROM,%d.%d.%d.%d*FF\r\n",time(NULL), 
         clientIp[0], clientIp[1], clientIp[2], clientIp[3]);
      clientExists = 1; // Mark ourselves connected.
      SLine_clear();
      SLine_put_control(true);
      led4 = 0.10; // Turn on LED4 (Connected)
      break;
    // The following cases generally should not happen often, but are explicitly covered anyway
    case TCPSOCKET_CONRST:
    case TCPSOCKET_CONTIMEOUT:
    case TCPSOCKET_CONABRT:
    case TCPSOCKET_ERROR:
    case TCPSOCKET_DISCONNECTED:
      // Close the socket (nmap/netcat -z portscanning us?)
      printf("$MBEDE,%d,IP,LISTENING,TCP_SOCKET_DISCONNECTED*FF\r\n",time(NULL));
      ListeningSock.close();
      clientExists = 0;
      SLine_put_control(false);
      SLine_clear();
      led4 = 0.0; // Shut off LED4 (Connected)
      break;
    default:
      break;
  };
}

// ------ Serial -----

// Set up the UARTs with MODSERIAL buffering
MODSERIAL hostPort(USBTX, USBRX);
MODSERIAL gpsPort(p13, p14);
MODSERIAL imuPort(p9, p10);
MODSERIAL wheelPort(p28, p27);

void captureSerial(MODSERIAL* serial, LINE_T* line, LINE_SRC_T src) {
    memset(line->line, 0, LINE_MAX_LEN + 1); // Empty out buffer first
    line->source = src; // Enumerate source from line_util.h
    line->usec = highres.read_us(); // Read highres timer
    serial->move(line->line, LINE_MAX_LEN); // Move the buffer into the struct member
    line->len = strip_crlf(line->line); // Strip the line of endings of the struct member
    line->timestamp = time(NULL); // Read RTC time
}

void generateOutput(TCPSocket* socket, LINE_T* line) {
    char tx_buf[MESSAGE_BUFFER_SIZE];
    unsigned int tx_length;    // total bytes to be sent.
    int bytes_sent;            // total bytes sent so far in function.
    int send_rc;               // result of last send operation.
    unsigned int send_chunk;   // amount attempted for tx operation.
    unsigned int consec_zeros; // consecutive socket send ops returning 0.
    
    if (line->len > 0) {
    
        // Though not strictly necessary, life is just easier if we preformat
        // the data we want to send by socket.

        tx_length = snprintf(tx_buf, MESSAGE_BUFFER_SIZE,
                              "%s,%010u,%010u,%s%s",
                              LINE_SRC_NAMES[line->source],
                              line->timestamp,
                              line->usec,
                              line->line,
                              REC_SEP);
        bytes_sent = 0;
        send_rc = 0;
        consec_zeros = 0;
        printf(tx_buf);

        // Traditional socket transmission APIs don't guarantee that
        // everything we ask to send will get sent.  Details on the mbed
        // implementation are light, but it is wise to take this likelihood
        // into consideration.  Socket buffer capacity is certainly not
        // infinite, and can fill for a variety of reasons.

        while ((send_rc >= 0) && ((tx_length - bytes_sent) > 0)) {
            
            // We'll go at it until we're out of stuff to send or we get an
            // error when we send (traditional socket transmission APIs
            // return -1 when encountering an error.

            send_chunk = ((tx_length - bytes_sent) > TX_CHUNK ?
                          TX_CHUNK : tx_length - bytes_sent);

            // We use this notion of chunking to go easy on the network
            // stack, never trying to transmit a large amount of data
            // in a single call.

            send_rc = pConnectedSock->send(&(tx_buf[bytes_sent]), 
                                           send_chunk);
            bytes_sent += (send_rc > 0 ? send_rc : 0);

            // If we try to send too much and not poll often enough, the
            // network stack will stop being able to accept data.  So, we'll
            // poll here since this is a really intense area of transmission.

            Net::poll();

            if (consec_zeros && (send_rc > 0)) {
                printf("### Recovery: First non-zero send after %06u tries "
                       "(%04d/%04u). ###\r\n", 
                       consec_zeros, send_rc, send_chunk);
                printf("Serial data line queue utilization: %u/%u.\r\n",
                       SLines_get_fill(), SLines_get_capacity());
                consec_zeros = 0;
            }

            if (send_rc < send_chunk) {
            
                if (!consec_zeros) {
                    printf("### Send result: %04d/%04u. ###\r\n",
                           send_rc, send_chunk);
                    printf("Serial data line queue utilization: %u/%u.\r\n",
                           SLines_get_fill(), SLines_get_capacity());
                }
                
                if (send_rc <= 0) {
                    consec_zeros++;
                }

            }

        }

    }

}

// ------ Serial ISRs -----

void gpsMessageReceive(void) {
    LINE_T new_line;

    captureSerial(&gpsPort, &new_line, LINE_SRC_GPS);
    SLine_put(&new_line);
}

void imuMessageReceive(void) {
    LINE_T new_line;

    captureSerial(&imuPort, &new_line, LINE_SRC_IMU);
    SLine_put(&new_line);
}

void wheelMessageReceive(void) {
    LINE_T new_line;

    captureSerial(&wheelPort, &new_line, LINE_SRC_WHEEL);
    SLine_put(&new_line);
}

void hostMessageReceive(void) {
    LINE_T new_line;

    captureSerial(&hostPort, &new_line, LINE_SRC_HOST);
    SLine_put(&new_line);
}

void processSerialQueues() {
    unsigned int cur_fill;
    unsigned int lines_handled;

    cur_fill = SLines_get_fill();
    lines_handled = 0;

    while ((cur_fill > 0) && (lines_handled < MAX_LINES_PER_ITER)) {

        // It's tempting to just process every line we have while we're in
        // here.  However, since we're in a super-looped environment, network
        // I/O is completed via polling, and it seems prudent to not jam too
        // much down the stack between polling operations.
    
        if (((cur_fill * 100) / SLines_get_capacity()) >= Q_ALERT_THRESH) {
            printf("Serial data line queue utilization: %u/%u.\r\n",
                   cur_fill, SLines_get_capacity());
        }
    
        if (clientExists) {
            generateOutput(pConnectedSock, SLine_get());
        }
        
        SLine_remove();
        cur_fill = SLines_get_fill();
        lines_handled++;
    }
    
}

void serialSetup() {
  // Set up the USB UART for host debugging
  hostPort.baud(HOST_BAUD); // Need a baud rate. We can go faster, but this is good.
  hostPort.attach(&hostMessageReceive, MODSERIAL::RxAutoDetect); // Attach a handler on receive.
  hostPort.autoDetectChar('\r'); // Tell the handler to keep an eye for carrage returns.
  
  // Set up the GPS UART for incoming sentences
  gpsPort.baud(38400); // Need a baud rate. We can go faster, but this is good.
  gpsPort.attach(&gpsMessageReceive, MODSERIAL::RxAutoDetect); // Attach a handler on receive.
  gpsPort.autoDetectChar('\n'); // Tell the handler to keep an eye for newlines.

  // Set up the IMU UART for incoming sentences
  imuPort.baud(57600); // Need a baud rate. We can go faster, but this is good.
  imuPort.attach(&imuMessageReceive, MODSERIAL::RxAutoDetect); // Attach a handler on receive.
  imuPort.autoDetectChar('\n'); // Tell the handler to keep an eye for newlines.

  // Set up the Wheel UART for incoming sentences
  wheelPort.baud(57600); // Need a baud rate. We can go faster, but this is good.
  wheelPort.attach(&wheelMessageReceive, MODSERIAL::RxAutoDetect); // Attach a handler on receive.
  wheelPort.autoDetectChar('\r'); // Tell the handler to keep an eye for carrage returns.
}

int ethernetSetup() {  // Setup ethernet hardware
  printf("\r\n$MBEDE,%d,IP,ETHERNET_DHCP_SLEEP_20*FF\r\n",time(NULL)); // Send some status
  wait(20);
  printf("\r\n$MBEDE,%d,IP,ETHERNET_DHCP*FF\r\n",time(NULL)); // Send some status
  EthernetErr ethErr = eth.setup(60000); // Initiate ethernet setup (DHCP or Static IP)
  if(ethErr) {
    printf("$MBEDE,%d,IP,ETHERNET_ERROR,%d*FF\r\n",time(NULL), ethErr);
    return -1;
  }
  IpAddr ip = eth.getIp(); // Say our IP address
  printf("$MBEDE,%d,IP,ADDRESS,%d.%d.%d.%d*FF\r\n",time(NULL), ip[0], ip[1], ip[2], ip[3]);
  led3 = 0.10; // Turn on LED3 (DHCP Success)
  led1 = 0.00; // Turn off LED1 (Xmit)
  led2 = 0.00; // Turn off LED2 (Recv)
  return 0;
}

void portSetup() { // Set the callbacks for Listening
  ListeningSock.setOnEvent(&onListeningTCPSocketEvent); // bind and listen on TCP
  err=ListeningSock.bind(Host(IpAddr(), TCP_LISTENING_PORT)); // Bind the port
  printf("$MBEDE,%d,IP,BINDING_PORT,%i*FF\r\n",time(NULL), TCP_LISTENING_PORT);
  if(err) printf("$MBEDE,%d,IP,BINDING_ERROR,%i*FF\r\n",time(NULL), TCP_LISTENING_PORT);
  err=ListeningSock.listen(); // Starts listening
  printf("$MBEDE,%d,IP,LISTENING_PORT,%i*FF\r\n",time(NULL), TCP_LISTENING_PORT);
  if(err) printf("$MBEDE,%d,IP,LISTENING_ERROR,%i*FF\r\n",time(NULL), TCP_LISTENING_PORT);
}

// ------ MAINLOOP -----

int main() {
  led1 = 1.0;
  highres.start(); // Initiate highres timer ticking.
  led1 = 0.75;
  buttonSetup(); // Activate button ISRs
  led1 = 0.50;
  serialSetup(); // Activate Serial ISRs
  led1 = 0.10; 
  printf(BannerText);
  led2 = 1.0;
  if(ethernetSetup()==-1) return -1; // 'quit' if we cannot setup ethernet. (return to bootloader/'reboot')
  led2 = 0.75;
  rtcSetup(); // Get time from network
  led2 = 0.50;
  portSetup(); // Open listens
  led2 = 0.10;
  
  while(true) { // infinite network poll loop
    Net::poll(); // Poll the network stack.
    processSerialQueues(); // Process queued messages (Seems fastest here)
  }
}