--[[
---------------------------------------------------------------------------
Remote Train Control Program for Windows

© Copyright 2019 by Mark DiVecchio

	This file is part of Remote Train Control.

	Remote Train Control is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	Remote Train Control is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Remote Train Control.  If not, see <http://www.gnu.org/licenses/>.

 Home Page : http://www.silogic.com/trains/RTC_Running.html
---------------------------------------------------------------------------]]
-- Semicolons are not required in Lua code
-- 
title = "UDP Test"
-- load the socket support and its friends
local socket = require("socket")
print(socket._VERSION)

local TimeoutSeconds = 1;
local DataErrCount = 0;
local TimeoutErrCount = 0;
local ReceivedPacketCount = 0;
local starttime;
local PACKETS = 200000;
local host, port;
local udp;
local ip;

-- main program
function setup() 
print("UDP Client Test");

print("Run with ESP8266 sketch Udp.ino");
-- First test the functions necessary to get the time and date
host, port = "localhost", 13
-- convert host name to ip address
ip = socket.try(socket.dns.toip(host))
-- create a new UDP object
udp = socket.try(socket.udp())
-- contact daytime host
socket.try(udp:sendto("anything", ip, port))
-- retrieve the answer and print results
udp:settimeout(TimeoutSeconds);	-- timeout in seconds, 0 = none, nil = infinite
local TimeString = udp:receive();
if (TimeString ~= nil and (string.match(TimeString, "AM") or string.match(TimeString, "PM"))) then
	print("Time and Date from " .. ip .. " = " .. string.sub(TimeString, 1, -2));	-- remove /n/r
	print("Time/Date OK");
	else
	print("Time/Date not correctly fetched");
	end;

-- Now send and receive packets to/from the ESP8266
port = 8888;
ip = "192.168.1.102"

print("Sending requests to server at " .. ip .. " port " .. port);

--[[
https://web.tecgraf.puc-rio.br/luasocket/old/luasocket-2.0-beta2/introduction.html

https://web.tecgraf.puc-rio.br/luasocket/old/luasocket-2.0-beta2/udp.html

 UDP (User Datagram Protocol) is a non-reliable datagram protocol. In other words, 
 applications communicating through UDP send and receive data as independent blocks, 
 which are not guaranteed to reach the other end. Even when they do reach the other 
 end, they are not guaranteed to be error free. Data transfers are atomic, one 
 datagram at a time. Reading only part of a datagram discards the rest, so that 
 the following read operation will act on the next datagram. The advantages are 
 in simplicity (no connection setup) and performance (no error checking or error 
 correction).

Note that although no guarantees are made, these days networks are so good that, 
under normal circumstances, few errors happen in practice.

An UDP socket object is created by the socket.udp function. UDP sockets do not 
need to be connected before use. The method sendto can be used immediately after 
creation to send a datagram to IP address and port. Host names are not allowed 
because performing name resolution for each packet would be forbiddingly slow. 
Methods receive and receivefrom can be used to retrieve datagrams, the latter 
returning the IP and port of the sender as extra return values (thus being slightly 
less efficient).

When communication is performed repeatedly with a single peer, an application 
should call the setpeername method to specify a permanent partner. Methods sendto 
and receivefrom can no longer be used, but the method send can be used to send data 
directly to the peer, and the method receive will only return datagrams originating 
from that peer. There is about 30% performance gain due to this practice.

To associate an UDP socket with a local address, an application calls the 
setsockname method before sending any datagrams. Otherwise, the socket is 
automatically bound to an ephemeral address before the first data transmission 
and once bound the local address cannot be changed. The other methods available 
for UDP sockets are getpeername, getsockname, settimeout, setoption and close. 

]]
local udp = socket.try(socket.udp());
udp:settimeout(TimeoutSeconds);	-- timeout in seconds, 0 = none, nil = infinite
udp:setoption('reuseaddr',true);
--
-- Using setpeername() 30% performance gain per the luasocket documentation
--assert(udp:setpeername(ip, port)); -- Echo IP and Port number

-- NOTE:
-- without setpeername():
-- 1000 packets in 23 seconds with 3 packets lost
-- with setpeername():
-- 1000 packets in 16 seconds with 0 packets lost (30% improvement)
--
-- without setpeername():
-- 2000 packets in 31 seconds with 4 packets lost
-- with setpeername():
-- 2000 packets in 32 seconds with 15 packets lost (0% improvement)

starttime = os.time();	

--
-- Note that originally this loop had no sleep() calls. That meant there
-- was no way to interrupt it once it starts. The [STOP] button
-- had no effect. This led me to develop the Yield() function. That
-- function checks for button presses (either [STOP] or a function button)
-- and executes that routine. Yield() uses WaitForMultipleObjects() in 
-- non blocking mode so it runs very quickly if there is nothing waiting.
--
for idx = 1,PACKETS do
	-- Not using setpeername()
	assert(udp:sendto("req", ip, port));
	-- When using setpeername()
	--assert(udp:send("req"));
	--
	-- receive response(s) waiting for timeout seconds
	local data = udp:receive();
	if (data) then
		ReceivedPacketCount = ReceivedPacketCount + 1;
		if ((idx % 1000) == 0) then
			print(".");
			end;
		if (data ~= "ack") then 
			DataErrCount = DataErrCount + 1;
			print(":" .. ReceivedPacketCount .. ": ", data);
			end;	
		else 
			TimeoutErrCount = TimeoutErrCount + 1;	
			print("--timeout #" .. TimeoutErrCount);
		end;
	Yield();	-- check for [STOP] or function button
	end

print("Elapsed time = " .. os.time() - starttime .. " seconds"); 
udp:close();

print("Done");

return Stop();
end
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
	-- this function is called once when the user presses the [STOP] button or the Stop() function is called
	print("cleanup() Running");
	if (DataErrCount == 0 and TimeoutErrCount == 0) then
		print("No errors in " .. ReceivedPacketCount .. " packets with " .. PACKETS - ReceivedPacketCount .. " packet(s) lost");
		else
		print(TimeoutErrCount .. " timeout error(s) and " .. DataErrCount .. " data error(s)");
		print(ReceivedPacketCount .. " packets with " .. PACKETS - ReceivedPacketCount .. " packet(s) lost");
		end;	
	return true;		-- false=cleanup failed, true=cleanup succeeded
	end
--[[---------------------------------------------------------------------------------------]]
