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

Revision:
0:5d5265391846
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Aug 13 07:05:57 2012 +0000
@@ -0,0 +1,452 @@
+// 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)
+  }
+}