Telnet server2

Top  Previous  Next

//-----------------------------------------------------------------------------
// telnet.vpl, created 2018-03-15 10:39
// 
// This is a VERY simple telnet server. It has only a few commands, but still
// shows how to establish a TCP/IP connection.
// When a Telnet client connects to the RTCU, a welcome message is shown,
// together with a prompt, where the telnet client then can enter commands.
// The following commands are available:
// input, on n, off n, quit and help. Input will show the state
// of 4 digital inputs, on/off n will switch digital output n to either on or off
// state, quit will close the connection, and help will show a list of available
// commands.
//-----------------------------------------------------------------------------
INCLUDE rtcu.inc
 
//  Input variables that can be configured via the configuration dialog (These are global)
VAR_INPUT
   din      : ARRAY [1..4] OF BOOL; | Digital inputs
END_VAR;
 
//  Output variables that can be configured via the configuration dialog (These are global)
VAR_OUTPUT
   dout     : ARRAY [1..4] OF BOOL; | Digital outputs
   led      : ARRAY [1..3] OF BOOL;
END_VAR;
 
//---------------------------------------------------------------------------
// Send a STRING on a TCP/IP connection
//---------------------------------------------------------------------------
FUNCTION SendData;
VAR_INPUT
   handle   : SYSHANDLE;
   str      : STRING;
END_VAR
VAR
   txbuf    : ARRAY [0..100] OF SINT;
   len      : INT;
   size     : DINT;
END_VAR;
   len := strLen(str := str);
   strToMemory(dst := ADDR(txbuf), str := str, len := len);
   DebugFmt(message:="soSend = \1", v1 := soSend(socket:=handle, data:=ADDR(txbuf), size:=len, sent:=size));
END_FUNCTION;
 
//---------------------------------------------------------------------------
// Thread which handles client connections
//---------------------------------------------------------------------------
THREAD_BLOCK TB_TELNET_CLIENT;
VAR_INPUT
   handle   : SYSHANDLE;
END_VAR;
VAR_OUTPUT
   inuse    : BOOL;
END_VAR;
VAR
   buf      : ARRAY [1..200] OF SINT;
   size     : DINT;
   sent     : DINT;
   rc, i    : INT;
 
   extract  : strGetValues;
   str      : STRING;
   Partial  : STRING;
   Command  : STRING;
   unknown  : BOOL;
END_VAR;
 
   // The thread is in use
   inuse := TRUE;
 
   // Send welcome message
   SendData(handle := handle, str := "Welcome to the RTCU Telnet Server.$N$N");
   SendData(handle := handle, str := "Available commands:$N");
   SendData(handle := handle, str := "  <input>  : Show status of digital inputs$N");
   SendData(handle := handle, str := "  <output> : Show status of digital outputs$N");
   SendData(handle := handle, str := "  <on n>   : Set digital output n to ON$N");
   SendData(handle := handle, str := "  <off n>  : Set digital output n to OFF$N");
   SendData(handle := handle, str := "  <quit>   : Close connection$N");
   SendData(handle := handle, str := "  <help>   : Show available commands$N");
   SendData(handle := handle, str := "$NEnter command> ");
 
