// datataker.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "datataker.h"
#include <chrono>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// The one and only application object
DataTakerApp theApp;

//BEGIN_MESSAGE_MAP(DataTakerApp, CWinApp)
//	//{{AFX_MSG_MAP(DataTakerApp)
//	//}}AFX_MSG
//END_MESSAGE_MAP()

//Another flavour of an ODBC_CHECK_RETURN macro
#define ODBC_CHECK_RETURN3(nRet, handle) \
  handle.ValidateReturnValue(nRet); \
  if ((nRet != SQL_SUCCESS) && (nRet != SQL_SUCCESS_WITH_INFO)) \
  { \
    return nRet; \
  }

//update wrapper for tellerstand
class CdboTellerStandUpdateAccessor
{
public:

    DEFINE_ODBC_COMMAND( CdboTellerStandUpdateAccessor, 
                         _T("UPDATE %s SET %s = %s"))
		
	  //You may wish to call this function if you are inserting a record and wish to
	  //initialize all the fields, if you are not going to explicitly set all of them.
	  void ClearRecord()
	  {
		  memset(this, 0, sizeof(*this));
	  }	
};


//test class used for demonstrating updating a row in AdventureWorks2008.Production.ProductCategory table
class CdboTellerStandUpdate : public CODBC::CAccessor<CdboTellerStandUpdateAccessor>
{

  public:
    //Methods
	  SQLRETURN Update(CODBC::CConnection* pDbConnect, CString table, CString Column, CString ColVal)
    {

      //Validate our parameters
      ATLASSUME(pDbConnect);

      //Create the statement object
      CODBC::CStatement statement;
      SQLRETURN nRet = statement.Create(*pDbConnect);
      ODBC_CHECK_RETURN3(nRet, statement);

		  //Prepare the count statement
      SQLTCHAR *tmp = GetDefaultCommand();
      SQLTCHAR sql[4096];
      sprintf( (char *)&sql, (char *)tmp, table.GetBuffer(), Column.GetBuffer(), ColVal.GetBuffer());
		  nRet = statement.Prepare( sql);
      ODBC_CHECK_RETURN3(nRet, statement);

      //Bind the parameters 
      nRet = BindParameters(statement);
      ODBC_CHECK_RETURN3(nRet, statement);

      //Execute the statement
      nRet = statement.Execute();
      ODBC_CHECK_RETURN3(nRet, statement);
      return nRet;
    }

    //Update counter in table with one
    //NOTE: table is assumed to have only only one record, only in the first record a clounm is incremented
	  SQLRETURN AddOne(CODBC::CConnection* pDbConnect, CString table, CString Column, unsigned short *sCount)
    {

      //Validate our parameters
      ATLASSUME(pDbConnect);

      *sCount = 0;

      //Create the statement object
      CODBC::CStatement statement;
      SQLRETURN nRet = statement.Create(*pDbConnect);
      ODBC_CHECK_RETURN3(nRet, statement);

      //Prepare the select statement
      SQLTCHAR sql[4096];
      sprintf( (char *)&sql, "SELECT %s FROM %s", Column.GetBuffer(), table.GetBuffer());
		  nRet = statement.Prepare( sql);
      ODBC_CHECK_RETURN3(nRet, statement);

      nRet = statement.Execute();
      ODBC_CHECK_RETURN3(nRet, statement);
      
      //Bind the columns
      nRet = BindColumns(statement);
      ODBC_CHECK_RETURN3(nRet, statement);


      //Only the first record is updated
      //ClearRecord();
      nRet = statement.FetchNext();
      ODBC_CHECK_RETURN3(nRet, statement);

      //get the counter vlaue
      short s1;
      nRet = statement.GetData( 1, SQL_C_SSHORT,  &s1, sizeof(s1), NULL);
      ODBC_CHECK_RETURN3(nRet, statement);

      //increment counter an dconvert to string
      char val[80];
      if (!(++s1)) s1++;
      *sCount = s1;
      sprintf( val, "'%d'", s1);

      //select statement finished, close statement
      statement.Close();
      nRet = statement.Create(*pDbConnect);
      ODBC_CHECK_RETURN3(nRet, statement);

		  //Prepare the update statement
      SQLTCHAR *tmp = GetDefaultCommand();
      sprintf( (char *)&sql, (char *)tmp, table.GetBuffer(), Column.GetBuffer(), val);
		  nRet = statement.Prepare( sql);
      ODBC_CHECK_RETURN3(nRet, statement);

      //Bind the parameters 
      nRet = BindParameters(statement);
      ODBC_CHECK_RETURN3(nRet, statement);

      //Execute the statement
      nRet = statement.Execute();
      ODBC_CHECK_RETURN3(nRet, statement);
      return nRet;
    }
};


