// ===========================================================================
//                        ____ _____  ____  __    __ 
//                       |    / ___/ /    ||  |__|  |
//                        |  (   \_ |  o  ||  |  |  |
//                        |  |\__  ||     ||  |  |  |
//                        |  |/  \ ||  _  ||  `  '  |
//                        |  |\    ||  |  | \      / 
//                       |____|\___||__|__|  \_/\_/  
//
//                             LIVELOGGER SAMPLE
//
//                            2025-03-17 - V1.01
//
// ===========================================================================
//
// DESCRIPTION
//
//  Here is a sample code to show how to logger live data from ISAW sensors 
//  from serial port. This sample do following operations :
//		* Open the serial port
//		* Wakeup the sensor
//		* Get the sensor type
//		* Get the sensor serial number
//		* Wait measurement from sensor
//		* Write measurement data to a CSV file
//
// HOW TO BUILD
//
//  Use GCC (MINGW32 on Windows) to build this source code
//   gcc livelogger.c -o livelogger
//
// USAGE
//
//   livelogger.exe <port> [filename]
//
//   port     : Serial port (ex: COM5 or /dev/stty4)
//   filename : output filename (Live empty to use default filename ISAW_<sensor>_<serialnum>_<YYYYMMDD_hhmmss>.csv)
//
// NOTES
//   
//  * On linux you must need to add your user into dialout group if you dont want to use sudo
//    	sudo adduser $USER dialout
//	* For CSV header, take look into User guide, Appendix D: Serial communication, Serial measurement frame
//	* File is open and closed at each measurement.
//	* The PC datetime is added in front of each csv line.
//	* Excel lock csv file, livelogger will not be able to open it, this will lost measurement.
//	* If you start sensor in scopemode before, please reboot the sensor.
//	* If you enable debug, please disable debug.
// 
// ===========================================================================

// Includes
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <stdarg.h>

// ===========================================================================
//                                  DEFINES
// ===========================================================================

// Debug (uncomment to show debug message)
//#define SAMPLE_DEBUG

// Usefull defines
#define SAMPLE_TITLE					"LIVELOGGER SAMPLE"
#define SAMPLE_VERSION					"1.01"

// OS detection
#if defined(WIN64)
	#define SAMPLE_OS_WINDOWS
	#define SAMPLE_OS					"WIN64"
	#include <windows.h>
#elif defined(WIN32)
	#define SAMPLE_OS_WINDOWS
	#define SAMPLE_OS					"WIN32"
	#include <windows.h>
#elif defined(__linux__)
	#define SAMPLE_OS_LINUX
	#define SAMPLE_OS					"LINUX"
	#include <fcntl.h>
	#include <errno.h>
	#include <termios.h>
	#include <unistd.h>	
#else
	#error Unsupported operating system !
#endif

// Buffer configuration
#define MAX_PATH_SIZE					256
#define MAX_STR_SIZE					512
#define MAX_CMD_SIZE					1024

// Error codes
#define RET_OK							0
#define RET_ERR_HW						-1
#define RET_ERR_FMT						-2
#define RET_ERR_CMD						-3
#define RET_ERR_DL						-4
#define RET_ERR_IO						-5
#define RET_IS_ERROR(_err)				((_err) < RET_OK)

// Serial
#define SERIAL_BAUDERATE				115200
#define SERIAL_WAKEUP_TIMEOUT			1000
#define SERIAL_COMMON_TIMEOUT			5000
#define SERIAL_READ_BUFFER_SIZE			4096
#define SERIAL_WRITE_BUFFER_SIZE		4096

// Stat
#define STAT_EVERY_MS					2000

// Debug utility
#ifdef SAMPLE_DEBUG
	#define DEBUG_MSG(_fmt, ...)						printf("DEBUG : " _fmt "\n", ##__VA_ARGS__)
	#define DEBUG_BUFFER(_buff, _bsize, _fmt, ...)		do{											\
															printf("DEBUG : " _fmt, ##__VA_ARGS__);	\
															print_buffer(_buff, _bsize);			\
															printf(" (%u bytes)\n", _bsize);		\
														}while(0)															
