#include <stdio.h>
#include <unistd.h>

#include "../hubolib.h"

using namespace HuboLib;

/*
Compile and link: 
	g++ SampleCallbacks.cpp -L../ -lhubo -lpthread -lrt -o SampleCallbacks.out
Run:
	sudo ./SampleCallbacks.out
Purpose:
	Implementing a proper working controller algorithm requires to gather data equidistant 
	in time. 
	The cycle time already defines a time slice that will be used for gathering data. 
	Use Register_CycleTickCallback() to register a notification callback which is executed 
	at the end of each cycle.
	Register_ChannelDelayCallback() can be used to get notifications when a cycle time is
	violated by more then the length of one cycle.
	Be aware of the fact that these callbacks are executed in the background thread and 
	therefore require synchronisation to any other thread of your own (including the main
	thread)! Also note that the callback should not do lengthly operations as this would 
	cause it to delay its operation and the next cycle time might get dismissed!
*/

void ChannelDelayCallback 		  (__int64 cycleCount, __int64 t_Overrun_ms);
void CycleTickCallback    		  (unsigned long* pADChannelValues, unsigned char* pDigitalInputValues, unsigned char* pDigitalOutputValues);
void BreakingCycleTickCallback    (unsigned long* pADChannelValues, unsigned char* pDigitalInputValues, unsigned char* pDigitalOutputValues);

int main(void)
{
	// Initialize the library once in your program.
	if (!Initialize())
	{
		printf ("Error: Initialize\n");
		return 1;
	}
	
	// Define channel 0 to be oversampled 40 times (set to 40) all other channels (set to 0) are not read from the ADC.
	unsigned short overSampling[MAX_MCP3x08_CHANNELS] = { 1, 0, 0, 0, 0, 0, 0, 0 };
	Set_MCP3x08_Oversampling (overSampling);

	// Let's sample every 250ms (4Hz).
	Set_Cycle_Time (250);

	// Register the 2 callbacks.
	Register_CycleTickCallback (CycleTickCallback);
	Register_ChannelDelayCallback(ChannelDelayCallback);
	
	int 			channel = 0;
	unsigned long 	adcCount; 
	double	 		volt;
	for (int i=0; i<10; i++)
	{
		Get_AI_Channel (channel, adcCount, volt);
		printf ("Main thread: Channel=%d   ADC count=0x%02lX   Volts=%lf\n\n", channel, adcCount, volt);
		
		// Wait a second.
		usleep(1000000);
	}
	// Result:
	// You'll see a frequent calling of CycleTickCallback at a rate of 4Hz. Each second (1Hz) the main thread
	// will also output the value. Since the cycle time will not be broken there are no calls to ChannelDelayCallback()
	// for the time being.
	
	printf ("Now breaking the cycle time.\n");
	
	// The easiest way to break the cycle time of the background thread is to have it sleep for a time which
	// lasts longer than the cycle time is defined. Hence we'll let it sleep for 300ms.
	// Note that due to the higher thread priority of the background thread you'll not be able break the cycle
	// time from within the main thread or any other thread that runs at a lower priority. 
	
	// Unregister the current cycle tick callback and reregister a new one that will break the cycle time.
	Unregister_CycleTickCallback (CycleTickCallback);
	Register_CycleTickCallback (BreakingCycleTickCallback);
	
	for (int i=0; i<10; i++)
	{
		Get_AI_Channel (channel, adcCount, volt); // Comment out as described below.
		printf ("Main thread: Channel=%d   ADC count=0x%02lX   Volts=%lf\n\n", channel, adcCount, volt);
		
		// Wait a second.
		usleep(1000000);
	}
	
	// Result:
	// On top of the output above you'll see calls made to ChannelDelayCallback with an increasing delay.
	// One more thing that is not that straight forward. Even though the for-loop seems to end - it will not!
	// Since the background thread runs at high priority and is rescheduled soon after it left its usleep
	// it will prevent the mainthread from getting rescheduled. Strange thing - as the background thread runs 
	// into a usleep you might think. That's true. But the main thread tries to call Get_AI_Channel()
	// which internally runs against a mutex to sync the buffered sample data between the background thread 
	// and the threads calling into the library (in this case the main thread). Since the background thread
	// still owns the mutex while calling into the callback - the library is blocked for "client" calls.
	
	// Conclusion: 
	// Do not perform any time intensive functions within the callback functions. 
	// During the execution of the callback functions any other threads are blocked to use the library.
	// Also do not modify the data passed into the callbacks. A modification would directly impact the 
	// internal buffers. Hence - never keep a copy of the pointers in order to pass it around!
	// A good style would be perform a copy of the data and use it the same way as you'd be doing in any other 
	// multi threaded application (use mutex access for accessing your copies).

	// Comment out the Get_AI_Channel() call above and the application would nearly exit! The reason it still 
	// doesn't ist because the library is trying to catch up with the cycle tick calls but cannot as with
	// every new call it's loosing even more time.
	// Alternativley have the background thread get preempted (e.g. by a call to usleep() in order
	// to let an application continue its flow of control (e.g. terminate).
	
	// Once not needed anymore the callbacks can be unregistered.
	Unregister_CycleTickCallback (BreakingCycleTickCallback);
	Unregister_ChannelDelayCallback(ChannelDelayCallback);
	
	// Free library resources.
	Uninitialize();
	
	return 0;
}

void CycleTickCallback (unsigned long* pADChannelValues, unsigned char* pDigitalInputValues, unsigned char* pDigitalOutputValues)
{
	// Avoid lengthly operations such as printf's etc. in reality. This is just for demonstration!
	
	printf ("CycleTickCallback called.\n");
	if (pADChannelValues)
	{
		for (int i=0; i<8; i++)
			printf("0x%04lX ", pADChannelValues[i]);
		printf("\n");
	}

	if (pDigitalInputValues)
		printf ("Digital input  0x%02X\n", *pDigitalInputValues);

	if (pDigitalOutputValues)
		printf ("Digital output 0x%02X\n", *pDigitalOutputValues);
	
	printf ("\n");
}

void BreakingCycleTickCallback (unsigned long* pADChannelValues, unsigned char* pDigitalInputValues, unsigned char* pDigitalOutputValues)
{
	printf ("BreakingCycleTickCallback called.\n");
	CycleTickCallback(pADChannelValues, pDigitalInputValues, pDigitalOutputValues);
	
	// Sleeping for 300ms will break the cycle time (250ms) for 50ms. 
	usleep (300 * 1000);
}

void ChannelDelayCallback (__int64 cycleCount, __int64 t_Overrun_ms)
{
	// Avoid lengthly operations such as printf's etc. in reality. This is just for demonstration!
	
	printf ("ChannelDelayCallback called\n");
	printf ("delay: count = %lld  time[ms] = %lld\n\n", cycleCount, t_Overrun_ms);
	
	// Preepmting the background thread while it is late already will further increase its delay.
	// However, this might be a last resort to get an application to exit while the cycle time 
	// is permanently broken. In such a case the design of the software should be considered to 
	// get changed.
	// Uncomment the line below and the application will exit.
	// usleep (100 * 1000);
}