//test ODBC accessor class used for demonstrating inserting a row into AdventureWorks2008.Production.ProductCategory table
class CdboBlokInfoInsertAccessor
{
public:
//Parameter values  
	TCHAR m_sColumn[10][51];
	
BEGIN_ODBC_PARAM_MAP(CdboBlokInfoInsertAccessor)
	SET_ODBC_PARAM_TYPE(SQL_PARAM_INPUT)
	ODBC_PARAM_ENTRY( 1, m_sColumn[1])
	ODBC_PARAM_ENTRY( 2, m_sColumn[2])
	ODBC_PARAM_ENTRY( 3, m_sColumn[3])
	ODBC_PARAM_ENTRY( 4, m_sColumn[4])
	ODBC_PARAM_ENTRY( 5, m_sColumn[5])
	ODBC_PARAM_ENTRY( 6, m_sColumn[6])
END_ODBC_PARAM_MAP()

DEFINE_ODBC_COMMAND(CdboBlokInfoInsertAccessor, _T("INSERT INTO [datataker].[dbo].[blokinfo] ([pers],[datum_tijd],[hoogte],[gewicht],[temp],[anodeteller]) VALUES (?,CONVERT(datetime, ?, 105),?,?,?,?)"))
		//CONVERT(datetime, ?, 105)
	//You may wish to call this function if you are inserting a record and wish to
	//initialize all the fields, if you are not going to explicitly set all of them.
	void ClearRecord()
	{
		memset(this, 0, sizeof(*this));
	}	
};


//test class used for demonstrating inserting a row into  AdventureWorks2008.Production.ProductCategory table
class CdboBlokInfoInsert : public CODBC::CAccessor<CdboBlokInfoInsertAccessor>
{
public:
//Methods
	SQLRETURN Insert(CODBC::CConnection* pDbConnect, DT_SLAVE *dtSlave)
  {
    //Validate our parameters
    ATLASSUME(pDbConnect);

    //Create the statement object
    CODBC::CStatement statement;
    SQLRETURN nRet = statement.Create(*pDbConnect);
    ODBC_CHECK_RETURN3(nRet, statement);

    //construct the sql statement



		//Prepare the statement
		nRet = statement.Prepare(GetDefaultCommand());
    ODBC_CHECK_RETURN3(nRet, statement);

    //Bind the parameters
//  #ifdef _tcscpy_s
//    _tcscpy_s(m_sProductCategory, sizeof(m_sProductCategory)/sizeof(TCHAR), _T("Unicycles"));
//  #else
//    _tcscpy(m_sProductCategory, _T("Unicycles"));
//  #endif

    _tcscpy(m_sColumn[1], _T(dtSlave->name));
    _tcscpy(m_sColumn[2], _T((const char *)dtSlave->date_time)); //CONVERT(datetime, inserted.[Tijd], 105)
    _stprintf(m_sColumn[3], "%d", dtSlave->height);
    _stprintf(m_sColumn[4], "%d", dtSlave->weight);
    _stprintf(m_sColumn[5], "%d", dtSlave->temperature);
    _stprintf(m_sColumn[6], "%d", dtSlave->anode);

    nRet = BindParameters(statement);
    ODBC_CHECK_RETURN3(nRet, statement);

    //Execute the statement
    nRet = statement.Execute();
    ODBC_CHECK_RETURN3(nRet, statement);
    return nRet;
  }
};







