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

© Copyright 2025 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
--[[
When using this module, all variables (including functions!) must be declared through 
a regular assignment (even assigning nil will do) in a strict scope before being used 
anywhere or assigned to inside a nested scope.
https://github.com/lua-stdlib/strict
]]
local _ENV = require 'std.strict' (_G)	-- strict.lua will detect undeclared global variables
local _;
--
require([[defines]]);
--
-- Occupancy flags can be any integer value

local OCCUPANCYTIMEOUT = 3600;		-- drop dead timeout of 3600 seconds or 1 hour

function isEngine(EngNo)
	if (EngNo > 0) then return true; end
	return false;
	end
	
function notEngine(EngNo)
	if (EngNo == 0) then return true; end
	return false;
	end	
	
function isTagRear(TagLocation)
	if (TagLocation == TAG_ON_REAR_TRUCK) then return true; end
	return false;
	end

function isTagFront(TagLocation)
	if (TagLocation == TAG_ON_FRONT_TRUCK) then return true; end
	return false;
	end
	
function isDetector(Detector, x)
	if (Detector == x) then return true; end
	return false;
	end	
	
function isReader(Reader, x)
	if (Reader == x) then return true; end
	return false;
	end	
	
function isCarModel(CarModel, x)
	if (CarModel == x) then return true; end
	return false;
	end	
--[[---------------------------------------------------------------------------------------]]

function StopBlockAfterDelay(Layout, StopTable, StoppingBlockNo, EngineNo, direction, LastBlockSpeedLimit, BSL)
-- Calculate the stop delay based on parameters for either a "in block" stop or
-- an "at station" stop.
--
-- StopTable == true means use Stop in Block table, if false, use Stop at Station table
-- StoppingBlockNo is the block we are stopping in
-- EngineNo is the engine number
-- direction is the direction of the engine
-- LastBlockSpeedLimit is the speed at which the block was entered
-- BSL (BlockSpeedLimit) is the speed to which the engine is being slowed/spedup before it is asked to stop
--		if not specified, uses LastBlockSpeedLimit.
--
-- LastBlockSpeedLimit and BlockSpeedLimit may be equal
-- Either one may be greater than the other (requires the use of math.abs() to get a positive result)
--
-- Returns the stop delay in seconds and the target distance in inches
--
local Target_Station;
local Target_Distance;
local StopDelay;

local BlockSpeedLimit = BSL or LastBlockSpeedLimit;

Target_Station = BlockName[StoppingBlockNo];
if (StopTable == true) then
	if (EngineNo == 7 or EngineNo == 13) then
		-- the two GP7's are specified for passenger operation
		Target_Distance = Layout.STOPBlock(StoppingBlockNo, direction, PASSENGER);	--Passenger Target Distance;
		if (Target_Distance == nil) then
			-- use Freight distance if passenger distance is not specified
			Target_Distance = Layout.STOPBlock(StoppingBlockNo, direction, FREIGHT);	--Freight Target Distance;
			end;
		else
		Target_Distance = Layout.STOPBlock(StoppingBlockNo, direction, FREIGHT);	--Freight Target Distance;
		end
	end;
if (StopTable == false) then
	if (EngineNo == 7 or EngineNo == 13) then
		Target_Distance = Layout.STOPStation(StoppingBlockNo, direction, PASSENGER);	--Passenger StoppingDistance;
		else
		Target_Distance = Layout.STOPStation(StoppingBlockNo, direction, FREIGHT);	--Freight StoppingDistance;
		end
	end;
if (Target_Distance == nil) then
	Target_Distance = 0;
	StopDelay = 0;
	return EmergencyStop("SAD : Target_Distance missing in Layout description");
	end