#else
	#define DEBUG_MSG(_fmt, ...)
	#define DEBUG_BUFFER(_buff, _bsize, _fmt, ...)
#endif

// ===========================================================================
//                                  GLOBALS
// ===========================================================================

int is_running = 0;

// ===========================================================================
//                                  PROTOTYPES
// ===========================================================================

// Helpers
int error(int err, const char* fmt, ... );
void die(const char* fmt, ... );
void signal_callback(int sig);
void print_buffer(char *buffer, unsigned int bsize);
void trim_wspace(char *str);

// Sensors
int sensor_wakeup();
int sensor_cmd(const char *cmd, char *resp, unsigned int rsize);

// OS specifics function (HAL)
int serial_open(const char *port);
int serial_close();
int serial_write(unsigned char *buffer, unsigned int bsize);
int serial_read(unsigned char *buffer, unsigned int bsize);
int serial_set_timeout(unsigned int ms);
int serial_purge();
unsigned long get_tick_count();

// ===========================================================================
//                                MAIN ENTRY POINT
// ===========================================================================

int main(int argc, char **argv)
{
	int ret;
	char port[MAX_PATH_SIZE];
	char filename[MAX_PATH_SIZE];	
	time_t cts;
	struct tm *ctm;
	char buffer[MAX_CMD_SIZE];
	char sensor[MAX_STR_SIZE];
	char serialnum[MAX_STR_SIZE];
	char timestamp[32];
	FILE *fd;
		
	// Prompt
	printf("%s V%s (%s)\n", SAMPLE_TITLE, SAMPLE_VERSION, SAMPLE_OS);
	// Show usage if call without args
	if ((argc < 2) || (argc > 3)){
		printf("usage: %s <port> [filename]\n", argv[0]);
		return 0;
	}
	// Get serial port
	strcpy(port, argv[1]);
	
	// Handle app signal	
	signal(SIGINT, signal_callback);
	signal(SIGTERM, signal_callback);
#ifdef SAMPLE_OS_LINUX
	signal(SIGHUP, signal_callback);
#endif	
	// Opening serial port
	if (RET_IS_ERROR(serial_open(port))) die("Could not connect sensor !");
		
	// Waking up sensor
	printf("Waking up sensor...\n");
	if (RET_IS_ERROR(sensor_wakeup())) die("Could not wakeup sensor !");
	
	// Getting sensor info
	printf("Getting sensor type...\n");
	if (RET_IS_ERROR(sensor_cmd("get sens-type", sensor, MAX_STR_SIZE))) die("Could not get sensor type !");	
	
	printf("Getting sensor serial number...\n");
	if (RET_IS_ERROR(sensor_cmd("get sens-sn", serialnum, MAX_STR_SIZE))) die("Could not get sensor serialnum !");	
	trim_wspace(serialnum);

	// Get or create filename
	if (argc == 3){		
		strcpy(filename, argv[2]);
	}else{
		cts = time(NULL);
		ctm = localtime(&cts);
		strftime(timestamp, 32, "%Y%m%d_%H%M%S", ctm);
		snprintf(filename, MAX_PATH_SIZE, "ISAW_%s_%s_%s.csv", sensor, serialnum, timestamp);
	}	
		
	// Show information
	printf("Logging data from %s %s to '%s'...\n", sensor, serialnum, filename);
	printf("(Use CTRL+C to abort)\n");
		
	// Init loop
	is_running = 1;
	while(is_running){
		
		// Read data
		if (RET_IS_ERROR(ret = serial_read(buffer, MAX_CMD_SIZE))) break;
		if (!ret) continue;
		
		// Opening file
		DEBUG_MSG("Opening file '%s'...", filename);
		fd = fopen(filename, "at");
		if (!fd){
			error(RET_ERR_IO, "Could not open file '%s' !", filename);
			continue;
		}
		
		// Get timestamp
		cts = time(NULL);
		ctm = localtime(&cts);
		strftime(timestamp, 32, "%Y-%m-%d %H:%M:%S", ctm);
				
		// Log to screen
		printf("%s;%s\n", timestamp, buffer);
				
		// Write to file
		fprintf(fd, "%s;%s\n", timestamp, buffer);

		// Closing file
		DEBUG_MSG("Closign file...");	
		fclose(fd);
	
	}	
	
	printf("Done !\n");
	
	// Closing serial port
	serial_close();
		
	return 0;
}