using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int nRetCode = 0;

	HMODULE hModule = ::GetModuleHandle(NULL);

	if (hModule != NULL)
	{
		// initialize MFC and print and error on failure
		if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0))
		{
			// TODO: change error code to suit your needs
			_tprintf(_T("Fatal Error: MFC initialization failed\n"));
			nRetCode = 1;
		}
		else
		{
			// TODO: code your application's behavior here.
      theApp.InitInstance();

      //get the name of the ini file for our application
      CString mIniFile = theApp.m_pszAppName;
     
      //create our serial port object
      SerialPort MyCOMport( mIniFile.GetBuffer());

      //Get the COM port settings from the ini file
      //MyCOMport.OpenPort();
      //CString testtx = "data naar comport";
      
      //MyCOMport.Transmit( testtx.GetLength(), (unsigned char *)testtx.GetBuffer());


      //CString Ctest = "3,21,08:11:28, 7.368, 1022.0, 1030.4, 1026.8, 1027.2, 9.896, 10.112, 1031.6, 1028.8, 10.136, 10.532, 1032.4, 318.0, 14.252, 14.344, 14.196, 14.412, 317.2, 14.660, 14.584, 319.6,-50.64, 322.4, 17.712, 16.316, 326.4, 17.136, 14.196, 13.968, 13.208, 17.252, 331.2, 322.8\4";

      //theApp.UseData( (unsigned char *)Ctest.GetBuffer(), Ctest.GetLength());

      //Devices on the COM port are polled, to grant exclusive use to the port by just one thread
      while (true)
      {
        theApp.PollDevices( &MyCOMport);
      }
		}
	}
	else
	{
		// TODO: change error code to suit your needs
		_tprintf(_T("Fatal Error: GetModuleHandle failed\n"));
		nRetCode = 1;
	}

	return nRetCode;
}





DataTakerApp::DataTakerApp()
{


}