SetCounter(C4, Target_Distance);
print("SAD : " .. BlockName[StoppingBlockNo] .. " " .. DirectionNames[direction] .. ", Target Distance = " .. Target_Distance);
--
-- Equation for Stop Delay calculation in seconds:
-- 			Time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr / (12 in/ft * 5280 ft/mile * X Smiles/hr)
--
-- get instantaneous speed
local CoastingSpeed = GetSpeed(EngineNo, Layout.MASTER_TIU);	-- get speed as engine covers tag
if (CoastingSpeed == nil) then
	-- one retry
	CoastingSpeed = GetSpeed(EngineNo, Layout.MASTER_TIU);
	if (CoastingSpeed == nil) then
		return Stop("SAD : Failed in call to GetSpeed() after retry");
		end;
	end;
-- set speed to stop any acceleration or deceleration
local Spd = SetSpeed(CoastingSpeed, 0, EngineNo, Layout.MASTER_TIU);
if (Spd == nil) then
	-- one retry
	Spd = SetSpeed(CoastingSpeed, 0, EngineNo, Layout.MASTER_TIU);
	if (Spd == nil) then
		return Stop("SAD : Failed in call to SetSpeed() after retry");
		end;
	end;
	
--	The coasting distance is the target distance minus the stopping distance
local StoppingDistance = Layout.STOPDistance(EngineNo, CoastingSpeed);	-- inches
local CoastingDistance = Target_Distance - StoppingDistance;			-- inches
local CoastingDelay =  CoastingDistance * (48 / (12 * 5280 * CoastingSpeed)) * 3600;		-- seconds
print(string.format("SAD : CoastingDistance = %.3f in - CoastingSpeed = %.3f Smph - CoastingDelay = %.3f seconds",
	CoastingDistance, CoastingSpeed, CoastingDelay));
-- Then set the delay in seconds to wait before sending the engine stop command
StopDelay = CoastingDelay;
--
--
--
SetCounter(C5, StoppingDistance);
SetCounter(C6, StopDelay);
print(string.format("SAD : %s : Target Distance = %d in - Stopping Distance = %.3f in - Speed %d Smph = Stop Delay %.3f seconds",
	BlockName[StoppingBlockNo], Target_Distance, StoppingDistance, BlockSpeedLimit, StopDelay));
-- if the SpeedAdjustDelay value comes out negative, that means we have to stop
-- very quickly. If that happens, set the Decel rate very high and then
-- set the speed to zero.	
if (StopDelay < 0) then
	StopDelay = 0;
	return EmergencyStop("SAD : Engine cannot stop in time (Stopping Distance > Target_Distance)");
	end;
-- Stop delay any positive value including 0
if (StopDelay == 0) then
	Rate(8, 8, StopDelay, EngineNo, Layout.MASTER_TIU);	-- stop as quickly as possible
	end;
local LastSentSpeed = SetSpeed(0, StopDelay, EngineNo, Layout.MASTER_TIU);	-- stop engine;
if (StopDelay == 0) then
	Rate(Layout.AccDecRate, Layout.AccDecRate, StopDelay, EngineNo, Layout.MASTER_TIU);
	end;
