--[[
---------------------------------------------------------------------------
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
-- Commands from RTCMegaMaster.h
BELL_ON = "w4"
BELL_OFF = "bFFFB"
HDLT_ON ="ab1"
HDLT_OFF = "ab0"
WHISTLE_ON = "w2"
WHISTLE_OFF = "bFFFD"
DIRECTION_FORWARD = "d0"
DIRECTION_REVERSE = "d1"
DIRECTION_CHANGE = "d%d"
COMMAND_MODE = "m4"
COMMAND_STARTUP = "u4"
COMMAND_SHUTDOWN = "u5"
COMMAND_STARTUP_EXTENDED = "u6"
COMMAND_SHUTDOWN_EXTENDED = "u7"
COMMAND_STOP = "s0"
COUPLER_FRONT = "c0"
COUPLER_REAR = "c1"
SMOKE_EXHAUST_MIN = "ab12"
SMOKE_EXHAUST_MED = "ab11"
SMOKE_EXHAUST_MAX = "ab10"
SMOKE_EXHAUST_ON = "abF"
SMOKE_EXHAUST_OFF = "abE"
MARKERS_ON = "ab1C"
MARKERS_OFF = "ab1D"
BEACON_ON = "abD"
BEACON_OFF = "abC"
CAB_CHAT_ON = "ab13"
CAB_CHAT_OFF = "ab14"
CHUFF_RATE_2 = "g2"
CHUFF_RATE_4 = "g4"
COUPLER_CLASH = "h1"
--
AUX1_ON = "ab3"
AUX1_OFF = "ab2"
AUX2_ON = "ab4"
AUX2_OFF = "ab5"
AUX3_ON = "ab6"
AUX3_OFF = "ab7"
--
ACC_ON = "Ae%02d"
ACC_OFF = "Af%02d"
ACC_ALL_ON = "AaFFFFFFFFFFFFFF"
ACC_ALL_OFF = "Aa00000000000000"
SW_STRAIGHT = "Ac%02d"
SW_DIVERGE = "Ad%02d"
SW_ALL_STRAIGHT = "Ab05555555555555555555555555"
SW_ALL_DIVERGE = "Ab0AAAAAAAAAAAAAAAAAAAAAAAAA"
SW_CLOSED = SW_STRAIGHT
SW_THROWN = SW_DIVERGE
SW_ALL_CLOSED = SW_ALL_STRAIGHT
SW_ALL_THROWN = SW_ALL_DIVERGE
SPEED_SET = "s%d"
PLAY_SOUND = "n%d"
SINGLE_TOOT = "n41"
CROSSING = "n42"
DOUBLE_TOOT = "n43"
TRIPLE_TOOT = "n44"
IDLE_SOUND = "i%d"
VOLUME_CHANGE = "v%1d%03d"
MASTER_VOLUME = 0
ENGINE_VOLUME = 1
ACCENT_VOLUME = 2
HORN_VOLUME = 3
BELL_VOLUME = 4
COUPLER_CMD = "c%d"
FRONT = 0
REAR = 1
SETCMD = "ab%X"
CMD_HDLT_OFF = 0x00
CMD_HDLT_ON = 0x01
CMD_INT_OFF = 0x02
CMD_INT_ON = 0x03
CMD_MARS_OFF = 0x04
CMD_MARS_ON = 0x05
CMD_DITCH_OFF = 0x06
CMD_DITCH_ON = 0x07
CMD_BEACON_ON = 0x0D
CMD_BEACON_OFF = 0x0C
CMD_SMOKE_OFF = 0x0E
CMD_SMOKE_ON = 0x0F
CMD_SMOKE_MAX = 0x10
CMD_SMOKE_MED = 0x11
CMD_SMOKE_MIN = 0x12
CMD_MARKERS_ON = 0x1C
CMD_MARKERS_OFF = 0x1D
CMD_NUMBERBOARDS_ON = 0x05
CMD_NUMBERBOARDS_OFF = 0x04
ACC_RATE = "Da%d"
DEC_RATE = "Dd%d"
EMERGENCY_STOP = "o0"
LABOR_REV_UP = "r16"
LABOR_REV_DOWN = "r17"
LABOR_REV_NORMAL = "r15"

--[[---------------------------------------------------------------------------------------]]

-- Constants
-- Engine state
STARTED = true;
SHUTDOWN = false;
-- AIU Acessory state
ON = true;
OFF = false;
-- Engine direction
FORWARD = true;
REVERSE = false;
-- AIU switch position
STRAIGHT = true;
CLOSED = true;
NORMAL = true;
OPEN = false;
DIVERGE = false;
THROWN = false;
REVERSE = false;
-- Coupler
FRONT = true;
REAR = false;
-- maxium number of Program Control Windows
PCMAX = 10;
--[[---------------------------------------------------------------------------------------]]

-- Packet Constants from RTC_NFC.h
-- Engine Data in binary - 16 bytes
-- NOTE: if you add or change any of these entries, make the corresponding change to RTC_NFC.H
-- and to the String Lists on the debug version of the main RTC window
	  --TagID;	  -- 0 = tag not programmed, 0x88 = tag is programmed
		 TAG_PROGRAMMED = 0x88
	  --EngineNo;   -- 0 = not an Engine or LashUp (look at CarModel), otherwise Engine Number  1-99 (look at CarModel)
						  -- 101-120 LashUp Number
		 NOT_AN_ENGINE = 0
	  --TagLocation;  -- 0 = not on a truck, 1 = on Front truck, 2 = on Rear truck (Engines and Cars)
		 NOT_ON_A_TRUCK = 0
		 TAG_ON_FRONT_TRUCK = 1
		 TAG_ON_REAR_TRUCK = 2
		 TAG_ON_TRUCK = 4
		 TAG_AT_MIDPOINT = 5
	  --EngineType; -- 0 = not an engine, 1 = Diesel,  2 = Steam (Engines Only)
		 NOT_AN_ENGINE = 0
		 DIESEL_ENGINE_TYPE = 1
		 STEAM_ENGINE_TYPE = 2	 
		 GAS_MECHANICAL_ENGINE_TYPE = 3
		 GAS_ELECTRIC_ENGINE_TYPE = 4
		 STEAM_TURBINE_ENGINE_TYPE = 5
		 ELECTRIC_TYPE = 6
		 TURBINE_TYPE = 7
		 DIESEL_MECHANICAL_TYPE = 8
	  --Coupler
		 NO_COUPLER = 0
		 REAR_REMOTE_COUPLER_ONLY = 1
		 FRONT_REMOTE_COUPLER_ONLY = 2
		 FRONT_AND_REAR_REMOTE_COUPLERS = 3
		 COUPLER_UNKNOWN = 4
		 REAR_MANUAL_COUPLER_ONLY = 5
		 FRONT_MANUAL_COUPLER_ONLY = 6
		 FRONT_AND_REAR_REMOTE_COUPLERS = 7
		 REAR_DUMMY_COUPLER_ONLY = 8
		 FRONT_DUMMY_COUPLER_ONLY = 9
		 FRONT_DUMMY_AND_REAR_MANUAL_COUPLERS = 10
		 NOT_APPLICABLE = 0x80	-- Coupler is only used for Engines		 
	  --SXS;		-- 0 = no SXS Sound, 1 = has SXS sound at clip 42 (Engines only)
		 NO_SXS_SOUND = 0
		 HAS_SXS_SOUND = 1
	  --CarModel;	-- see below
		-- if EngineNo == NOT_AN_ENGINE
		 UNKNOWN_CARMODEL = 0 -- 0 = unknown
		 BOXCAR = 1 -- 1 = Boxcar
		 TANK_CAR = 2 -- 2 = Tank Car
		 FLAT_CAR = 3 -- 3 = Flat Car
		 CABOOSE = 4 -- 4 = Caboose
		 TENDER = 5 -- 5 = Tender
		 GONDOLA = 6 -- 6 = Gondola
		 COAL_HOPPER = 7 -- 7 = Coal Car
		 ORE_HOPPER = 8 -- 8 = Ore Car
		 MOW_FLATCAR = 9 -- 9 = MOW Flatcar
		 MOW_CRANE_CAR = 10 -- 10 = MOW Crane Car
		 MOW = 11 -- 11 = MOW Misc
		 COACH = 12 -- 12 = Passenger Coach
		 OBSERVATION = 13  -- 13 = Passenger Observation
		 BAGGAGE = 14 -- 14 = Baggage
		-- xx-255 TBD
		-- if EngineNo != NOT_AN_ENGINE (Lead Engine for LashUps)
		 UNKNOWN_ENGINEMODEL = 0 -- 0 = unknown
		 GP38_2 = 1 -- 1 = GP38-2
		 GP7 = 2 -- 2 = GP7
		 GP9 = 3 -- 3 = GP9
		 U28B = 4 -- 4 = U28B
		 BERKSHIRE = 5 -- 5 = Berkshire
		 DOODLEBUG = 6 -- 6 = Doodlebug
		 SW1200 = 7 -- 7 = SW1200
		 SW1500 = 8 -- 8 = SW1500
		 SWITCHER_0_6_0 = 9 -- 9 = 0-6-0 Switcher
		 SWITCHER_0_8_0 = 10-- 10 = 0-8-0 Switcher
		 H_9 = 11 -- 11 = 2-8-0 H-9 Consolidation
		 SW_9 = 12 -- 12 = SW-9
		 RS_3 = 13 -- 13 = Alco RS-3
		 MP15DC = 14 -- 14 EMD MP15DC
		 PCC = 15 -- 15 Presidents Conference Committee Trolley
		-- xx-255 TBD
	  --Railroad;   -- see below
		 UNKNOWN_RAILROAD = 0
		 P_AND_LE = 1
		 A_AND_S = 2
		 NYC = 3
		 B_AND_O = 4
		 P_AND_E = 5
		 LE_AND_E = 6
		 P_RR = 7
		 NH = 8
		 RDG = 9
         J_AND_L = 10
         Isalys = 11
         LV = 12
		AC		= 13
		ATSF	= 14
		AC		= 15
		BAR		= 16
		B_AND_A		= 17
		BM		= 18
		BCR_AND_N	= 19
		BN		= 20
		BNSF	= 21
		CPRR	= 22
		CNJ		= 23
		CSX		= 24
		CV		= 25
		CO		= 26
		CBQ		= 27
		MILW	= 28
		CGW		= 29
		RI		= 30
		CNW		= 31
		C_AND_S		= 32
		CR		= 33
		D_AND_RG	= 34
		D_AND_RGW	= 35
		DTI		= 36
		EJ_AND_E	= 37
		EL		= 38
		Erie	= 39
		GT		= 40
		GN		= 41
		IC		= 42
		LN		= 43
		MCR		= 44
		MSTL	= 45
		MKT		= 46
		MP		= 47
		NS		= 48
		OW		= 49
		NKP		= 50
		NS		= 51
		NW		= 52
		NP		= 53
		PC		= 54
		SAL		= 55
		SP		= 56
		SPSR	= 57
		SPSF	= 58
		TNO		= 59
		WM		= 60
		WP		= 61
		WC		= 62
		PRC 	= 63
		 
        -- Many more can be defined. see "Tag Programmer" or the Railroad Name String list on the RTC main window --
	  --CarLength;  -- Length of car in centimeters, coupler face to coupler face (include Tender)
	  --TagOffset;  -- Distance from center of tag to coupler face in centimeters
						  -- for engines/cars with two tags, the distance must be same for both tags
	  --CarNumber[6];  -- Up to 6 digit engine/car number in ASCII (w/leading spaces) "  2808" -- NOT NULL TERMINATED
--[[---------------------------------------------------------------------------------------]]
-- The first six counters define the fields in the Program Control window that the Lua scripts can
-- increment (BumpCounter()), decrement (DecCounter()), read (GetCounter()) and clear (ClearCounter()).
-- Be careful if you change the TAGSUB, UNIQTAG or TAGCNT counters. These counters
-- are used by the tag detection system and changing them at the wrong time may confuse
-- the tag detection. Those counters appear on the Tags Detected List window.
-- The ELAPSEDTIME counter counts seconds and its probably not a good idea to change this value.
COUNTER01 = 0 -- User defined Counter 1
COUNTER02 = 1 -- User defined Counter 2
COUNTER03 = 2 -- User defined Counter 3
COUNTER04 = 3 -- User defined Counter 4
COUNTER05 = 4 -- User defined Counter 5
COUNTER06 = 5 -- User defined Counter 6
ELAPSEDTIME = 6   -- Elapsed Time Counter 7
TAGSUB = 10   -- The number of UID only tags detected that were replaced by tag data from previous tags
UNIQTAG = 11  -- The number of unique tags ever detected by your and previous scripts
TAGCNT = 12   -- The number of tags detected by your script
ENGINENO = 13	-- The value in the Engine Number spinner
NUMENG = 14		-- the number of started up engines and lashups
--[[---------------------------------------------------------------------------------------]]
-- Beep() parameter
DEFAULT_BEEP = 0;
CRITICAL_STOP = 1;
QUESTION = 2;
EXCLAMATION = 3;
ASTERISK  = 4;

--[[---------------------------------------------------------------------------------------]]
-- Playsound WAV files in Remote_Control.rc
RELAY_CLICK = 100; -- "Relay Clicking.wav"
TOY_WHISTLE = 101; -- "toy-train-whistle.wav"
MOUSE_CLICK = 102; -- "positive-interface-click.wav"
N_O_SQUARE = 103; -- "Disneyland New Orleans Square Telegraph.wav"
AIR_HOSE = 104;   -- "air-hose-disconnect.wav"

--[[---------------------------------------------------------------------------------------]]
-- LED colors for the SetLED() and other functions that require a color value
clGreen = 0;
clYellow = 1;
clRed = 2;
clClear = 3;
clBlack = 4;
clBlue = 5;
clFuchsia = 6;
--[[---------------------------------------------------------------------------------------]]
-- Packet Types
-- These numeric values are the different responses that RTC can receive
-- from the Detectors
-- Start of Packet Character
START_OF_PACKET = 0xFF
-- End of Packet Character
END_OF_PACKET = 0xF0

-- NOP - ACK - NAK - WACK
NOP = 0x00
ACK = 0x61
NAK = 0x62
WACK = 0x63

--Error:
--0x10 Didn't find PN53x detector on port X
PN53xNOTFOUND = 0x10
--0x11 Unable to read Block X
CANTREADBLOCK = 0x11
--0x12 Tag not Programmed
TAGNOTPROG = 0x12
--0x13 Unable to authenticate
AUTHERROR = 0x13
--0x14 SOP character missing
SOPMISSING = 0x14
--0x15 Write block error
WRITEERROR = 0x15
--0x16 No Tag Detected When Expected
NOTAGDETECTED = 0x16

--Tag Data (TAGDATABLOCK4 also returns the Tag UID):
--0x20 not used
--0x21 Block 4
TAGDATABLOCK4 = 0x21
--0x22 Block 5
TAGDATABLOCK5 = 0x22
--0x23 Block 6
TAGDATABLOCK6 = 0x23
--0x24 Block 7
TAGDATABLOCK7 = 0x24

--Information:
--0x01 Hello X RFID Readers Attached (RTCNFCIRQ version in Reader byte and TagCount byte)  
HELLOPACKET = 0x01
--0x03 Detector Y Reader X Detected
TAGDETECTED = 0x03
--0x04 Found chip PN5xx
PN53xFOUND = 0x04
--0x05 Waiting for an ISO14443A tag detection
WAITING = 0x05
--0x06 Detector Y Sensor X Detected
SENSORDETECTED = 0x06
--[[---------------------------------------------------------------------------------------]]
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
-- These constants match constants in the Program Control code in RTC
-- They must not be changed
--[[---------------------------------Event Types------------------------------------------]]
NOEVENT = 0;
RUNTOEVENT = 1;
STARTUPENGINE = 2;
SHUTDOWNENGINE = 3;
ONSHEET = 4;
BLOCK_OCCUPANCY_EVENT = 5;
TAG_EVENT = 6;
--[[-------------------------Direction-------------------------------------]]
WB = 1;
EB = 2;
NB = 3;
SB = 4;
DirectionNames = {
	[0] = "Unknown Direction",
	[WB] = "WB",	-- westbound
	[EB] = "EB",	-- eastbound
	[NB] = "NB",	-- northbound
	[SB] = "SB",	-- southbound
	};
--[[----------Block Occupancy as used in APB Master.lua script-------------]]
UNOCCUPIED = 0;  	-- Clear
OCCUPIED = 1;		-- Occupied. Normally == 1 but can be 2 when both tracks of a sideing are occupied

FLAGLIMIT = 100;	-- maximum number of flags allowed
--[[------------Signals on the Program Control Cab Display-----------------]]
HOMESIGNAL = 0;
DISTANTSIGNAL = 1;
--[[-----------------------Aspect Names------------------------------------]]
AspectNames = {
	"None",		-- [1]
	"Dark",		-- [2], etc 
	"Stop Signal", 
	"Clear", 
	"Stop and Proceed", 
	"Approach", 
	"Medium Clear",
	"Medium Approach", 
	"Slow Clear", 
	"Slow Approach", 
	"Approach Medium", 
	"Approach Slow",
	"Restricting", 
	"Approach Limited", 
	"Limited Clear", 
	"Limited Approach",
	"Medium Approach Slow",	--[17]
	"Test",					--[18]
	};

--[[-----------------------Aspect Selection------------------------------------]]
-- Reflect any changes to this list to Layout.h in RTC
--
AsNone = 1;					-- Invalid Aspect or the Signal does not exist
AsDark = 2;					-- All Lights Off
AsStopSignal = 3;			-- Rule 292 without Number Plate or with 'A' on Number Plate. Indication: Stop
AsClear = 4;				-- Rule 281 					Indication: Proceed
AsStopandProceed = 5;		-- Rule 291 with Number Plate.	Indication: Stop: Then proceed at Restricting Speed.
AsApproach = 6;				-- Rule 285 					Indication: Proceed prepared to stop at next signal. Train exceeding Medium Speed must at once reduce to that speed.
AsMediumClear = 7;			-- Rule 283 					Indication: Proceed: Medium Speed within interlocking limits.
AsMediumApproach = 8;		-- Rule 286 					Indication: Proceed at Medium Speed preparing to stop at next signal.
AsSlowClear = 9;			-- Rule 287 		Indication: Proceed: Slow Speed within interlocking limits.
AsSlowApproach = 10;		-- Rule 288 		Indication: Proceed preparing to stop at next signal: Slow Speed within interlocking limits.
AsApproachMedium = 11;		-- Rule 282 		Indication: Proceed approaching next signal at Medium Speed.
AsApproachSlow = 12;		-- Rule 284 		Indication: Proceed approaching next signal at Slow Speed. Train exceeding Medium Speed must at once reduce to that speed.
AsRestricting = 13;			-- Rule 290 		Indication: Proceed at Restricted Speed.
AsApproachLimited = 14;		-- Rule 281 (B) 	Indication: Proceed approaching next signal at Limited Speed.
AsLimitedClear = 15;		-- Rule 281 (C) 	Indication: Proceed: Limited Speed within interlocking limits.
AsLimitedApproach = 16;		-- Rule 281 (D) 	Indication: Proceed at Limited Speed preparing to stop at next signal.
AsMediumApproachSlow = 17;	-- Rule 283 (B) 	Indication: Proceed at Medium Speed approaching next signal at slow speed.
AsTest = 18;				-- test by turning on all LED
AsLast = 19;				-- last valid Aspect + 1

--[[-----------------------Signal Head Types (Number and kind of Lights)------------------------------------]]
HdMLight = 4	-- one head with three lights (Green - Yellow - Red)
Hd3Light = 3	-- one head with three tri-color lights
Hd2Light = 2	-- one head with two tri-color lights
Hd1Light = 1	-- one head with one tri-color light
HdNone = 0

--[[-----------------------Signal Types------------------------------------]]
UNDEFINED_SIGNAL_TYPE = 0;
PERMISSIVE_IN_OPEN_COUNTRY = 1;
ABSOLUTE_HEAD_BLOCK = 2;
PERMISSIVE_HEAD_BLOCK = 3;
APPROACH_TO_HEAD_BLOCK = 4;

--[[-----------------------Traffic Stick Directions------------------------------------]]
NDT = 0;		-- No direction-of-traffic
WEST = 1;
EAST = 2;
--[[---------------------------------------------------------------------------------------]]
--[[
	Copyright (c) 2016 Scott Lembcke and Howling Moon Software
	
	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:
	
	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.
	
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
	
	TODO:
	* Print short function arguments as part of stack location.
]]
function Pretty(obj, max_depth)
	if max_depth == nil then max_depth = 10 end
	
	-- Returns true if a table has a __tostring metamethod.
	local function coerceable(tbl)
		local meta = getmetatable(tbl)
		return (meta and meta.__tostring)
	end
	
	local function recurse(obj, depth)
		if type(obj) == "string" then
			-- Dump the string so that escape sequences are printed.
			return string.format("%q", obj)
		elseif type(obj) == "table" and depth < max_depth and not coerceable(obj) then
			local str = "{"
			
			for k, v in pairs(obj) do
				local pair = MyPretty(k, 0).." = "..recurse(v, depth + 1)
				str = str..(str == "{" and pair or ",\n "..pair)
			end
			
			return str.."}"
		else
			-- tostring() can fail if there is an error in a __tostring metamethod.
			local success, value = pcall(function() return tostring(obj) end)
			return (success and value or "<!!error in __tostring metamethod!!>")
		end
	end
	
	return recurse(obj, 0)
end
--[[---------------------------------------------------------------------------------------]]
PrettyPrint = function(value, depth) print(Pretty(value, depth)) end
--[[---------------------------------------------------------------------------------------]]
