/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		TSL_Tally_Set.DLL

USED BY			BBC Academy Wood Norton BNCS installation for Studio C

VERSION			1.0
DATE			28th July 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This module implements a component intended for use on an automatic panel. The function of the
component is to enable GPI contact states or Infodriver slot states to send TSL tally states
to the Studio C multiviewer where the tally is used as the alarm source for various displays
such as house light status, firelane light status, REH. The component is used in an automatic
panel enabling quick addition of sensing elements. 

The component is controlled by several properties set at design time. These are listed below.

Design Time Parameters
======================
Once the component has been selected as the target for a script component on the panel there are
several parameters that need to be set for the component to operate as the monitoring setter.

instance	The named entry in instances.xml that defines the GPI or infodriver slot to monitor.
			The component detects the type of driver to monitor from an extra element added to
			the instance defintion. The extra element is devtype="g" for GPI monitoring or 
			devtype="i" for infodriver slot monitoring. If the devtype is not present, a message
			is sent to debug and infodriver monitoring is assumed The device driver number and 
			contact/slot number are shown in the 3rd row of mimic displays.

monitoring	Entered via the visual editor. Place a name here that reflects the function of the
			component function - for example "Houselight Status Monitor". The name is shown in
			the label slot at the top of the component GUI. The label is set for smart text, so
			long names display in small letters.

setslot		The name of the entry in instances.xml that holds the infodriver ID and TSL address
			whose tally is set when the monitored data changes. The component adds the offset
			provided by design time parameter "slotoffset"

slotoffset	This value is added to the TSL address to form the infodriver slot number that is 
			set to the tally_val. Typically this is 1000 for the TSL driver tally control slots

tally_on	The value which will cause the component to set the tally value to "on". For a GPI 
			contact the only values should be 0 or 1. For an infodriver slot the value is the 
			text that should be in that slot to trigger the tally on state.

tally_val	The value to be set in the TSL address slot when the tally on condition is met. The
			value 0 is set when the condition for tally on is not active. Values 1,2,3 are
			possible values for TSL protocol.

General
=======
There may be a short delay (0.5 seconds) after entering values into the design time form before
the TSL data stream is updated. The delay happens as part of the testing for all needed data
before a revertive registration and poll occurs.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

#include <windows.h>
#include <stdio.h>
#include <bncs_string.h>
#include <bncs_config.h>
#include "TSL_Tally_Set.h"

#define MAIN_PNL	1
#define MAIN_PNL_NAME	"tsltally.bncs_ui"

#define TIMER_SETUP	1
#define TIMER_SETUP_TICK	500

////////////////////////////////////////////////////////
// Macro to make our class visible to the outside world.
EXPORT_BNCS_SCRIPT(TSL_Tally_Set)


////////////////////////////////////////////////
// Constructor - equivalent to ApplCore STARTUP.
TSL_Tally_Set::TSL_Tally_Set(bncs_client_callback * parent, const char * path) : bncs_script_helper(parent, path)
{
	// Set default "safe" values into module variables
	myContact = 0;			// gpi contact or infodriver slot to monitor
	myDriverMon = 0;		// driver id of myContact
	mySetSlot = 0;			// slot number of TSL tally infodriver to set
	mySetDriver = 0;		// driver ID of TSL tally infodriver
	mySlotOffset = 0;		// Offset to add to setSlot
	gpiTallyTest = 0;		// value of gpi for active state
	isGPI = false;			// set true if myDriverMon is a GPI driver

	m_instance = "";		// m_instance gpi contact/info slot to monitor
	m_monitoring = "";		// m_monitoring is label to show in component GUI
	m_setslot = "";			// m_setslot is entry in instances.xml that defines driver/slot_id to be set
	m_slotoffset = "";		// m_slotoffset is value added to slot_id to define slot to set in defined driver
	m_tally_on = "";		// m_tally_on is value to be match to cause active tally to be asserted
	m_tally_val = "";		// m_tally_val is value to set when tally condition is active

	// Show our component's panel
	panelShow(MAIN_PNL, MAIN_PNL_NAME);
}


/////////////////////////////////////////////////
// Destructor - equivalent to ApplCore CLOSEDOWN.
TSL_Tally_Set::~TSL_Tally_Set()
{
}


/////////////////////////////////////////////////
// All button pushes and notifications come here.
void TSL_Tally_Set::buttonCallback(buttonNotify *b)
{
	// No active buttons on this component
}