print(string.format("SAD : Set speed to 0 Smph after %.3f seconds", StopDelay))
return StopDelay, Target_Distance, LastSentSpeed;
end;
--[[---------------------------------------------------------------------------------------
--
Occupancy Flags are supported as part of the Engineer On Board project.
Up to 100 flags are available to indicate block occupancy. 
A Zero value (UNOCCUPIED) means the block is unoccupied. 
A non zero value means the block is occupied.

The Layout window must be visible to use these functions and the flags themselves must
be defined on that Layout window.

local Layout4BlockOcc = {};			-- Table to hold the APB Master Block Occupancy Status, OCCUPIED or UNOCCUPIED
									-- Values in this table can be:
									--  0	UNOCCUPIED
									-- >0	OCCUPIED
									--		1	Occupied or a thru/passing with 1 branch occupied
									--		2	Thru/passing with both branches occupied

function SetOccupancy(block, value, Wait)
	TrySetFlag(block, value)

function TrySetOccupancy(block, va
	TrySetFlag(block, value)

function ClearOccupancy(block, value)
	SetFlag(block, UNOCCUPIED)

function OverrideOccupancy(block, value)
	SetFlag(block, value)

function MultiSetOccupancy(blocks, value, Wait)
	TryMultiSetFlag(blocks, value)

function TryMultiSetOccupancy(blocks, value)
	TryMultiSetFlag(blocks, value)

function SingleSetOccupancy(blocks, value, Wait)
	TrySetFlag(occupiedblock, value)

function ClearAllOccupancy()
	OverrideOccupancy(block, UNOCCUPIED)
--
]]
--[[---------------------------------------------------------------------------------------]]
--ClearAllOccupancy
-- Clear all occupancy flags to UNOCCUPIED
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
--
function ClearAllOccupancy()
local block;
for block = FIRST_BLOCK, LAST_BLOCK do
	OverrideOccupancy(block, UNOCCUPIED);
	end;
return;
end;

--[[---------------------------------------------------------------------------------------]]
--SetOccupancy
-- Set an integer value into the block occupancy flag
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
-- Waits 'forever' for the block to show as unoccupied before it sets occupancy
-- returns two values
-- 1: true on success, false on failure
-- 2: on failure, the block numbers that would not lock, otherwise nil
-- Wait is the number of seconds to wait, default to OCCUPANCYTIMEOUT seconds
-- When occupied, the Flag should be set to the engine number of the engine in the block.
--
function SetOccupancy(block, value, Wait)
if (block < FIRST_BLOCK or block > LAST_BLOCK) then
	return EmergencyStop("Invalid Block Number in SetOccupancy");
	end;
if (value == 0) then
	return EmergencyStop("Value in SetOccupancy is zero, use ClearOccupancy instead");
	end;
local WaitTime = Wait or OCCUPANCYTIMEOUT;
-- set the occupancy flag if it is zero or we already have ownership - use lock
while (not TrySetFlag(block, value)) do
	-- wait 'forever'
	if (((WaitTime >> 1) % 10) == 0) then
		print("SetOccupancy(" .. WaitTime .. ") Waiting for " .. BlockName[block] .. " to clear");
		end;
	Sleep(2);
	WaitTime = WaitTime - 2;
	if (WaitTime <= 0) then
		return EmergencyStop("SetOccupancy(" .. WaitTime .. ") Timed out waiting for " .. BlockName[block] .. " to clear"); 
		end	
	end;
print("SetOccupancy(" .. WaitTime .. ") flag on " .. BlockName[block] .. " to " .. string.format("%s for Eng#%d", DirectionNames[(value & 0xFF00) >> 8], (value & 0x00FF)));
return true;
end;

--[[---------------------------------------------------------------------------------------]]
-- TrySetOccupancy
-- Tries to set a value into the block occupancy flag
-- Only tries once. Returns true on success, false if the block is occupied.
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
--
function TrySetOccupancy(block, value)
if (block < FIRST_BLOCK or block > LAST_BLOCK) then
	return EmergencyStop("Invalid Block Number in TrySetOccupancy");
	end;
if (value == 0) then
	return EmergencyStop("Value in TrySetOccupancy is zero, use ClearOccupancy instead");
	end;
-- set the occupancy flag if it is zero or we already have ownership - use lock
if (not TrySetFlag(block, value)) then
	-- did not get a lock
	print("TrySetOccupancy flag on " .. BlockName[block] .. " - could not get lock");
	return false;
	end;
print("TrySetOccupancy flag on " .. BlockName[block] .. " to " .. string.format("%s for Eng#%d", DirectionNames[(value & 0xFF00) >> 8], (value & 0x00FF)));
return true;
end;

--[[---------------------------------------------------------------------------------------]]
-- ClearOccupancy
-- Clear a block occupancy flag to zero if we have ownership, that is,
-- we are occupying the block or if the block is only soft occupied
-- Ownership means that the lower byte matches the lower byte of the value
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
-- Flag value of 666 overrides all other values and sets the flag to unoccupied
--
function ClearOccupancy(block, value)
if (block < FIRST_BLOCK or block > LAST_BLOCK) then
	return EmergencyStop("Invalid Block Number in ClearOccupancy");
	end;
local iFlag = GetFlag(block);
-- flag value includes direction and engine number but we only compare on engine number
if ((value ~= nil and ((iFlag & 0xFF) == (value & 0xFF))) or (iFlag == 666)) then
	print("ClearOccupancy flag on " .. BlockName[block]);
	SetFlag(block, UNOCCUPIED);
	else
	print("ClearOccupancy flag not owned on " .. BlockName[block] );
	end;
return true;
end;

--[[---------------------------------------------------------------------------------------]]
-- OverrideOccupancy
-- set a block occupancy flag without waiting for lock or checking ownership
-- Can be very dangerous if your script makes a mistake.
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
--
function OverrideOccupancy(block, value)
if (block < FIRST_BLOCK or block > LAST_BLOCK) then
	return EmergencyStop("Invalid Block Number in OverrideOccupancy");
	end;
if (value == nil) then
	value = UNOCCUPIED;
	end;
if (value >= 1) then
	print("OverrideOccupancy flag on " .. BlockName[block] .. " to " .. string.format("%s for Eng#%d", DirectionNames[(value & 0xFF00) >> 8], (value & 0x00FF)));
	else
	print("OverrideOccupancy flag on " .. BlockName[block] .. " to " .. value);
	end;
SetFlag(block, value);
return true;
end;

--[[---------------------------------------------------------------------------------------]]
-- GetOccupancy
-- fetch a block occupany flag
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
--
function GetOccupancy(block)
if (block < FIRST_BLOCK or block > LAST_BLOCK) then
	return EmergencyStop("Invalid Block Number in GetOccupancy");
	end;
return GetFlag(block);
end;

--[[---------------------------------------------------------------------------------------]]
-- MultiSetOccupancy
-- Set a value into several block occupancy flags. All blocks must be unoccupied.
-- Waits OCCUPANCYTIMEOUT seconds or the given 'Wait' time in seconds for the blocks to show as unoccupied before it sets occupancy
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
-- Returns two values
-- 1: true on success, false on failure
-- 2: on failure, one of the block numbers that would not lock, otherwise 0
-- Wait is the number of seconds to wait, default to OCCUPANCYTIMEOUT
-- blocks is a table of the form {2, 44, 21}

function MultiSetOccupancy(blocks, value, Wait)
if (type(blocks) ~= "table") then return EmergencyStop("MultiSetOccupancy block not a table"), 0; end
-- loop through all blocks in table
for k, v in ipairs(blocks) do
	if (v < FIRST_BLOCK or v > LAST_BLOCK) then
		return EmergencyStop("Invalid Block Number in MultiSetOccupancy"), 0;
		end;
	end;
if (value == 0) then
	return EmergencyStop("Value in MultiSetOccupancy is zero, use ClearOccupancy instead"), 0;
	end;
local WaitTime = Wait or OCCUPANCYTIMEOUT;
-- set the occupancy flag if it is zero or we already have ownership - use lock
local occupiedblock;
while (true) do
	local result;
	result, occupiedblock = TryMultiSetFlag(blocks, value)
	if (result) then
		break;
		end;
	-- wait almost forever
	if (((WaitTime >> 1) % 10) == 0) then
		print("MultiSetOccupancy(" .. WaitTime .. ") Waiting for " .. BlockName[occupiedblock] .. " to clear");
		end;
	local SleepResult = Sleep(2);
	if (SleepResult ~= true) then	-- return false on error
		return false, 0;
		end;
	WaitTime = WaitTime - 2;
	if (WaitTime <= 0) then
		return EmergencyStop("MultiSetOccupancy(" .. WaitTime .. ") Timed out waiting for " .. BlockName[occupiedblock] .. " to clear"), blocks[1]; 
		end
	end;
-- loop through all blocks in table
for k, v in ipairs(blocks) do
	print("MultiSetOccupancy(" .. WaitTime .. ") flag on " .. BlockName[v] .. " to " .. string.format("%s for Eng#%d", DirectionNames[(value & 0xFF00) >> 8], (value & 0x00FF)));
	end;
return true, 0;
end;

--[[---------------------------------------------------------------------------------------]]
-- TryMultiSetOccupancy
-- Set a value into several block occupancy flags. All blocks must be unoccupied.
-- Only tries ONCE
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
-- returns two values
-- 1: true on success, false on failure
-- 2: on failure, the block number that would not lock, otherwise nil
--
function TryMultiSetOccupancy(blocks, value)
-- blocks is a table of the form {2, 44, 21}
if (type(blocks) ~= "table") then return EmergencyStop("TryMultiSetOccupancy block not a table"); end
for k, v in ipairs(blocks) do
	if (v < FIRST_BLOCK or v > LAST_BLOCK) then
		return EmergencyStop("Invalid Block Number in TryMultiSetOccupancy"), nil;
		end;
	end;
if (value == 0) then
	return EmergencyStop("Value in TryMultiSetOccupancy is zero, use ClearOccupancy instead"), nil;
	end;
-- set the occupancy flag if it is zero or we already have ownership - use lock
local success, occupiedblock = TryMultiSetFlag(blocks, value);
if (not success) then
	-- did not get a lock
	print("TryMultiSetOccupancy flag on " .. BlockName[occupiedblock] .. " - could not get lock");
	return false, occupiedblock;
	end;
-- loop through all blocks in table
for k, v in ipairs(blocks) do
	print("TryMultiSetOccupancy flag on " .. BlockName[v] .. " to " .. string.format("%s for Eng#%d", DirectionNames[(value & 0xFF00) >> 8], (value & 0x00FF)));
	end;
return true, nil;
end;

--[[---------------------------------------------------------------------------------------]]
-- SingleSetOccupancy
-- Set a "value" into one of several block occupancy flags
-- Waits OCCUPANCYTIMEOUT seconds or the given "Wait" time in seconds for one of the blocks to show as unoccupied before it sets occupancy
-- Occupancy Flag (SetFlag(), GetFlag(), etc) is available system wide. One use to construct the hint on each occupancy flag on the Layout window.
-- returns two values
-- 1: true on success, false on failure
-- 2: on success, the block number that locked, otherwise nil
-- "Wait" is the number of seconds to wait, default to OCCUPANCYTIMEOUT
-- "blocks" is a table of the form {2, 44, 21} or {2}
--
function SingleSetOccupancy(blocks, value, Wait)
if (type(blocks) ~= "table") then return EmergencyStop("SingleSetOccupancy blocks not a table"), 0; end
-- loop through all blocks in table
for k, v in ipairs(blocks) do
	if (v < FIRST_BLOCK or v > LAST_BLOCK) then
		return EmergencyStop("Invalid Block Number in SingleSetOccupancy"), nil;
		end;
	end;
if (value == 0) then
	return EmergencyStop("Value in SingleSetOccupancy is zero, use ClearOccupancy instead"), nil;
	end;
local WaitTime = Wait or OCCUPANCYTIMEOUT;
-- set the occupancy flag if it is zero or we already have ownership - use lock
local occupiedblock;
while (true) do
	for k,v in ipairs(blocks) do
		occupiedblock = v;
		-- lock the first block that becomes available
		local result = TrySetFlag(occupiedblock, value);
		if (result) then
			print("SingleSetOccupancy(" .. WaitTime .. ") flag on " .. BlockName[occupiedblock] .. " to " .. string.format("%s for Eng#%d", DirectionNames[(value & 0xFF00) >> 8], (value & 0x00FF)));
			return true, occupiedblock;
			end;
		if (((WaitTime >> 1) % 10) == 0) then
			print("SingleSetOccupancy(" .. WaitTime .. ") Waiting for " .. BlockName[occupiedblock] .. " to clear");
			end;
		end;
	-- wait almost forever
	Sleep(2);
	WaitTime = WaitTime - 2;
	if (WaitTime <= 0) then
		return EmergencyStop("SingleSetOccupancy(" .. WaitTime .. ") Timed out waiting for " .. BlockName[occupiedblock] .. " to clear"); 
		end		
	end;
return false, nil;
end;
--[[---------------------------------------------------------------------------------------]]

function WaitforStopSignaltoClear(Wait)
-- return true on sucess, return false on error, EmergencyStop on timeout
local Aspect = (GetCabAspect(HOMESIGNAL));
-- check that home signal is AsStopSignal, if it is, we MUST Stop until it clears
if (Aspect == AsStopSignal) then
	-- we are stopped, so wait until the Stop Signal aspect is cleared.
	print("Home Signal aspect is Stop Signal. Wait for it to clear before proceeding.");
	local WaitTime = Wait or 1800;	-- let's say that 30 minutes is 'forever'
	-- Wait for the signal to changed from Stop Signal
	while (Aspect == AsStopSignal) do
		-- wait 'forever'
		if (((WaitTime >> 1) % 10) == 0) then
			print("Waiting for Stop Signal to clear");
			end;
		local SleepResult = Sleep(2);
		if (SleepResult ~= true) then	-- return false on error
			return false;
			end;
		WaitTime = WaitTime - 2;
		if (WaitTime <= 0) then
			return EmergencyStop("Timed out waiting for Stop Signal to clear"); 
			end;
		Aspect = (GetCabAspect(HOMESIGNAL));
		end;
	print ("Home Signal cleared to : " .. AspectNames[Aspect]);
	end;
return true;
end;
--[[---------------------------------------------------------------------------------------]]
-- from   https://q-syshelp.qsc.com/DeveloperHelp/Content/Code_Examples/Hex_Data_Handling.htm

--Convert a string's bytes to a string of human readable hex encoded value pairs
function str2hex(str)
  return str:gsub(".",
    function(byte)
      return string.format("%02X", string.byte(byte))
    end
  )
end

--Translate a string of 2 character hex values (human readable) to a string of character encoded bytes
function hex2str(str)
  return str:gsub("..", 
    function(byte) 
      return string.char( tonumber(byte,16) ) 
    end
  )
end

--Return a numerical equivalent of the bytes within the string
--Note that a maximum of 7 bytes can be converted by this function before the number will overflow
function hex2num(str)
  local value = 0
  if str:len()>0 then
    for i=1,str:len() do
      value = str:byte(i) + (value << 8)
    end
  end
  return value
end

--Return a string of bytes equivalent to the numerical value
function num2hex(val)
  local str = ""
  while val > 0 do
    str = string.char(val % 256) .. str 
    val = val >> 8
  end
  return str
end

--[[
Handling Hex and Binary Data

Lua scripts often encounter hex or binary data coming from remote devices. Handling this 
data effectively is achieved by planning the context of how the data is formatted, which 
avoids inconsistencies and confusion.

Received data from the TCPSocket, UDPSocket, and Serial objects is formatted as ASCII characters 
and wrapped in a string. The data is stored sequentially as received and each character 
represents one byte (0-255 decimal).

A byte value encoded as a character in a string can be input as a hex-encoded escape character
, a decimal value escape character, a character, or encoded from a number using the string.char
 function:

“\x41” == “\65” == ”A” = string.char(65) == string.char(0x41)

The same byte can be encoded as a number using hex notation or decimal notation, translated 
from a character to a numerical byte:

0x41 == 65 == string.byte(“A”)

Translating this byte data into a readable format (strings or numbers) is a common task. This 
allows the non-printable characters in the string of bytes to be used more effectively. The 
following helper functions support these tasks.

--String conversion test
local strdata = "\x48\x65\x6C\x6C\x6F\x20\x57\x6F\x72\x6C\x64\x21"
print(strdata)           --> Hello World!
hexdata = str2hex(strdata)
print(hexdata)           --> 48656C6C6F20576F726C6421
print(hex2str(hexdata))  --> Hello World!

--Number conversion test
strdata = "\xDE\xCA\xFB\xAD"
print(strdata)           --> ????   (4 unprintable characters)
print(str2hex(strdata))  --> DECAFBAD
numdata = hex2num(strdata)
print(numdata)           --> 3737844653
hexdata = num2hex(numdata)
print(hex2str(hexdata))  --> DECAFBAD
]]
--[[---------------------------------------------------------------------------------------]]