BOOL DataTakerApp::InitInstance()
{

  CString sSlave;
  polltime = 100; //polltime in msec, default is 100 msec
  timepast = 0;
  //rx_len = 0;
  char *tmpbuf;


  CExecImageVersion MyVersion;
  //CIniFile MyINI("datataker.ini");

  //Settings will be stored in the registry
  //CString mijntest = MyVersion.GetLegalTrademarks();
  SetRegistryKey( MyVersion.GetLegalTrademarks());

  CString mIniFile = m_pszAppName;
  mIniFile.Append( ".ini");
  DtIniFile = new CIniFile( mIniFile.GetBuffer());

  //debug settings for the datataker application, global setting
  dbgview.SetLog2DbgViewer( DtIniFile->GetInteger( "Log2DbgViewer", "DATATAKER", 1));
  dbgview.SetLog2Screen( DtIniFile->GetInteger( "Log2Screen", "DATATAKER", 0));
  dbgview.SetLog2File( DtIniFile->GetInteger( "Log2File", "DATATAKER", 0));
  dbgview.SetLog2EventViewer( DtIniFile->GetInteger( "Log2EventViewer", "DATATAKER", 0));
  dbgview.SetTraceLevel( DtIniFile->GetInteger( "Tracelevel", "DATATAKER", 0));

  dbgview.CodeTrace( "DataTaker: InitInstance function called, ini file [%s] read", 
                      6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                      mIniFile.GetBuffer());


  //first assume all slave addresses are invalid
  memset( (void *)aDT_slave, 0, sizeof(aDT_slave));
  for( int i = 0; i < MAX_SLAVES; i++)
  {

    aDT_slave[ i].address = -1; 
    aDT_slave[ i].status = 0; 
  }

  //read all the slaves from the ini file, just check if there is an address available
  //for slaves in the address range 1 - 99.
  for( int i = 0; i < MAX_SLAVES; i++)
  {

    //keyword for the ini is slave followed by a number
    sSlave.Format( "SLAVE%d", i);

    //check if there is a slave
    int iSlave = DtIniFile->GetInteger( "ADDRESS", sSlave.GetBuffer(), -1);
    if ( ( iSlave > 0) && ( iSlave < MAX_SLAVES)) 
    {

      //set the slave number
      aDT_slave[ iSlave].address = iSlave;

      //fill in description, identical to column name in 'tellerstanden'
      strcpy( aDT_slave[ iSlave].name, DtIniFile->GetString( "NAME", sSlave.GetBuffer(), "???"));

      aDT_slave[ iSlave].date_time[ 0] = '\0';     /* date + time of m easurement */
      aDT_slave[ iSlave].weight = 0;               /* weight */
      aDT_slave[ iSlave].height = 0;               /* height */
      aDT_slave[ iSlave].temperature = 0;          /* temperature */
      aDT_slave[ iSlave].anode = 0;                /* anode id */

      //fill in the communication strings
      strcpy( aDT_slave[ iSlave].pInitString, DtIniFile->GetString( "InitString", sSlave.GetBuffer(), "/c/e/k/L/m/n/O/r/t/u P22=44 P24=4 P39=0"));
      strcpy( aDT_slave[ iSlave].pDateString, DtIniFile->GetString( "DateString", sSlave.GetBuffer(), "D=%d"));
      strcpy( aDT_slave[ iSlave].pTimeString, DtIniFile->GetString( "TimeString", sSlave.GetBuffer(), "T=%s"));
      strcpy( aDT_slave[ iSlave].pStartString, DtIniFile->GetString( "StartString", sSlave.GetBuffer(), "R1-E"));
      strcpy( aDT_slave[ iSlave].pReadString, DtIniFile->GetString( "ReadString", sSlave.GetBuffer(), "D T 1+..17+V"));
      strcpy( aDT_slave[ iSlave].pTermString, DtIniFile->GetString( "TermString", sSlave.GetBuffer(), "\0xd\0xa"));
      strcpy( aDT_slave[ iSlave].pClearString, DtIniFile->GetString( "ClearString", sSlave.GetBuffer(), "CLAST"));
      strcpy( aDT_slave[ iSlave].pScanString, DtIniFile->GetString( "ScanString", sSlave.GetBuffer(), "U"));
      strcpy( aDT_slave[ iSlave].pAbortString, DtIniFile->GetString( "AbortString", sSlave.GetBuffer(), "/Q"));
      strcpy( aDT_slave[ iSlave].pSlaveString, DtIniFile->GetString( "SlaveString", sSlave.GetBuffer(), "#%d"));

    }
  }

  //Setup the ODBC environment
  //CODBC::CEnvironment env;
  nRet = env.Create();
  env.ValidateReturnValue(nRet);
  nRet = env.SetAttr(SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3);
  env.ValidateReturnValue(nRet);  
  
  nRet = env.SetAttrU(SQL_ATTR_CONNECTION_POOLING, SQL_CP_OFF);
  env.ValidateReturnValue(nRet);  
      
  nRet = env.GetAttr(SQL_ATTR_ODBC_VERSION, nVersion);
  env.ValidateReturnValue(nRet);

  nRet = env.GetAttrU(SQL_ATTR_CONNECTION_POOLING, nPooling);
  env.ValidateReturnValue(nRet);

  nRet = env.CommitTran();
  env.ValidateReturnValue(nRet);
  nRet = env.RollbackTran();
  env.ValidateReturnValue(nRet);

  //Setup the connection
  nRet = con.Create(env);
  con.ValidateReturnValue(nRet);

  //Set login timeout to 5 seconds
  nRet = con.SetAttrU(SQL_ATTR_LOGIN_TIMEOUT, 5);
  con.ValidateReturnValue(nRet);

  //Connect to the datasource 
  nRet = con.DriverConnect(reinterpret_cast<SQLTCHAR*>(_T("Driver={SQL Server};Server=RLD-HPZ400A;Database=datataker;UID=datataker;PWD=datataker;")), sFullInitString);

  //create the wrapper objects
  tblTellerstand = new CdboTellerStandUpdate;
  tblBlokInfo = new CdboBlokInfoInsert;


  //get the communication strings from the ini file
  tmpbuf = (char *)DtIniFile->GetString( "PROTOCOL", "Delimiter", ",");
  pDelimiter = tmpbuf[ 0];

  tmpbuf = (char *)DtIniFile->GetString( "PROTOCOL", "Decimal", ".");
  pDecimal = tmpbuf[ 0];

  tmpbuf = (char *)DtIniFile->GetString( "PROTOCOL", "Terminator", "\4");
  pTerminator = tmpbuf[ 0];

  time_t t = time(0);   // get time now
    struct tm * timeinfo = localtime( & t );
//    tm timeinfo = {};
//  timeinfo.tm_year = 2014 - 1900;
//  timeinfo.tm_mday = 16;
//  timeinfo.tm_mon = 2;
//  mktime(&timeinfo);
  int dayNumber = timeinfo->tm_yday;

  return true;
}