// ===========================================================================
//                                  TOOLS
// ===========================================================================

void die(const char* fmt, ... )
{
	va_list args;
	printf("ERROR : ");
    va_start(args, fmt);	
	vprintf(fmt, args);
	va_end(args);	
	printf("\n");
	serial_close();	
	exit(1);
}

int error(int err, const char* fmt, ... )
{	
	va_list args;
	printf("ERROR : ");
    va_start(args,fmt);
	vprintf(fmt, args);
	va_end(args);	
	printf("\n");	
	return err;
}

void signal_callback(int sig)
{
    switch(sig) {
		case SIGINT:
			DEBUG_MSG("Interrupt signal (SIGINT) !");
			if(is_running == 0) exit(0);
			is_running = 0;
			break;
		case SIGTERM:
			DEBUG_MSG("Terminate signal (SIGTERM) !");
			exit(0);
			break;
#ifdef XTAC_PLATFORM_LINUX
		case SIGHUP:
			DEBUG_MSG("Hangup signal (SIGHUP) !");
			break;
#endif
	}
}

void print_buffer(char *buffer, unsigned int bsize)
{
	int i;
	char *ptr;
	for(i = 0, ptr = buffer; i < bsize; i++, ptr++){
		printf("0x%02X ", *ptr);
	}
}

void trim_wspace(char *str)
{
	int i;
	char *ptr;
	int n = strlen(str);
	for(i = 0, ptr = str; i < n; i++, ptr++){
		if (*ptr == ' '){
			*ptr = 0;
			break;
		}
	}
}

// ===========================================================================
//                              SENSOR UTILITIES
// ===========================================================================

int sensor_wakeup()
{
	int ret;
	char buffer[MAX_CMD_SIZE];
	int i;	
	DEBUG_MSG("Waking up sensor...");
	serial_set_timeout(SERIAL_WAKEUP_TIMEOUT);	
	for(i = 0; i < 5; i++){		
		ret = sensor_cmd("get sens-type", buffer, MAX_CMD_SIZE);
		if (ret == RET_OK) break;
	}	
	serial_set_timeout(SERIAL_COMMON_TIMEOUT);	
	serial_purge();	
	return ret;
}
	
int sensor_cmd(const char *cmd, char *resp, unsigned int rsize)
{
	int ret;
	char buffer[MAX_CMD_SIZE];
	char *ptr;
	int respsz, cmdsz = strlen(cmd);
	resp[0] = '\0';
	// Send command	
	DEBUG_MSG("Sensor command '%s'...", cmd);
	sprintf(buffer, "%s%c%c", cmd, 13);		
	if (RET_IS_ERROR(ret = serial_write(buffer, cmdsz+1))) return ret;
	// Read echo
	if (RET_IS_ERROR(ret = serial_read(buffer, MAX_CMD_SIZE))) return ret;
	if (strncmp(cmd, buffer, cmdsz)) return error(RET_ERR_FMT, "Bad command echo '%s' !", buffer);
	// Read response
	if (RET_IS_ERROR(ret = serial_read(buffer, MAX_CMD_SIZE))) return ret;
	DEBUG_MSG("Sensor response '%s'...", buffer);
	respsz = strlen(buffer);
	if (respsz < 2) return error(RET_ERR_FMT, "Bad command response format '%s' !", buffer);
	// Check response code 'OK='
	if (strncmp(buffer, "OK", 2)) return error(RET_ERR_CMD, "Sensor response error '%s' !", buffer);
	// Split response
	ptr = strchr(buffer, '=');
	if (ptr){
		ptr++;
		strncpy(resp, ptr, rsize - (ptr-buffer) );
	}
	return RET_OK;	
}

