Serial To Tcp Converter

From openPicus Wiki
Jump to: navigation, search

Contents

What you are going to learn

This tutorial describes a real application: a serial2TCP bridge with dynamic configuration. It also makes use of the Smart Network Configuration to provide an easy to use application right from the start.

Disclaimer

The term application is not to be intended in any commercial meaning: no direct or indirect, express or implicit guarantees and/or responsibilities are accepted. This project is not intended to be sold as is, but rather as a framework for custom development.

What you need

Just a Flyport module, two may allow for more easy testing.

How it works

Explanation

The web interface is composed by two pages. One is the Smart Network Configuration to allow in-situ configuration of the network connection. The other is the Serial2TCP configuration page.

The code allows for dynamic reconfiguration of the TCP server to allow switching between different UART configurations. Configuration parameters supported are

  • TCP Server Port
  • UART Device
  • UART Speed
  • UART Parity
  • UART Stop bits
  • UART TX pin
  • UART RX pin

To achieve this we use some JS on the page to control the parameters that get passed to the Flyport, and then some parsing code on the Flyport.

Let's start from the webpage.

The Webpage

Serial2tcp.png

As you can see from the image above, the layout is simple and straightforward: all the configurable parameters are right on the front page. Since this is a "download and use" project, the Smart Network Configuration page has been embedded too.

NOTE The Uart device 1 has been eliminated do avoid problems with the default debug port. You can modify the code to allow using device number 1 if you so choose.

Changing the selection of any of the comboboxes results in the change of a variable. All variable are then serialized into a string and passed to the Flyport with a GET request when the Submit button is pressed.

Here is the relevant JS code

<script type="text/javascript">
// Parses the xmlResponse from status.xml and updates the status box
function updateStatus(xmlData) {
	var mainstat = document.getElementById('display').style.display;
	var loadstat = document.getElementById('loading').style.display;
 
	// Check if a timeout occurred
	if(!xmlData)
	{
		mainstat = 'none';
		loadstat = 'inline';	
 
		return;
	}
 
	// Make sure we're displaying the status display
	mainstat = 'inline';
	loadstat = 'none';
 
	// Loop over all the LEDs
	for(i = 0; i < 5; i++)
		document.getElementById('led' + i).style.color = (getXMLValue(xmlData, 'led' + i) == '1') ? '#ff8b00' : '#777';
 
	// Loop over all the buttons
	for(i = 0; i < 3; i++)
		document.getElementById('btn' + i).innerHTML = (getXMLValue(xmlData, 'btn' + i) == 'up') ? '&Lambda;' : 'V';
 
	// Update the POT value
	for(i = 0; i < 1; i++)
		document.getElementById('pot'+i).innerHTML = getXMLValue(xmlData, 'pot'+i);
 
	// Update for bargraph
	for(i = 1; i < 2; i++)
	{
		var wd=0;
		wd= (getXMLValue(xmlData, 'pot'+(i-1)))/5;
		document.getElementById('bar'+i).style.width=wd+"px";
	}
 
	if (getXMLValue(xmlData, 'uarterror') == '1')
		alert("Error. Check the parameter sequence for misconfigurations.\nTCP Server configuration unchanged.");
 
}
 
function change232params() {
	if (document.getElementById("custompins").checked == true)
		newAJAXCommand('serial.cgi?dev='+uartdevice.value+'&baud='+rs232baud.value+'&par='+rs232parity.value+'&stop='+rs232stop.value+'&pins=1&tx='+txpin.value+'&rx='+rxpin.value+'&port='+serverport.value);
	else
		newAJAXCommand('serial.cgi?dev='+uartdevice.value+'&baud='+rs232baud.value+'&par='+rs232parity.value+'&stop='+rs232stop.value+'&pins=0'+'&port='+serverport.value);
}
 
 
setTimeout("newAJAXCommand('status.xml', updateStatus, true)",500);
 
 
function enablecustompins() {
	if (document.getElementById("custompins").checked == true)
	{
		document.getElementById("pinbox").className = "smallbox";
		document.getElementById("txpin").disabled = false;
		document.getElementById("rxpin").disabled = false;
	}
	else
	{
		document.getElementById("pinbox").className = "smallboxgrey";
		document.getElementById("txpin").disabled = true;
		document.getElementById("rxpin").disabled = true;
	}
}
 
 
 
</script>

The Firmware