void DataTakerApp::PollDevices( SerialPort *DTComPort)
{
  unsigned short sCount = 0;
  char cLast='\0';


  //there are several steps which will to do before we can query a device
  //software assumes a device can always send a datastring, we try to respond to this string correctly
  /* the order in communication is:
     1. send init command string
        complete string slave 2 = "#2/c/e/k/L/m/n/O/r/t/u P22=44 P24=4 P39=0  D=75 T=12:39:21 R1-E D T 1+..17+V\0xd\0xa"
        init string = "/c/e/k/L/m/n/O/r/t/u P22=44 P24=4 P39=0 "
        date string = " D=75" //number of days in this year
        time string = " T=12:39:21" //current time
        start string = "R1-E"
        read string = " D T 1+..17+V"
        terminator string = \0xd\0xa"
     2. keep silent for 10 seconds
     3. send clear command for device
     4. send ask for data command
     5. wait 5 seconds, and go to step 4

     7. check for start of new day: send date + time + terminator string

     9. check for received data, and try to evaluate
  */
  /*
InitString = "/c/e/k/L/m/n/O/r/t/u P22=44 P24=4 P39=0 "
ReadString = " D T 1+..17+V" + Chr(13) + Chr(10)
ClearString = "CLAST"
AbortString = "/Q"
ScanString = "U"

  */
  CString strSend;
  CString strTemp;

  typedef std::chrono::high_resolution_clock Clock;
  typedef std::chrono::milliseconds milliseconds;
  Clock::time_point t0 = Clock::now();

  //open COM port
  if ( !DTComPort->IsOpened()) DTComPort->OpenPort();

  //fail if comport is not opened
  if ( !DTComPort->IsOpened())
  {

    //first of all release the com port, make sure for at least one second the port is not opened
    Sleep( 4500);

    //process all the slaves
    for (int i = 0; i < MAX_SLAVES; i++)
    {

      //process only present slaves
      if (aDT_slave[ i].address >= 0)
        aDT_slave[ i].status = 0; //reset defined slaves
    }

    //wait for polltime and update the time past
    UpdateTime( 0);

    return;
  }

  int tx_error = SER_GOOD; //only send errors are handled to close the port
  int com_error = SER_GOOD;

  //process all the slaves
  for (int i = 0; i < MAX_SLAVES; i++)
  {

    //save the last transmit error
    if ( tx_error  != SER_GOOD) com_error = tx_error;

    //process only present slaves
    if (aDT_slave[ i].address >= 0)
    {



      switch (aDT_slave[ i].status)
      {

        case 0: //start with reset
          //DtString = " D=" + Trim(Str(DatePart("y", Date))) + " T=" + Time$
          //Call DisplayOutput(Slave(AktSlave) + InitString + DtString + " " + IniString.Text + ReadString)

          dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                             6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                             aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

          strSend.Format( aDT_slave[ i].pSlaveString, aDT_slave[ i].address);
          strSend += aDT_slave[ i].pInitString;
          strTemp.Format( aDT_slave[ i].pDateString, GetYearDay());
          strSend += " ";
          strSend += strTemp;
          strSend += " ";
          strTemp.Format( aDT_slave[ i].pTimeString, GetTimeNow());
          strSend += strTemp;
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pStartString);
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pReadString);
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pTermString);

          tx_error = DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

          //update status and time elapsed
          aDT_slave[ i].status = 1;
          aDT_slave[ i].msec = 0;
          break;

        case 1: //wait 10 seconds

          if ( aDT_slave[ i].msec >= 10000)
          {

            dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                               6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                               aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

            aDT_slave[ i].status = 2;
            aDT_slave[ i].msec = 0;
          }

          break;

        case 2: //ask data from slave

          dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                             6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                             aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

          strSend.Format( aDT_slave[ i].pSlaveString, aDT_slave[ i].address);
          strSend += aDT_slave[ i].pScanString;

          tx_error = DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

          //update status and time elapsed
          aDT_slave[ i].status = 3;
          aDT_slave[ i].msec = 0;
          break;

        case 3: //wait 4 seconds for data, if received go to 4, otherwise abort and go to 2

          if ( aDT_slave[ i].msec >= 4000)
          {

            dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                               6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                               aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

            //send abort string
            strSend.Format( aDT_slave[ i].pSlaveString, aDT_slave[ i].address);
            strSend += aDT_slave[ i].pAbortString;

            tx_error = DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

            aDT_slave[ i].status = 2;
            aDT_slave[ i].msec = 0;
          }

          break;

        case 4: //data received and handled, send clear

          dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                             6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                             aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

          strSend.Format( aDT_slave[ i].pSlaveString, aDT_slave[ i].address);
          strSend += aDT_slave[ i].pClearString;

          tx_error = DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

          //update status and time elapsed
          aDT_slave[ i].status = 5;
          aDT_slave[ i].msec = 0;
          break;

        case 5: //wait 5 seconds and ask for data

          if ( aDT_slave[ i].msec >= 4000)
          {

            dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                               6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                               aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

            aDT_slave[ i].status = 2;
            aDT_slave[ i].msec = 0;
          }
          break;

        default:
          break;
      }
    }
  }


  sCount = 1; 
  //try receving one character at a time, keep receiving while there is data
  while ( sCount)
  {
    DTComPort->Receive( &sCount, &cLast);

    switch (cLast)
    {
      case 4:

        //there is data, save it in the database
        UseData(DTComPort->RX_GetBuffer(), DTComPort->RX_GetLength());
        break;

      case 13:
        //check for 'empty' string
        //"empty" + Chr(13)
        if ( strstr( (char *)DTComPort->RX_GetBuffer(), "empty\x00d") != NULL)
        {

          unsigned int i = 0;
          char SlaveId[20];
          char *SlaveBuf = (char *)DTComPort->RX_GetBuffer();
          SlaveId[0] = '\0';

          while (i <= DTComPort->RX_GetLength() && i <= 20 && SlaveBuf[ i] != ',')
          {

            SlaveId[ i] = SlaveBuf[ i];
            SlaveId[ ++i] = '\0';
          }

          int SlaveNr = atoi( SlaveId);

          if ( SlaveNr > 0) //this is a real slave number
          {

            //perform a reset
            aDT_slave[ SlaveNr].status = 0;
          }

          //clear receive buffer
          DTComPort->RX_Clear();
        }
        break;

      default:
        break;
    }
  }

  //fake a received message
  unsigned int tp = GetTimePast();

  if ( ( tp >= 35000))
  {
    CString Ctest = "3,21,08:11:28, 7.368, 1022.0, 1030.4, 1026.8, 1027.2, 9.896, 10.112, 1031.6, 1028.8, 10.136, 10.532, 1032.4, 318.0, 14.252, 14.344, 14.196, 14.412, 317.2, 14.660, 14.584, 319.6,-50.64, 322.4, 17.712, 16.316, 326.4, 17.136, 14.196, 13.968, 13.208, 17.252, 331.2, 322.8\4";
    theApp.UseData( (unsigned char *)Ctest.GetBuffer(), Ctest.GetLength());
    timepast = 0;
  }


  Clock::time_point t1 = Clock::now();
  milliseconds ms = std::chrono::duration_cast<milliseconds>(t1 - t0);
  __int64 ms_correction = ms.count();


  //wait for polltime and update the time past
  UpdateTime( (int)ms_correction);

  //check the transmit errors, if there is one close the COM port
  if (com_error != SER_GOOD)
  {

    //clear buffers
    DTComPort->RX_Clear();
    DTComPort->TX_Clear();

    //close the com port
    DTComPort->ClosePort();

    //reset all slaves
    //process all the slaves
    for (int i = 0; i < MAX_SLAVES; i++)
    {

      //process only present slaves
      if (aDT_slave[ i].address >= 0)
        aDT_slave[ i].status = 0; //reset defined slaves
    }

    //wait 2 seconds
    Sleep( 2000);
  }

}