// ===========================================================================
//                        SERIAL OS SPECIFICS FUNCTIONS (HAL)
// ===========================================================================

#if defined(SAMPLE_OS_LINUX)

// OS Specifics globals
int hcom = -1;

int serial_open(const char *port)
{
	struct termios tty;
	// Opening serial port
	DEBUG_MSG("Opening serial port '%s'...", port);
	hcom = open(port, O_RDWR);
	if (hcom < 0) return error(RET_ERR_HW, "Could not open serial port '%s' : %s !", port, strerror(errno));
	// Getting serial port configuration
	DEBUG_MSG("Getting serial port configuration...");
	if(tcgetattr(hcom, &tty) != 0) error(RET_ERR_HW, "Could not get serial port configuration : %s !", strerror(errno));
	// Modify serial port configuration
	DEBUG_MSG("Setting serial port configuration...");
	// Control
	tty.c_cflag &= ~PARENB;		// No parity
	tty.c_cflag &= ~CSTOPB;		// One stop bit
	tty.c_cflag &= ~CSIZE;
	tty.c_cflag |= CS8; 		// 8 bits
	tty.c_cflag &= ~CRTSCTS; 	// Disable RTS/CTS hardware
	tty.c_cflag |= CREAD | CLOCAL; 	// Allow read
	// Local
	tty.c_lflag &= ~ECHO; 		// Disable echo
	tty.c_lflag &= ~ISIG; 		// Disable signal char
	// Input
	tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off xflags
	tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable special char
	// Output
	tty.c_oflag &= ~OPOST; 		// Disable special char
	tty.c_oflag &= ~ONLCR; 		// Disable convertion
	// Timemout 
	tty.c_cc[VTIME] = 50;    	// Wait for up to 5s (50 deciseconds)
	tty.c_cc[VMIN]  = 0;
	// Baudrate
	cfsetispeed(&tty, B115200);
	cfsetospeed(&tty, B115200);
	// Setting serial port configuration
	if (tcsetattr(hcom, TCSANOW, &tty) != 0) error(RET_ERR_HW, "Could not set serial port configuration : %s !", strerror(errno));
	return RET_OK;
}

int serial_close()
{
	if (hcom > -1){
		DEBUG_MSG("Closing serial port...");
		close(hcom);
		hcom = -1;
	}
	return RET_OK;
}

int serial_write(unsigned char *buffer, unsigned int bsize)
{
	DEBUG_BUFFER(buffer, bsize, "TX> ");
	if (write(hcom, buffer, bsize) < 0) error(RET_ERR_HW, "Could not write to serial port : %s !", strerror(errno));
	return RET_OK;
}

int serial_read(unsigned char *buffer, unsigned int bsize)
{
	unsigned int nread, n;
	unsigned char *ptr;
	for(nread = 0, ptr = buffer; nread < bsize;){
		n = read(hcom, ptr, 1);
		if (n < 0) return error(RET_ERR_HW, "Could not write to serial port : %s !", strerror(errno));
		if ((!n) || (*ptr == 10)) break;
		if (*ptr == 13) continue;
		nread++;
		ptr++;
	}
	buffer[nread] = 0;
	DEBUG_BUFFER(buffer, nread, "RX> ");
	return (int)nread;
}

int serial_set_timeout(unsigned int ms)
{
	struct termios tty;
	DEBUG_MSG("Setting serial port timeout to %u ms...", ms);
	if(tcgetattr(hcom, &tty) != 0) error(RET_ERR_HW, "Could not get serial port configuration : %s !", strerror(errno));
	tty.c_cc[VTIME] = ms/100;
	if (tcsetattr(hcom, TCSANOW, &tty) != 0) error(RET_ERR_HW, "Could not set serial port configuration : %s !", strerror(errno));
	return RET_OK;
}

int serial_purge()
{
	DEBUG_MSG("Purge serial port...");
	tcflush(hcom, TCIOFLUSH);
	return RET_OK;
}