The other important part is the firmware. Here there are many subroutines managing all the various aspects:

  • the HTTPExecuteGet in HTTPApp.c, has all the sanity checks on the uart parameters
  • the UART parser
  • the TCP parser
  • the parameter changer

The GET request parser

This is the most complex part of this project, involving multiple checks on the parsed variables, and keeping track of how many tests are passed successfully.

001 if(!memcmp(filename, "serial.cgi", 10))		// Is the requested file name "serial.cgi"?
002 {
003 	char tmp[500];
004 	uart_sanitychecks = 0;
005 	ptr = HTTPGetArg(curHTTP.data, (BYTE *)"dev");
006 	long num = atoi((char*)ptr);
007 	sprintf(tmp, "Uart device:%d\r\n", (int)num);
008 	UARTWrite(1, tmp);
009 	uart_device = num;
010 	
011 	ptr = HTTPGetArg(curHTTP.data, (BYTE *)"baud");
012 	num = atol((char*)ptr);		
013 	sprintf(tmp, "baud:%ld\r\n", num);
014 	UARTWrite(1, tmp);
015 	uart_baud = num;
016 	
017 	ptr = HTTPGetArg(curHTTP.data, (BYTE *)"par");
018 	sprintf(tmp, "%s", ptr);
019 	if(strstr(tmp, "8n")!=NULL)
020 	{
021 		uart_parity = 0;
022 	}
023 	else if(strstr(tmp, "8e")!=NULL)
024 	{
025 		uart_parity = 8; 
026 	}
027 	else if(strstr(tmp, "8o")!=NULL)
028 	{
029 		uart_parity = 2;
030 	}
031 	else if(strstr(tmp, "9n")!=NULL)
032 	{
033 		uart_parity = 6;
034 	}
035 	sprintf(tmp, "parity:%s\r\n", ptr);
036 	UARTWrite(1, tmp);
037 	
038 	ptr = HTTPGetArg(curHTTP.data, (BYTE *)"stop");
039 	num = atoi((char*)ptr);
040 	sprintf(tmp, "stop:%s\r\n", ptr);
041 	UARTWrite(1, tmp);
042 	uart_stop = num;		
043 	
044 	ptr = HTTPGetArg(curHTTP.data, (BYTE *)"pins");
045 	num = atoi((char*)ptr);
046 	sprintf(tmp,"Use custom pins: %s\r\n",num?"YES":"NO");
047 	UARTWrite(1,tmp);
048 	if (num)
049 		uart_usecustompins = 1;
050 	else
051 		uart_usecustompins = 0;
052 	
053 	if (uart_usecustompins)
054 	{
055 		ptr = HTTPGetArg(curHTTP.data, (BYTE *)"tx");
056 		num = atoi((char*)ptr);
057 		sprintf(tmp,"TX pin: p%2ld\r\n",num);
058 		UARTWrite(1,tmp);
059 		uart_txpin = num;
060 	
061 		ptr = HTTPGetArg(curHTTP.data, (BYTE *)"rx");
062 		num = atoi((char*)ptr);
063 		sprintf(tmp,"RX pin: p%2ld\r\n",num);
064 		UARTWrite(1,tmp);
065 		uart_rxpin = num;
066 	}
067 		
068 	ptr = HTTPGetArg(curHTTP.data, (BYTE *)"port");
069 	num = atol((char*)ptr);
070 	sprintf(tmp,"TCP Port: %s\r\n",ptr);
071 	UARTWrite(1,tmp);
072 	sprintf(uart_serverport,"%ld",num);
073 	
074 	if( 1 < uart_device && uart_device <= 3)
075 		uart_sanitychecks++;
076 	if( 4799 < uart_baud && uart_baud < 230401)
077 		uart_sanitychecks++;
078 	if( 0 < uart_stop && uart_stop < 3 )
079 		uart_sanitychecks++;
080 	if( 0 < num && num < 65536)
081 		uart_sanitychecks++;
082 	
083 	if (uart_usecustompins == 0 && uart_sanitychecks == UART_SANITY_TSRH_NOPINS)
084 		uart_newparams = TRUE;
085 	if (uart_usecustompins == 1)
086 	{
087 		if ( 0 < uart_txpin && uart_txpin < 26)
088 			uart_sanitychecks++;
089 		if ( 0 < uart_rxpin && uart_rxpin < 26)
090 			uart_sanitychecks++;
091 		if (uart_rxpin != uart_txpin)
092 			uart_sanitychecks++;
093 		if (uart_sanitychecks == UART_SANITY_TSRH_PINS)
094 			uart_newparams = TRUE;
095 	}
096 	
097 	if (uart_newparams == FALSE)
098 	{
099 		uart_error = TRUE;
100 		UARTWrite(1,"Error in new config parameters\n");
101 	}
102 }

