|
//-----------------------------------------------------------------------------
// 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;
|