////////////////////////////
// All revertives come here.
int TSL_Tally_Set::revertiveCallback(revertiveNotify *r)
{
	// Check if the driver matches the one this component is monitoring
	if (r->device() == myDriverMon)
	{
		if (r->index() == myContact)
		{
			if (isGPI)
			{
				// Component is monitoring a GPI driver.
				if (r->info() == gpiTallyTest)
				{
					infoWrite(mySetDriver, m_tally_val, mySetSlot + mySlotOffset);
					textPut("text", m_tally_val, MAIN_PNL, "Tally");
				}
				else
				{
					infoWrite(mySetDriver, "0", mySetSlot + mySlotOffset);
					textPut("text", "0", MAIN_PNL, "Tally");
				}
			}
			else
			{
				// Component is monitoring an Infodriver.
				if (r->sInfo() == m_tally_on)
				{
					infoWrite(mySetDriver, m_tally_val, mySetSlot + mySlotOffset);
					textPut("text", m_tally_val, MAIN_PNL, "Tally");
				}
				else
				{
					infoWrite(mySetDriver, "0", mySetSlot + mySlotOffset);
					textPut("text", "0", MAIN_PNL, "Tally");
				}
			}
		}
	}

	
	return 0;
}


////////////////////////////////////////////
// All database name changes come back here.
void TSL_Tally_Set::databaseCallback(revertiveNotify *r)
{
	// No changes relevant to this component
}


/////////////////////////////////////////////////////////////////////////
// All parent notifications come here. This script component is just one 
// element of another dialog so our host might want to tell us things.
bncs_string TSL_Tally_Set::parentCallback( parentNotify *p )
{
	if( p->command() == "return" )
	{
		if( p->value() == "all" )
		{	
			// Asking for the persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			sl << bncs_string("monitoring=%1").arg(m_monitoring);
			sl << bncs_string("setslot=%1").arg(m_setslot);
			sl << bncs_string("slotoffset=%1").arg(m_slotoffset);
			sl << bncs_string("tally_on=%1").arg(m_tally_on);
			sl << bncs_string("tally_val=%1").arg(m_tally_val);
		
			return sl.toString('\n');
		}

	}


	// ++++++++++++++++++ //
	// Instance being set //
	// ++++++++++++++++++ //
	else if ((p->command() == "instance") && (p->value() != m_instance))
	{	
		// Our instance is being set/changed
		bool	inst_found = false;
		int		drv = 0;
		int		ctact = 0;
		bncs_string new_instance;
		bncs_string	driverType;

		// Unregister for any monitoring property that may already be active.
		if (myDriverMon != 0)
		{
			if (isGPI)
				gpiUnregister(myDriverMon);		// Previous driver was a gpi type
			else
				infoUnregister(myDriverMon);	// Only other option is infodriver

			myDriverMon = 0;
			isGPI = true;
			m_instance = "";
		}

		new_instance = p->value();

		// The instance data tells this component the driver number and gpi_contact/infodriver slot to monitor for status.
		// Also need the device type so that can correctly register for revertives and poll for initial state. An extra
		// property, drvtype=, is present in the instances entry. The value is "i" for an infodriver and "g" for a gpi.
		// Use the helper definitions for bncs_config to read the value.
		inst_found = getDev(new_instance, &drv, &ctact);	// Get the device and offset values
		if (inst_found == false)
		{
			// Instance name was not found. Do not set up persisting values
			debug(bncs_string("TSL_Tally_Set - failed to find instance %1\n").arg(p->value()));
			textPut("text=unknown", MAIN_PNL, "deviceType");
			textPut("text=0", MAIN_PNL, "SrcDev");
			textPut("text=0", MAIN_PNL, "SrcIndex");
			return "";
		}

		// We will trust the user to set valid driver number (1..999) and slot number
		// Look for the drvtype= value
		bncs_config cfg(bncs_string("instances.%1").arg(new_instance));
		driverType = cfg.attr("drvtype");

		if (!((driverType.lower() == "g") || (driverType.lower() == "i")))
		{
			debug(bncs_string("TSL_Tally_Set invalid driver type %1 - will assume Infodriver\n").arg(driverType));
		}
		isGPI = (driverType.lower() == "g") ? true : false;

		// Save persisting data and set up the mimic data
		myDriverMon = drv;
		myContact = ctact;
		m_instance = new_instance;
		textPut("text", isGPI ? "GPI" : "Info", MAIN_PNL, "DeviceType");
		textPut("text", bncs_string("%1").arg(myDriverMon), MAIN_PNL, "SrcDev");
		textPut("text", bncs_string("%1").arg(myContact), MAIN_PNL, "SrcIndex");

		// Register for revertives.
		if ((myDriverMon != 0) && (myContact != 0))
		{
			if (isGPI)
			{
				gpiRegister(myDriverMon, myContact, myContact);
			}
			else
			{
				infoRegister(myDriverMon, myContact, myContact);
			}
		}

		// Registration and initial polling should only start when have the driver and contact to monitor, the tally trigger compare value, 
		// the TSL driver address is known, the value to set when tally condition is active. We use a timer timeout to check for these 
		// conditions being true, then it activates the registration and initial poll.
		timerStart(TIMER_SETUP, TIMER_SETUP_TICK);
	}


	// +++++++++++++++++++++++++++++ //
	// Identification text being set //
	// +++++++++++++++++++++++++++++ //
	else if( p->command() == "monitoring" )
	{	
		// Display the text to show in label "Legend"
		m_monitoring = p->value();	// Save copy in our persisting variable
		textPut("text", m_monitoring, MAIN_PNL, "Legend");	// Display on mimic
	}


	// +++++++++++++++++++++++++++++++++++++++ //
	// Instance name for destination being set //
	// +++++++++++++++++++++++++++++++++++++++ //
	else if (p->command() == "setslot")
	{
		// 
		bool	inst_found = false;
		int		drv = 0;
		int		slot = 0;
		bncs_string new_instance = p->value();

		// Look up the instance data for destination
		inst_found = getDev(new_instance, &drv, &slot);
		if (inst_found == false)
		{
			// Instance name of destination was not found. Do not set up persisting values
			debug(bncs_string("TSL_Tally_Set - failed to find destination instance %1\n").arg(p->value()));
			textPut("text", "", MAIN_PNL, "Addr");
			return "";
		}
		
		m_setslot = new_instance;
		mySetSlot = slot;
		mySetDriver = drv;
		textPut("text", bncs_string("%1").arg(slot), MAIN_PNL, "Addr");
	}	// end of else if (p->command() == "setslot")


	// +++++++++++++++++++++++++++++++++ //
	// Destination slot offset being set //
	// +++++++++++++++++++++++++++++++++ //
	else if (p->command() == "slotoffset")
	{
		// The number to be added to the TSL_Address to get the tally control slot.
		// Typically 1000 for the BNCS TSL driver.
		m_slotoffset = p->value();		// Save for visual editor dialogues
		mySlotOffset = p->value().toInt();	// Save the value
		return "";
	}


	// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
	// Setting compare value that will cause tally value to be set "on". //
	// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
	else if (p->command() == "tally_on")
	{
		// The value that will cause the tally to be set active
		// is set by this parameter. For a GPI only 0 or 1 should be used, for an
		// infodriver enter the text that must be matched.
		m_tally_on = p->value();						// Text for infodriver compare
		gpiTallyTest = (p->value() == "0") ? 0 : 1;		// Number for GPI compare
		textPut("text", p->value(), MAIN_PNL, "AlarmTest");
		return "";
	}

	// ++++++++++++++++++++++++++++++ //
	// Value for "Tally=ON" being set //
	// ++++++++++++++++++++++++++++++ //
	else if (p->command() == "tally_val")
	{
		// The value to set in the tally slot when active is set here
		if ((p->value() == "1") || (p->value() == "2") || (p->value() == "3"))	// Limit the supported values
		{
			m_tally_val = p->value();		// Save it as a text strung
		}
		return "";
	}


	// ***** CONNECTIONS EVENTS HELPER LIST *****
	else if( p->command() == "_events" )
	{	
		// Helper-list of everything in this component generated by hostNotify's
		bncs_stringlist sl;
		sl << "";		
		return sl.toString( '\n' );
	}


	// ***** CONNECTIONS COMMANDS HELPER LIST *****
	else if( p->command() == "_commands" )
	{	
		// Helper-list of any commands/parameters you might want to set at run-time
		bncs_stringlist sl;
		sl << "";
		return sl.toString( '\n' );
	}

	return "";
}