// Main loop
WHILE TRUE DO
   // Wait for data
   rc := soRecv(
                socket  := handle,
                data    := ADDR(buf),
                maxsize := SIZEOF(buf),
                size    := size
               );
   IF rc < 1 THEN
      DebugFmt(message := "soRecv = \1", v1 := rc);
      soClose(socket := handle);
      inuse := FALSE;
      RETURN;
   END_IF;
 
   // Echo the received data
   rc := soSend(socket := handle, data:=ADDR(buf), size := size, sent := sent);
   IF rc < 1 THEN
      DebugFmt(message := "soRecv = \1", v1 := rc);
      soClose(socket := handle);
      inuse := FALSE;
      RETURN;
   END_IF;
 
   // Convert the received data to a string
   str := strFromMemory(src := ADDR(buf), len := INT(size));
   DebugMsg(message := str);
 
   // concatenate the received data to command string
   Partial := strConcat(str1 := Partial, str2 := str);
   i := strFind(str1 := Partial, str2 := "$R");
   IF i > 0 THEN
      // We now have a complete command assembled in PartialCommand
      Command := strLeft(str := Partial, length := i - 1);
      DebugMsg(message := strConcat(str1 := "Command=", str2 := Command));
      Partial := "";
      unknown := ON;
 
      // Check if it is a "ON" command
      extract(format := "ON \1", str := Command);
      IF extract.match AND extract.v1 >= 1 AND extract.v1 <= 8 THEN
         unknown := OFF;
         dout[extract.v1] := ON;
         str := strFormat(format := "Setting output \1 to ON", v1 := extract.v1);
         DebugMsg(message := str);
         SendData(handle := handle, str := strConcat(str1 := str, str2 := "$N"));
      END_IF;
 
      // Check if it is a "OFF" command
      extract(format := "OFF \1", str := Command);
      IF extract.match AND extract.v1 >= 1 AND extract.v1 <= 8 THEN
         unknown := OFF;
         dout[extract.v1] := OFF;
         str := strFormat(format := "Setting output \1 to OFF", v1 := extract.v1);
         DebugMsg(message := str);
         SendData(handle := handle, str := strConcat(str1 := str, str2 := "$N"));
      END_IF;
 
      // Check if it is a "INPUT" command 
      IF strCompare(str1 := "INPUT", str2 := Command) = 0 THEN
         unknown := OFF;
         FOR i := 1 TO 4 DO
            str := strFormat(format := "$NInput \1 is \2", v1 := i, v2 := INT(din[i]));
            SendData(handle := handle, str := str);
         END_FOR;
      END_IF;
 
      // Check if it is a "OUTPUT" command 
      IF strCompare(str1 := "OUTPUT", str2 := Command) = 0 THEN
         unknown := OFF;
         FOR i := 1 TO 4 DO
            str := strFormat(format := "$NOutput \1 is \2", v1 := i, v2 := INT(dout[i]));
            SendData(handle := handle, str := str);
         END_FOR;
      END_IF;
 
      // Check if it is a "HELP" command 
      IF strCompare(str1 := "HELP", str2 := Command) = 0 THEN
         unknown := OFF;
         SendData(handle := handle, str := "Available commands:$N");
         SendData(handle := handle, str := "  <input>  : Show status of digital inputs$N");
         SendData(handle := handle, str := "  <output> : Show status of digital outputs$N");
         SendData(handle := handle, str := "  <on n>   : Set digital output n to ON$N");
         SendData(handle := handle, str := "  <off n>  : Set digital output n to OFF$N");
         SendData(handle := handle, str := "  <quit>   : Close connection$N");
         SendData(handle := handle, str := "  <help>   : Show available commands$N");
      END_IF;
 
      // Check if it is a "QUIT" command 
      IF strCompare(str1 := "QUIT", str2 := Command) = 0 THEN
         unknown := OFF;
         SendData(handle := handle, str := "$NGoodbye.$N");
 
         // Diconnect
         soClose(socket := handle);
      END_IF;
 
      // Unknown command 
      IF unknown THEN
         SendData(handle := handle, str := "Unknown command$N");
      END_IF;
 
      // Send a new prompt 
      SendData(handle := handle, str := "$NEnter command> ");
   END_IF;
 
END_WHILE;
END_THREAD_BLOCK;
 
//  These are the global variables of the program
VAR
   client   : ARRAY [1..10] OF TB_TELNET_CLIENT;
END_VAR;
 
//---------------------------------------------------------------------------
// Show information about incoming connection
//---------------------------------------------------------------------------
FUNCTION ShowInfo;
VAR_INPUT
   handle   : SYSHANDLE;
END_VAR;
VAR
   local    : STRING;
   remote   : STRING;
   host     : STRING;
   port     : DINT;
   iface    : SINT;
END_VAR;
   // Get information
   soStatus(socket := handle, local := local, remote := remote);
   soAddrInetGet(address := remote, host := host, port := port);
   iface := soAddrToInterface(address := local);
 
   // Show information
   DebugMsg(message := "Incoming connection");
   DebugFmt(message := "    interface  = \1", v1 := iface);
   DebugMsg(message := "    IP address = " + host);
   DebugFmt(message := "    IP port    = \4", v4 := port);
END_FUNCTION;
 
//---------------------------------------------------------------------------
// Thread which listens for incoming connections
//---------------------------------------------------------------------------
THREAD_BLOCK TB_TELNET_SERVER;
VAR_INPUT
   port     : DINT;
