--[[
---------------------------------------------------------------------------
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
--[[---------------------------------------------------------------------------------------]]
-- This test measures the stopping distance of an engine.
-- Set the starting and ending speeds and the starting and ending deceleration rates.
-- The script will run all combinations and print the results.
--
-- Creates table constructors for insertion into the
-- "LayoutX.lua" scripts for each layout
-- Table constructions are created for each Deceleration rate run
-- [11] = {[1] = 0.0, [2] = 0.5}
--
-- This seems to have trouble running over WiFi. A lot of timeouts and other errors. Particular
-- trouble reading the time using DTO();
-- Over the CC1101E radio, it is much more stable.
--[[---------------------------------------------------------------------------------------]]
title = "Deceleration Test"

require("defines");

-- Counter Labels
Counter01Label = "Outer Loops";
NO_LOOPS = COUNTER01;
Counter02Label = "No of Starts";
NO_STARTS = COUNTER02;
Counter03Label = "No of Stops";
NO_STOPS = COUNTER03;

-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "Control";
Tab02Label = "Not Used";
Tab03Label = "Not Used";

-- starting time
DTOStart = 0.0;
-- Program Variables
StartingSpeed = 1;		-- starting engine speed in Smph, normally 1
EndingSpeed = 40;		-- ending engine speed in Smph, normally 40
StartingDecel = 1;		-- beginning deceleration rate, normally 1 or 4
EndingDecel = 1;		-- ending deceleration rate, normally 1 or 4

local MyTIUNo, MyEngineNo;

local TableConstructor = {};

Decel = StartingDecel;
EngSpeed = StartingSpeed;
Spd = 0;
DTORunning = 0.0;
DTOStopped = 0.0;
EngineSoundSave = -1;
SoundOff = false;
-- keep the noise to a minimum
AutoBell = false;
AutoWhistle = false;

-- state machine states
IDLE = 0;
DONE = 1;
S0 = 2;
S1 = 3;
S2 = 4;
S3 = 5;
S4 = 6;
S5 = 7;
S6 = 8;
-- starting state
SM = IDLE;
LastTime = 0;
local stepsize = 0.1;
--[[---------------------------------------------------------------------------------------]]
function setup(Engine)
--
MyEngineNo = Engine;	-- Engine number selected on the RTC Main window
MyTIUNo = GetTIUNo(MyEngineNo);	-- TIU associated with MyEngineNo 
--
print("setup : TIU " .. MyTIUNo .. " Engine " .. MyEngineNo .. " Debug " .. Debug());
print(string.format("Run Time = %.2f Seconds", RunTime()));
print("Check condition of traction tires before using the results of this script");
if (Smoke(OFF, 0, MyEngineNo, MyTIUNo) == nil) then
		return Stop("Smoke Off failed");
		end;
Name, PS = GetName(MyEngineNo);
print("Engine#" .. MyEngineNo .. " " .. Name .. " PS" .. PS);
Title(title .. " - " .. GetName(MyEngineNo));
-- start selected engine
if (StartUp(0, MyEngineNo, MyTIUNo) == nil) then
		return Stop("StartUp() failed");
		end;
-- let engine startup
Sleep(15);
SoundOffButton(MyEngineNo, MyTIUNo);		-- run quietly
Sleep(5);
-- Stop the Garbage collector since this is a real-time program
collectgarbage("stop")
print("Setup : Garbage Collection stopped");
--
print("StartingSpeed  " .. StartingSpeed .. " Smph");
print("EndingSpeed  " .. EndingSpeed .. " Smph");
print("StartingDecel  " .. StartingDecel .. " Smph/sec");
print("EndingDecel  " .. EndingDecel .. " Smph/sec");
print("Step Size " .. stepsize .. " seconds");

print(string.format("Run Time = %.2f Seconds", RunTime()));
DTOStart = DTO(MyEngineNo, MyTIUNo);
if (DTOStart  == nil) then
		return Stop("DTO() failed");
		end;
print(string.format("Trip Odometer Start = %.2f Smiles", DTOStart));
print(string.format("Run Time = %.2f Seconds", RunTime()));
print(string.format("Stopping Distance-Smph-Rate------Calc------Stopping Time"));
print(string.format("Smiles   Inches        Smph/sec  Smph/Sec    Seconds"));
for iDecel = 1, EndingDecel do
	TableConstructor[iDecel] = "[" .. Engine .. "] = {";
	end;
SM = S0;	-- start the state machine
return true;
end
--[[---------------------------------------------------------------------------------------]]
function loop()
--
--[[---------------------------------------------------------------------------------------]]
-- STATE MACHINE
--[[---------------------------------------------------------------------------------------]]	
Yield();	-- yield to other processes. Needed because there are no Sleep() calls during
			-- normal running of the State Machine.
--[[---------------------------------------------------------------------------------------]]	
if (SM == IDLE) then	-- do nothing 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end
if (SM == DONE) then	-- finish up
	print("Almost Done...");
	return Stop("DONE");
	end
if (SM == S0) then
	--print("S0");
	if (Decel > EndingDecel) then
		SM = DONE;
		return true;	-- false=stop calling loop(), true=continue to call loop()
		end
	-- set deceleration value
	if (Rate(1, Decel, 0, MyEngineNo, MyTIUNo) == nil) then
		return Stop("Rate() failed");
		end;
	SM = S1;
	if(Debug() > 7) then print("S0 : Set deceleration rate to " .. Decel .. " Smph/sec"); end;
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end
if (SM == S1) then
	--print("S1");
	if (EngSpeed > EndingSpeed) then
		SM = S6;
		return true;	-- false=stop calling loop(), true=continue to call loop()
		end 
	BumpCounter(NO_STARTS);
	Sleep(1);
	--Beep();
	if(Debug() > 7) then print("S1 : Set speed to " .. EngSpeed .. " Smph"); end
	if (Throttle(EngSpeed, 0, MyEngineNo, MyTIUNo) == nil) then
		return Stop("Throttle() failed");
		end;
	SM = S2;
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end
		
if (SM == S2) then
	--print("S2");
	Spd = GetSpeed(MyEngineNo, MyTIUNo);
	if (Spd  == nil) then
		return Stop("GetSpeed() failed");
		end;
	if (Spd ~= nil and Spd == EngSpeed) then
		if(Debug() > 7) then print("S2 : Reached speed of " .. Spd .. " Smph"); end
		Sleep(10);	-- Run for awhile at speed
		SM = S3;
		return true;	-- false=stop calling loop(), true=continue to call loop()
		end
	-- For highest accuracy, check the deceleration speed as quickly as possible
	-- Acceleration speed does not require exact timing
	Sleep(1);
	-- stay in S2
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end
		
if (SM == S3) then
	--print("S3");
	BumpCounter(NO_STOPS);
	Sleep(1);
	--Beep();
	-- Measure time to decelerate to 0 Smph
	DTORunning = DTO(MyEngineNo, MyTIUNo);
	if (DTORunning == nil) then
		return Stop("DTO() failed");
		end;
	LastTime = MicroSeconds();
	if (Throttle(0, 0, MyEngineNo, MyTIUNo) == nil) then
		return Stop("Throttle() failed");
		end;
	if(Debug() > 7) then 
		print(string.format("S3 : Trip Odometer @ Speed is %.5f Smiles", DTORunning));
		print("S3 : Then set speed to 0");
		end
	SM = S4;
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end
		
if (SM == S4) then
	--print("S4");
	Spd = GetSpeed(MyEngineNo, MyTIUNo);
	if (Spd == nil) then
		return Stop("GetSpeed() failed");
		end;
	if (Spd ~= nil and Spd == 0) then
		-- Save the time to decelerate to 0 Smph in the TableConstructor
		now = MicroSeconds();
		DTOStopped = DTO(MyEngineNo, MyTIUNo);
		if (DTOStopped == nil) then
			return Stop("DTO() failed");
			end;	
		if(Debug() > 7) then print(string.format("S4 : Trip Odometer at " .. Spd .. " Smph is %.5f Smiles", DTOStopped)); end
		Smiles = DTOStopped - DTORunning;
		SFeet = Smiles * 5280.0;
		SInches = SFeet * 12.0;
		Inches = SInches / 48.0;
		-- d = 1/2 a (t^2) from Physics I
		-- a = 2 * d /( t * t)
		delta = (now - LastTime)/ 1000000.0;
		CalcDecel = 2.0 * Smiles * 3600.0 / (delta * delta);	-- Smiles/hr/sec
		print(string.format("%7.5f %5.1f     %2d   %2d        %4.1f        %6.3f", Smiles, Inches, EngSpeed, Decel, CalcDecel, delta));
		TableConstructor[Decel] = TableConstructor[Decel] .. "[" .. EngSpeed .. "] = " .. Inches .. ", ";
		Sleep(5);	-- pause for awhile between speed steps
		SM = S5;
		return true;	-- false=stop calling loop(), true=continue to call loop()
		end
	-- For highest accuracy, check the speed as quickly as possible
	-- but not too quickly
	-- Going down in speed requires exact timing
	Sleep(stepsize);
	-- stay in S4
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end
	
if (SM == S5) then
	--print("S5");
	EngSpeed = EngSpeed + 1;
	if(Debug() > 7) then print("S5 : New Engine Speed = " .. EngSpeed .. " Smph"); end
	SM = S1;
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end
		
if (SM == S6) then
	--print("S6");
	Decel = Decel + 1;
	EngSpeed = StartingSpeed;
	BumpCounter(NO_LOOPS);
	print(" ");
	SM = S0;
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end
print("Invalid State " .. SM);		
return false;	-- false=stop calling loop(), true=continue to call loop()
end
--[[---------------------------------------------------------------------------------------]]
function cleanup()
--
print("cleanup() : TIU " .. MyTIUNo .. " Engine " .. MyEngineNo .. " Debug " .. Debug());

if (SetSpeed(0, 0, MyEngineNo, MyTIUNo) == nil) then
	print("SetSpeed() failed in cleanup()");
	else
	print("Speed set to zero in cleanup()");
	end;
DTOFinish = DTO(MyEngineNo, MyTIUNo);
if (DTOFinish == nil) then
	print("DTO() failed in cleanup()");
	DTOFinish = 0;
	end;
-- wait for dispatch list to empty
while(CountDispatchList() > 0) do Sleep(1); end
print(string.format("Run Time in cleanup() = %.2f Seconds", RunTime()));
if (DTOFinish > 0) then
	print(string.format("Trip Odometer in cleanup() = %.2f Smiles", DTOFinish));
	else
	print("Trip Odometer not available in cleanup()");
	end;
-- reset acceleration and deceleration values
Decel = 1
print("S0 : Set deceleration rate to " .. Decel .. " Smph/sec in cleanup()");
if (Rate(1, Decel, 0, MyEngineNo, MyTIUNo) == nil) then
	print("Rate() failed in cleanup()");
	end;
--
if (SoundOff) then
	SoundOnButton(MyEngineNo, MyTIUNo)
	Sleep(4);
	end;
--
if (ShutDown(0, MyEngineNo, MyTIUNo) == nil) then
	print("ShutDown() failed in cleanup()");
	else
	print("ShutDown() in cleanup()");
	end;
--
for iDecel=StartingDecel, EndingDecel do
	if (TableConstructor[iDecel] ~= nil) then
		TableConstructor[iDecel] = string.sub(TableConstructor[iDecel], 1, -3) .. "}";	-- remove trailing comma
		-- it is not really necessary to remove the trailing comma
		print (TableConstructor[iDecel]);
		end;
	end;
-- Error Summary for WIU and WTIU only
print(string.format("Retries %d  Echo %d Input %d Checksum %d Failures %d DCS Timeout %d", 
          GetDebugCounter(5), GetDebugCounter(18), GetDebugCounter(6), GetDebugCounter(2), GetDebugCounter(4), GetDebugCounter(14)));
-- TBD Error Summary for Radio
--
-- restart the garbage collector
collectgarbage("restart") ;
return true;
end
--[[---------------------------------------------------------------------------------------]]
function PTC()
	print(string.format("Run Time = %.2f Seconds", RunTime()));
	print(TableConstructor[Decel]);
	return true;
end
Function01Name, Function01Label = PTC, "Table Constructor";
--[[---------------------------------------------------------------------------------------]]
function ShowDTO()
	print(string.format("Run Time = %.2f Seconds", RunTime()));
	local DTOShow =  DTO(MyEngineNo, MyTIUNo);
	if (DTOShow  == nil) then
		print ("DTO() failed");
		return false;
		end;
	print(string.format("Trip Odometer = %.2f Smiles", DTOShow));
	return true;
end
Function10Name, Function10Label = ShowDTO, "Trip Odometer";
--[[---------------------------------------------------------------------------------------]]

--[[---------------------------------------------------------------------------------------]]
function SoundOffButton()
	print("Sound Off Button()");
	SD = GetVolume(MASTER_VOLUME, MyEngineNo, MyTIUNo);	-- get Volume
	if (SD == nil) then
		return Stop("GetVolume() failed in SoundOffButton()");
		end; 
	if (SD == 0) then SD = 75; end;
	print(" Sound Off - Engine " .. MyEngineNo .. " Level was " .. SD);
	EngineSoundSave = SD;
	SoundOff = true;
	SD = SetVolume(MASTER_VOLUME, 0, 0, MyEngineNo, MyTIUNo);
	if (SD == nil) then
		return Stop("SetVolume() failed in SoundOffButton()");
		end	
	return true;
end
Function04Name, Function04Label = SoundOffButton, "Sound Off";
--[[---------------------------------------------------------------------------------------]]
function SoundOnButton()
	print("Sound On Button()");
	if (EngineSoundSave == -1) then	
		SD = 90;
		else
		SD = EngineSoundSave;
		end;
	SD = EngineSoundSave;	
	print(" Sound On - Engine " .. MyEngineNo .. " Level restored to " .. SD);
	SoundOff = false;;
	SD = SetVolume(MASTER_VOLUME, SD, 0, MyEngineNo, MyTIUNo);
	if (SD == nil) then
		return Stop("SetVolume() failed in SoundOnButton()");
		end		
	return true;
end
Function05Name, Function05Label = SoundOnButton, "Sound On";
--[[---------------------------------------------------------------------------------------]]