unsigned long get_tick_count()
{
	struct timespec ts;
	unsigned long tick = 0U;
	clock_gettime( CLOCK_REALTIME, &ts );
	tick  = ts.tv_nsec / 1000000;
	tick += ts.tv_sec * 1000;
	return tick;
}

#elif defined(SAMPLE_OS_WINDOWS)

// OS Specifics globals
HANDLE	hcom = NULL;	// Serial port handle

int serial_open(const char *port)
{
	DCB dcb;
	char path[MAX_PATH_SIZE];
	// Create serial device path
	sprintf(path, "\\\\.\\%s", port);
	// Open serial port
	DEBUG_MSG("Opening serial port '%s'...", port);
	hcom = CreateFile(path, GENERIC_READ|GENERIC_WRITE , 0, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL);
	if (hcom == INVALID_HANDLE_VALUE) return error(RET_ERR_HW, "Could not open serial port '%s' !", port);
	// Set buffer size
	DEBUG_MSG("Setting serial port buffer size...");
	SetupComm(hcom, SERIAL_READ_BUFFER_SIZE, SERIAL_WRITE_BUFFER_SIZE);
	// Configure serial communication
	memset(&dcb,0x0,sizeof(DCB));
	dcb.DCBlength	= sizeof(DCB);
	dcb.fBinary		= TRUE;
	dcb.fDtrControl	= DTR_CONTROL_DISABLE;
	dcb.fRtsControl	= RTS_CONTROL_DISABLE;
	dcb.ByteSize	= 8;
	dcb.BaudRate	= SERIAL_BAUDERATE;
	dcb.Parity		= 0;
	dcb.StopBits	= ONESTOPBIT;
	DEBUG_MSG("Setting serial port parameters...");
	if (!SetCommState(hcom, &dcb)) return error(RET_ERR_HW, "Could not set serial configuration !");	
	// Set timeout
	serial_set_timeout(SERIAL_COMMON_TIMEOUT);
	// Purge
    serial_purge();
	return RET_OK;
}

int serial_close()
{
	
	if (hcom){
		DEBUG_MSG("Closing serial port...");
		CloseHandle(hcom);
	}
	hcom = NULL;
	return RET_OK;
}

int serial_write(unsigned char *buffer, unsigned int bsize)
{
	DWORD nwrite = 0;
    PurgeComm(hcom, PURGE_TXCLEAR | PURGE_TXABORT);
	DEBUG_BUFFER(buffer, bsize, "TX> ");
	if (!WriteFile(hcom, buffer, bsize, &nwrite, NULL)) return error(RET_ERR_HW, "Could not write to serial port !");	
	if (bsize != nwrite) return error(RET_ERR_HW, "Could not fully write to serial port !");
	return RET_OK;
}

int serial_read(unsigned char *buffer, unsigned int bsize)
{
	DWORD nread, n;
	unsigned char *ptr;
	for(nread = 0, ptr = buffer; nread < bsize;){
		if (!ReadFile(hcom, ptr, 1, (DWORD*)&n, NULL)) return error(RET_ERR_HW, "Could not read from serial port !");	
        if ((!n) || (*ptr == 10)) break;
        if (*ptr == 13) continue;
		nread++;
		ptr++;
	}
	buffer[nread] = 0;
	DEBUG_BUFFER(buffer, nread, "RX> ");
	return (int)nread;
}

int serial_set_timeout(unsigned int ms)
{
	COMMTIMEOUTS ct;
	DEBUG_MSG("Setting serial port timeout to %u ms...", ms);
	memset(&ct,0x0,sizeof(COMMTIMEOUTS));
	ct.ReadIntervalTimeout			= ms;
	ct.ReadTotalTimeoutConstant		= ms;
	ct.WriteTotalTimeoutConstant	= ms;
	if(!SetCommTimeouts(hcom, &ct)) return error(RET_ERR_HW, "Could not set serial port timeout !");
	return RET_OK;
}

int serial_purge()
{
	DEBUG_MSG("Purge serial port...");
	PurgeComm(hcom, PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_RXABORT);
	return RET_OK;
}

unsigned long get_tick_count()
{
	return (unsigned long)GetTickCount();
}

#endif