END_VAR;
VAR
   handle   : SYSHANDLE;
   remote   : SYSHANDLE;
   inuse    : BOOL;
   address  : STRING;
 
   rc       : INT;
   i        : INT;
   size     : DINT;
   buf      : ARRAY [1..10] OF SINT;
END_VAR;
 
// Setup
strToMemory(dst := ADDR(buf), str := "EXIT$N", len := 6);
 
// Build listen address
soAddrInetSet(address := address, port := port);
 
WHILE TRUE DO
   // Open listen socket
   IF NOT inuse THEN
      // Create socket
      rc := soCreate(socket := handle);
      IF rc < 1 THEN
         DebugFmt(message := "soCreate = \1", v1 := rc);
         RETURN;
      END_IF;
 
      // Bind socket
      rc := soBind(socket := handle, address := address);
      IF rc < 1 THEN
         DebugFmt(message := "soBind = \1", v1 := rc);
         soClose(socket := handle);
         RETURN;
      END_IF;
 
      // Start listening
      rc := soListen(socket := handle);
      IF rc < 1 THEN
         DebugFmt(message := "soListen = \1", v1 := rc);
         soClose(socket := handle);
         RETURN;
      END_IF;
 
      inuse := TRUE;
   END_IF;
 
   // Accept
   rc := soAccept(socket := handle, remote := remote);
   IF rc = 1 THEN
      // Find free thread
      FOR i := 1 TO 10 DO
         IF NOT client[i].inuse THEN
            EXIT;
         END_IF;
      END_FOR;
 
      // Start thread
      IF NOT client[i].inuse THEN
         // Show information
         ShowInfo(handle := remote);
 
         // Start thread
         client[i](handle := remote);
      ELSE
         // Send denied
         soSend(
                socket  := remote,
                data    := ADDR(buf),
                size    := 6,
                sent    := size
               );
         soClose(socket := remote);
      END_IF;
   ELSE
      DebugFmt(message := "soAccept = \1", v1 := rc);
      soClose(socket := handle);
      inuse := FALSE;
   END_IF;
 
   Sleep(delay := 250);
END_WHILE;
END_THREAD_BLOCK;
 
PROGRAM Telnet;
VAR
   telnet      : TB_TELNET_SERVER;
   val_con     : BOOL;
   old_con     : BOOL;
   i           : INT;
 
   iface       : SINT;
   port        : DINT;
END_VAR;
 
   // Welcome
   DebugMsg(message := "----------------------------------------");
   DebugMsg(message := "  RTCU Telnet Server.");
   DebugMsg(message := "----------------------------------------");
 
   //Initialize
   DebugMsg(message := "Initialize...");
   iface    := _NET_IFACE_LAN1;
   port     := 5020;
 
   // Open network interface
   netOpen(iface := iface);
 
   // Wait for connection to the Internet
   // (This is actually not needed, as the soListen() can be called before
   // the connection is established)
   WHILE NOT netConnected(iface := iface) DO
      DebugMsg(message := "   Waiting for network connection");
      Sleep(delay := 3000);
   END_WHILE;
   old_con := ON;
 
   // Start server
   telnet(port := port);
 
   // Show our assigned IP address (this is the one the telnet client should connect to)
   DebugMsg(message := strConcat(str1 := "My IP Address= ", str2 := sockIPToName(ip := sockGetLocalIP(iface := iface))));
   DebugFmt(message := "My IP Port=    \4", v4 := port);
   DebugMsg(message := "----------------------------------------");
   DebugMsg(message := "Running.");
 
BEGIN
   // Update interface status
   val_con := netConnected(iface := iface);
   IF val_con <> old_con THEN
      IF val_con THEN
         DebugMsg(message := "Network: Connected");
      ELSE
         DebugMsg(message := "Network: Connection lost");
      END_IF;
   END_IF;
   old_con := val_con;
   
   // Update LEDS
   led[1]  := val_con;
   led[2]  := telnet._running;
   led[3]  := OFF;
   FOR i := 1 TO 10 DO
      IF client[i].inuse THEN led[3] := ON; END_IF;
   END_FOR;   
 
   // Delay
   Sleep(delay := 250);
END;
END_PROGRAM;