//update the time used for polling, i.e update actual time past with the poll time, and perform a sleep
//note that the time past is aproximate, we don't need the exact time! Only used to calculate timeouts.
void DataTakerApp::UpdateTime( unsigned int ms_correction)
{

  unsigned int sleepms = (ms_correction < polltime ? polltime - ms_correction : 0);

  Sleep( (DWORD)sleepms);

  //process all the slaves
  for (int i = 0; i < MAX_SLAVES; i++)
  {

    //process only present slaves, update slave time value
    if (aDT_slave[ i].address >= 0)
      aDT_slave[ i].msec += sleepms + ms_correction;
  }

  timepast++;
}


void DataTakerApp::UseData( unsigned char *ubuf, unsigned short ulen)
{
  
  int idx = 0;
  double fTemp;
  int i, k;
  unsigned short sWeight;
  unsigned short sTemperature;
  unsigned short sHeight;
  unsigned short sSlave;

//Private Sub VerwerkRegel(Buffer, Lengte)
//Dim I, HJaar, HDag, Dag, Slv As Integer
//Dim Dat, Char As String
//RDataString = ""
//Index = 0

//I = 1
  //Haal alle elementen op gescheiden door een komma tot EOT (4)
  for (i = 0, k = 0; i < ulen; i++)
  {
    switch (ubuf[ i])
    {

      case ' ': //strip spaces
      case  10: //LF
      case  13: //CR
        break;

      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '.':
      case '-':
      case ':':

        rx_data[ idx][ k++] = ubuf[ i];
        break;

      case '\4': //end of message
      case ',':
        
        rx_data[ idx++][ k++] = '\0';
        //if (idx == 2) 
        //{
        //  //save time string
        //  memccpy( dt_time, val_buf, '\0', k);
        //  dt_time[ k] = '\0';
        //}
        //else 
        //{
        //  rx_data[ idx++] = atoi( (const char *)val_buf);
        //}

        k = 0;
        break;

      default:
        break;
    }
  }

  //slave id
  sSlave = atoi( (const char *)rx_data[ 0]);

  //date + time string
  //we have the time from the datataker, and the day of year
  time_t t = time(0);   // get time now
  struct tm * timenow = localtime( & t );
  mktime(timenow);

  tm timeinfo = {};
  memset( (void *)&timeinfo, 0, sizeof( tm));
  timeinfo.tm_year = timenow->tm_year;
  timeinfo.tm_mday = atoi( (const char *)rx_data[ 1]);
  mktime(&timeinfo);
  
  //build date + time
  char sDateTime[80];
  memset( (void *)sDateTime, 0, sizeof(sDateTime));
  strcpy( sDateTime, _itoa( timeinfo.tm_mday, sDateTime, 10));
  strcat( sDateTime, "-");
  _itoa( timeinfo.tm_mon+1, &sDateTime[strlen( sDateTime)], 10);
  strcat( sDateTime, "-");
  _itoa( timeinfo.tm_year+1900, &sDateTime[strlen( sDateTime)], 10);
  strcat( sDateTime, " ");
  strcat( sDateTime, (const char *)rx_data[ 2]);
  strcpy( (char *)rx_data[ 2], sDateTime);

  //weight
  for ( i = 0, sWeight = 0; i <= 11; i++)
  {
    fTemp = atof( (const char *)rx_data[ i + 3]);
    if ( fTemp < 400) sWeight |= (0x1 << i);
  }

  sWeight = bcdToDec( sWeight);

  //temperature
  for ( i = 0, sTemperature = 0; i <= 9; i++)
  {
    fTemp = atof( (const char *)rx_data[ i + 15]);
    if ( fTemp > 200) sTemperature |= (0x1 << i);
  }

  sTemperature = bcdToDec( sTemperature);

  //height
  for ( i = 0, sHeight = 0; i <= 10; i++)
  {
    fTemp = atof( (const char *)rx_data[ i + 25]);
    if ( fTemp > 200) sHeight |= (0x1 << i);
  }

  sHeight = bcdToDec( sHeight);

  //update the slave record
  aDT_slave[ sSlave].weight = sWeight;
  aDT_slave[ sSlave].height = sHeight;
  aDT_slave[ sSlave].temperature = sTemperature;
  strcpy( (char *)aDT_slave[ sSlave].date_time, (char *)rx_data[ 2]);

  //Increment anode counte rin database table
  unsigned short sCounter = 0;
  tblTellerstand->AddOne( &con, "[datataker].[dbo].[tellerstand]", "[135]", &sCounter);

  //update the anode counter
  aDT_slave[ sSlave].anode = sCounter;

  //add record i th eloca datataker database
  tblBlokInfo->Insert( &con, &aDT_slave[ sSlave]);
  
  //update slave status
  if ( aDT_slave[ sSlave].status)
    aDT_slave[ sSlave].status = 4; //send clear
}