//////////////////////////
// Timer events come here.
void TSL_Tally_Set::timerCallback( int id )
{
	switch( id )
	{
	case TIMER_SETUP:
		// When the instance that sets the device and slot to monitor is set, timer 1 is started. When the timer ticks the timer 
		// processing code checks to see if there is a driver and contact to monitor, there is a tally trigger compare value, 
		// the TSL driver address is known, and the value to set when tally condition is active. If the condition is met, 
		// revertive registration occurs and a poll request is issued. The time is then stopped.

		can_register = true;	// Assume we will be ok to register, failing a test will set it false.

		if ((myDriverMon == 0) || (myContact == 0)) can_register = false;
		if ((mySetDriver == 0) || (mySetSlot == 0)) can_register = false;
		if (m_tally_on == "") can_register = false;
		if (m_tally_val == "") can_register = false;

		if (can_register)
		{
			if (isGPI)
			{
				gpiRegister(myDriverMon, myContact, myContact);
				gpiPoll(myDriverMon, myContact, myContact);
			}
			else
			{
				infoRegister(myDriverMon, myContact, myContact);
				infoPoll(myDriverMon, myContact, myContact);
			}

			timerStop(id);

		}
		
		break;

	default:	// Unhandled timer event
		timerStop(id);
		break;
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Callbacks above - Methods below ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////