We used a new CGI file to separate the parsing routine. It starts by setting to zero the successful tests counter and then immediately starts parsing all the variables. Once each variable is parsed and stored, checks start (line 74). There are two sanity check counts: one for when custom pins are not specified, and one for when they are. Only if the sanity check is passed the newparams flag is set, and the main cycle will later update the server. If the check fails, the error flag is set resulting in a visible error message on the web interface.

Serial2tcp error.png

The main cycle

In the main cycle there are 3 sections as seen in the firmware introduction. Let's start from the parameter updater.

01 if (uart_newparams)
02 {
03 	int timeout=3;
04 	uart_newparams = FALSE;
05 	TCPServerClose(SocketTCPServer);
06 	SocketTCPServer = INVALID_SOCKET;
07 	sprintf(rcv,"Re-opening TCP server on port: %s\n",uart_serverport);
08 	UARTWrite(1,rcv);
09 	while(SocketTCPServer == INVALID_SOCKET && timeout > 0)
10 	{
11 		SocketTCPServer = TCPServerOpen(uart_serverport);
12 		vTaskDelay(100);
13 		if (SocketTCPServer != INVALID_SOCKET)
14 			UARTWrite(1,"TCP Socket opened succesfully\n");
15 		else
16 		{
17 			sprintf(rcv,"Unable to open socket\nRetrying %d times\n",timeout);
18 			UARTWrite(1, rcv);
19 		}
20 		timeout--;
21 	}
22 	// Turn Off the UART port
23 	RS232Off(uart_device_old);
24 	RS232Remap(uart_device, uart_txpin, uart_rxpin, 0, 0);
25 	// Set STOP bits
26 	RS232SetParam(uart_device, RS232_STOP_BITS, uart_stop);
27 	// Set DATA bits and PARITY
28 	RS232SetParam(uart_device, RS232_DATA_PARITY, uart_parity);
29 	// Set Baudrate
30 	RS232Init(uart_device, uart_baud);
31 	// Turn On the UART port
32 	RS232On(uart_device);
33 	uart_device_old = uart_device;
34 	
35 }

The parameter update only starts when the relevant flag is set, so the parser in the HTTPApp module must have already been run. At first a timeout is set, to prevent looping indefinitely if the TCPIP stack cannot successfully open a new socket later. Then the flag is reset. Then the TCP Socket is released and the server closed (this also gives a notification to the client application if it is still connected). The program then attempts to open the new TCP socket, retries the number of times specified by timeout waiting 1s between each try. After the TCP is either estabilished or not, the UART device is first closed, then set to the new parameters, then reopened.


 
01 msglen = UARTBufferSize(uart_device); //check buffer of UART3
02 if (msglen > 0)
03 {
04 	UARTRead(uart_device, rcv, msglen); // read from UART3
05 	rcv[msglen]='\0';
06 	UARTWrite(1,"Received on UART: ");
07 	UARTWrite(1,rcv);
08 	UARTWrite(1,"\n");
09 	TCPPutString(SocketTCPServer,(BYTE *)rcv);
10 }

The above reported code is the UART parser: it essentially checks if there are any characters on the UART buffer, and if so provides to send them over onto the TCP socket.

 
01 if(TCPisConn(SocketTCPServer))
02 {
03 	msglen = TCPRxLen(SocketTCPServer);
04 	if(msglen > 0)
05 	{			
06 		TCPRead(SocketTCPServer, rcv, msglen);
07 		rcv[msglen]='\0';
08 		UARTWrite(1,"Received on TCP: ");
09 		UARTWrite(1,rcv);
10 		UARTWrite(1,"\n");
11 		UARTWrite(uart_device,rcv);				
12 	}
13 }

Code above is the TCP parser, doing the exact same thing of the UART parser but on the TCP socket.

Conclusions

With this tutorial you are able to field a working appliance: a serial2tcp bridge with configurable WiFi network interface, and dynamically configurable UART and TCP server.

Code

Source Code

Personal tools
Namespaces

Variants
Actions
START HERE
DEVELOPMENT
HARDWARE INFO
RESOURCES
PHASED OUT
Toolbox