//****************************************************************************
//*                           Irinos.pas
//****************************************************************************
//*   Purpose:    Data exchange with the Irinos measurement system
//*
//****************************************************************************
//*   Comment:    High-Level-Access to the Irinos system.
//*               Uses MscDll.dll.
//*
//****************************************************************************
//*             (c) Messtechnik Sachs GmbH, D-73614 Schorndorf
//****************************************************************************
unit cl_Irinos;

interface

uses
	Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
	Dialogs, cl_mscCall, cl_opcodes, TypInfo, MMSystem;


const
  // General communication parameters
	cSendPeriod = 1;          // [ms] Minimum time between two data packets
	cDisconnectTimeout = 500; // [ms]
	cResponseTimeOut = 75;    // [ms] Timeout for data packet response
	cRetryCount =	10;         // Number of packet transfer retries

  // Specific communication parameters
 	cMaxStdBufSize = 1500;    // Max buffer size (e.g. for static measurement values
  cMaxStaticChannels = 256; // Max number of static measurement channels
  cMaxBitIOBytes = 64;      // Max number of bytes for Bit I/Os
  cCmdTimeOut =	500;        // [ms] Timeout for WriteCommand

  // Messages
	WM_MESSAGE_MSC_READSTATIC = WM_USER + 0;  // Message for "static measurement values received"
	WM_MESSAGE_MSC_BITIO      = WM_USER + 1;	// Message for "Bit I/O data exchanged"
	WM_MESSAGE_MSC_HW_STATUS  = WM_USER + 2;	// Message for "hardware status updated


type
	TConnectStatus = (	cscUndefined,        // Initialization
                      cscTryConnect,       // Trying to connect
                      cscNoDevices,        // No devices found
                      cscNoDeviceInfo,     // No device info available
                      cscConnected,        // Connection established successfully
                      cscConnectionClosed, // Connection closed
                      cscNoDeviceOpen,     // Failed to open connection to device
                      cscInitDone,         // Device initialization finished
                      cscInitFailed,       // Device initialization failed
                      cscCommStarted,      // Communication started
                      cscCommFailed        // Failed to start communication
   );


	TMeasData = record
		measData: packed array of Integer;
	end;



	TIrinos = class( TCMscDll )
         constructor Create;
		private
         pDevice : MSC_HANDLE;
         ulNDevices: Cardinal;
         ucDummy : byte;
         ucRHSVersion : byte;
         bMMTimerStarted : Boolean;
         procedure StartMMTimer;
		public
         staticMeasVals : packed array [ 1 .. cMaxStaticChannels ] of integer;  // Static measurement values
         hardStat : packed array [ 1 .. cMaxStaticChannels ] of byte;           // Hardware status
         digInputBytes : packed array [ 1 .. cMaxBitIOBytes ] of byte;          // Bit I/O: digital inputs
         digOutputBytes : packed array [ 1 .. cMaxBitIOBytes ] of byte;         // Bit I/O: digital outputs
         rcvBufferCmd : packed array [ 1 .. cMaxStdBufSize ] of Byte;           // Receive buffer for "WriteCommand"
         sndBufferCmd : packed array [ 1 .. cMaxStdBufSize ] of Byte;           // Send buffer for "WriteCommand"
         rcvBufferDyn	 : packed array of TMeasData;                             // Receive buffer for dynamic measurement values
         eConnectStat: TConnectStatus;
         function  GetConnectionStatusText : string;
         function  ConnectIrinos : TConnectStatus;
         function  StopAndClose : boolean;
         function  SetupStaticChannel( OpCode : byte; SndSize : Cardinal; pWindowHwnd : HWND ) : Boolean;
         procedure ReadStaticMeasurementValues;
         procedure ReadDigitalInputs;
         procedure PostDigitalOutputs;
         procedure ReadHardwareStatus;
         function  WriteDownCommandCMD( Opcode : byte; SndSize : Integer ) : Boolean;
         function  WriteDownCommandS( Opcode : byte; var data : AnsiString ) : Boolean;
         function  SetupDynamicChannel(OpCode : byte; maxMeasValues, nChannels : Cardinal ) : Boolean;
         function  GetDynBytesReceived( Opcode : byte; var ulPosition : Cardinal) : Boolean;
	end;



implementation



//*****************************************************************************
// FUNCTION: MMTimerCallback
//-----------------------------------------------------------------------------
// Dummy callback-function for multimedia timer.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: none
//*****************************************************************************/
procedure MMTimerCallback(TimerID, Msg: Uint; dwUser, dw1, dw2: DWORD); pascal;
var
  ulTEv : Cardinal;
begin
  Inc(ulTEv);
end;





//*****************************************************************************
// FUNCTION: TIrinos.Create
//-----------------------------------------------------------------------------
// Class constructor
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: none
//*****************************************************************************
constructor TIrinos.Create;
begin
	inherited create;
   eConnectStat := cscUndefined;
   ulNDevices := 0;
   pDevice := nil;
   bMMTimerStarted := false;
end;



//*****************************************************************************
// FUNCTION: cl_Irinos::ConnectIrinos
//-----------------------------------------------------------------------------
// Trying to establish a connection to the first Irinos-System, which can be
// found. Communication parameters are taken from Msc.cfg by the DLL.
//
// Note:
// It is possible to access more than one Irinos-System from the same PC.
// In this case more than one Device-Handle must be created.
// However: In more than 10 years, there was not any application with this
// requirement. If you want/need to use it, please request for more information.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: see type definition
//*****************************************************************************
function TIrinos.ConnectIrinos : TConnectStatus;
var
   ulBusType: Cardinal;
   s: Ansistring;
begin
   Result := cscTryConnect;

   // Get count of available devices.
	 // If 1 Irinos-System is connected, 1 should be returned.
   if (F_MSC_EnumerateDevices(ulNDevices) = MSC_STATUS_SUCCESS) then begin
   end
   else begin
      Result := cscNoDevices;
   end;

   // Read device information
   if Result = cscTryConnect then begin
      ulBusType := 0;
      s := '';
      SetLength( s, MSC_MAX_UNIQUEID_SIZE );
      if (F_MSC_GetDeviceInfo( ulNDevices - 1, ulBusType, pAnsichar(s) ) = MSC_STATUS_SUCCESS) then begin
      end
      else begin
         Result := cscNoDeviceInfo;
      end;
   end;

   // Open device / create handle for device
   if Result = cscTryConnect then begin
      pDevice := nil;
      if F_MSC_OpenDevice( ulNDevices - 1, pDevice ) = MSC_STATUS_SUCCESS then begin
         Result := cscConnected;
      end
      else begin
         Result := cscNoDeviceOpen;
      end;
   end;


   // Initialize device
   if Result = cscConnected then begin
      if F_MSC_InitDevice( pDevice ) = MSC_STATUS_SUCCESS then begin
         Result := cscInitDone;
      end
      else begin
         Result := cscInitFailed;
      end;
   end;


   // Start communication
   if Result = cscInitDone then begin
      if F_MSC_Start( pDevice, cSendPeriod, cDisconnectTimeout, cRetryCount, cResponseTimeout ) = MSC_STATUS_SUCCESS then begin
         Result := cscCommStarted;
      end
      else begin
         Result := cscCommFailed;
      end;
   end;

   eConnectStat := Result;
end;


//*****************************************************************************
// FUNCTION: TIrinos.StopAndClose
//-----------------------------------------------------------------------------
// Close the connection (if opened).
// This function should be called before closing the application.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: true if a connection was closed.
//*****************************************************************************
function TIrinos.StopAndClose : boolean;
begin
	if pDevice <> nil then begin
		F_MSC_Stop(pDevice);
		F_MSC_CloseDevice(pDevice);
		eConnectStat := cscConnectionClosed;
		Result := true;
	end
	else begin
      Result := false;
   end;
end;


//*****************************************************************************
// FUNCTION: TIrinos.GetConnectionStatusText
//-----------------------------------------------------------------------------
// Returns a description for the current connection status.
// There is no need to use this function. However, it may be helpful to
// show some info on the GUI about the current connection status.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: Description of the current connection status.
//*****************************************************************************
function TIrinos.GetConnectionStatusText : string;
begin
   Result := 'Unknown status (' + GetEnumName(TypeInfo(TConnectStatus), Integer(eConnectStat)) + ')';

   case eConnectStat of
      cscUndefined:        begin Result := 'Not initialized'; end;
      cscTryConnect:       begin Result := 'Establishing connection'; end;
      cscNoDevices:        begin Result := 'No devices found'; end;
      cscNoDeviceInfo:     begin Result := 'Could not get device information'; end;
      cscConnected:        begin Result := 'Connected'; end;
      cscConnectionClosed: begin Result := 'Connection closed'; end;
      cscNoDeviceOpen:     begin Result := 'Could not open device'; end;
      cscInitDone:         begin Result := 'Device initialization done'; end;
      cscInitFailed:       begin Result := 'Device initialization failed'; end;
      cscCommStarted:      begin Result := 'Communication started'; end;
      cscCommFailed:       begin Result := 'Communication failed'; end;
   end;

end;



//*****************************************************************************
// FUNCTION: TIrinos.SetupStaticChannel
//-----------------------------------------------------------------------------
// Start reading static measurement values / Bit I/O / hardware status
//-----------------------------------------------------------------------------
// PARAMETERS:
// ucOpode: DLL-Opcode opcRS or opcBIO or opcRHS.
// ulSendSize: Size of data to be sent from corresponding send buffer.
//             --> currently only required for opcBIO!
// pWindowHandle: Window handle which shall receive the notification message,
//                when the static channel was updated.
//-----------------------------------------------------------------------------
// Return: true if a static channel was setup successfully.
//*****************************************************************************/
function TIrinos.SetupStaticChannel( OpCode : byte; SndSize : Cardinal; pWindowHwnd : HWND ) : Boolean;
var
   res, RcvSize : Cardinal;
begin
   Result := false;

   if eConnectStat = cscCommStarted then begin

      // ## Read static measurement values
      if OpCode = opcRS then begin

         RcvSize := sizeof( staticMeasVals );
         // The following loop will always be left vie 'break'
         while true do begin
            // Register static channel
            res := F_MSC_SetupStaticChannel( pDevice, Opcode, 1, @ucDummy, RcvSize );
            if res <> MSC_STATUS_SUCCESS then begin
               break;
            end;

            // Register message on response
            res := F_MSC_SetNotificationMessage( pDevice, Opcode, pWindowHwnd, WM_MESSAGE_MSC_READSTATIC, 10, 10 );
            if res <> MSC_STATUS_SUCCESS then begin
               break;
            end;

            // Register message on error
            res := F_MSC_SetNotificationMessage( pDevice, -1, pWindowHwnd, WM_MESSAGE_MSC_READSTATIC, 20, 20 );
            if res <> MSC_STATUS_SUCCESS then begin
               break;
            end;

            // If the while loop has not been left via 'break' so far, setting
            // up the static channel was successful.
            Result := true;
            break;
         end;
      end

      // ## Read digital inputs / write digital outputs
      else if OpCode = opcBIO then begin
         RcvSize := sizeof( digInputBytes );
         if SndSize > sizeof( digOutputBytes ) then begin
            SndSize := sizeof( digOutputBytes );
         end;

         // The following loop will always be left vie 'break'
         while true do begin
            // Register static channel
            res := F_MSC_SetupStaticChannel( pDevice, Opcode, SndSize, @digOutputBytes, RcvSize );
            if res <> MSC_STATUS_SUCCESS then begin
               break;
            end;

            // Register message on response
            res := F_MSC_SetNotificationMessage( pDevice, Opcode, pWindowHwnd, WM_MESSAGE_MSC_BITIO, 10, 10 );
            if res <> MSC_STATUS_SUCCESS then begin
               break;
            end;

            // If the while loop has not been left via 'break' so far, setting
            // up the static channel was successful.
            Result := true;
            break;
         end;
      end

      // ## Read hardware status
      else if OpCode = opcRHS then begin
         RcvSize := sizeof( hardStat );
         ucRHSVersion := 2;
         // The following loop will always be left vie 'break'
         while true do begin
            // Register static channel
            res := F_MSC_SetupStaticChannel( pDevice, Opcode, 1, @ucRHSVersion, RcvSize );
            if res <> MSC_STATUS_SUCCESS then begin
               break;
            end;

            // Register message on response
            res := F_MSC_SetNotificationMessage( pDevice, Opcode, pWindowHwnd, WM_MESSAGE_MSC_HW_STATUS, 10, 10 );
            if res <> MSC_STATUS_SUCCESS then begin
               break;
            end;

            // If the while loop has not been left via 'break' so far, setting
            // up the static channel was successful.
            Result := true;
            break;
         end;
      end;
   end;

end;




//*****************************************************************************
// FUNCTION: TIrinos.GetStaticMeasurementValues
//-----------------------------------------------------------------------------
// Copy the static measurement values into the array staticMeasVals.
// The application can read directly from this array.
// This function is typically called after new measurement values have been
// received, e.g. to update the GUI.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: none
//*****************************************************************************/
procedure TIrinos.ReadStaticMeasurementValues;
var
  ulDataCount : Cardinal;
begin
   if eConnectStat = cscCommStarted then begin
      // Request values from the DLL
      F_MSC_ReadStatic(pDevice, opcRS, sizeof(staticMeasVals), @staticMeasVals, ulDataCount);
   end;
end;



//*****************************************************************************
// FUNCTION: TIrinos.ReadDigitalInputs
//-----------------------------------------------------------------------------
// Copy the digital inputs into the array digiInputBytes.
// The application can read directly from this array.
// This function is typically called after new digital input values have been
// received, e.g. to update the GUI.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: none
//*****************************************************************************/
procedure TIrinos.ReadDigitalInputs;
var
  ulDataCount : Cardinal;
begin
  if eConnectStat = cscCommStarted then begin
    // Request values from the DLL
    F_MSC_ReadStatic(pDevice, opcBIO, sizeof(digInputBytes), @digInputBytes, ulDataCount);
  end;
end;



//*****************************************************************************
// FUNCTION: TIrinos.PostDigitalOutputs
//-----------------------------------------------------------------------------
// Tell the MscDll that the digital output data has been updated.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: none
//*****************************************************************************/
procedure TIrinos.PostDigitalOutputs;
begin
  if eConnectStat = cscCommStarted then begin
    F_MSC_RefreshChannel(pDevice, opcBIO);
  end;
end;



//*****************************************************************************
// FUNCTION: TIrinos.ReadHardwareStatus
//-----------------------------------------------------------------------------
// Copy the hardware status values into the array hardStat.
// The application can read directly from this array.
// This function is typically called after new hardware status data has been
// received, e.g. to update the GUI.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: none
//*****************************************************************************/
procedure TIrinos.ReadHardwareStatus;
var
  ulDataCount : Cardinal;
begin
   if eConnectStat = cscCommStarted then begin
      // Request values from the DLL
      F_MSC_ReadStatic(pDevice, opcRHS, sizeof(hardStat), @hardStat, ulDataCount);
   end;
end;



//*****************************************************************************
// FUNCTION: TIrinos.WriteDownCommandCMD
//-----------------------------------------------------------------------------
// Write binary "command" data to the Irinos system and receive the
// corresponding response.
// Data to write must be written to sndBufferCMD before.
// Received data can be read from rcvBufferCMD.
// The response should be evaluated by the application.
//-----------------------------------------------------------------------------
// PARAMETERS:
// Opcode: Opcode for the command
// SndSize: Number of bytes to send
//-----------------------------------------------------------------------------
// Return: true if data was exchanged successfully
//*****************************************************************************/
function TIrinos.WriteDownCommandCMD( Opcode : byte; SndSize : Integer ) : Boolean;
var
   res, nCount, RcvSize : Cardinal;
begin
   Result := False;

   if eConnectStat = cscCommStarted then begin
      RcvSize := sizeof( rcvBufferCMD );
      res := F_MSC_WriteCommand( pDevice, Opcode, SndSize, @sndBufferCMD[1], RcvSize, @rcvBufferCMD[1], nCount, cCmdTimeOut );
      if res = MSC_STATUS_SUCCESS then begin
         Result := True;
      end;
   end;
end;



//*****************************************************************************
// FUNCTION: TIrinos.WriteDownCommandS
//-----------------------------------------------------------------------------
// Write "command" data string to the Irinos system and receive the
// corresponding response.
// Data to write must be contained in the parameter data.
// Received data will be written to the same parameter data.
// The response should be evaluated by the application.
//-----------------------------------------------------------------------------
// PARAMETERS:
// Opcode: Opcode for the command
// SndSize: Number of bytes to send
//-----------------------------------------------------------------------------
// Return: true if data was exchanged successfully
//*****************************************************************************/
function TIrinos.WriteDownCommandS( Opcode : byte; var data : AnsiString ) : Boolean;
var
   res, nCount, RcvSize : Cardinal;
begin
   Result := false;

   if eConnectStat = cscCommStarted then begin
      RcvSize := sizeof(rcvBufferCMD);

      res := F_MSC_WriteCommand( pDevice, Opcode, Length(data), PAnsiChar( data ), RcvSize, @rcvBufferCMD, nCount, cCmdTimeOut );
      if res = MSC_STATUS_SUCCESS then begin
         // Convert incoming data to string
         SetString(data, PAnsiChar(@rcvBufferCMD), nCount);
         Result := true;
      end;
   end;
end;


//*****************************************************************************
// FUNCTION: TIrinos.SetupDynamicChannel
//-----------------------------------------------------------------------------
// Allocates memory for dynamic measurement values and assigns this buffer
// to the MscDll. The MscDll will write received dynamic measurement values
// automatically into this buffer.
//-----------------------------------------------------------------------------
// PARAMETERS:
// Opcode: opcRDM1 or opcRDM2.
// maxMeasValues: Maximum number of measurement values (per channel) to be
//                received.
// ulNChannels: Number of measurement channels.
//-----------------------------------------------------------------------------
// Return: true if dynamic channel was setup successfully.
//*****************************************************************************/
function TIrinos.SetupDynamicChannel(Opcode : byte; maxMeasValues, nChannels : Cardinal ) : Boolean;
var
  ucDummy : byte;
  ulI, ulNBytes : Cardinal;
begin
  Result := false;

  if eConnectStat = cscCommStarted then begin

    // Set windows tick time to 1 ms for faster data transfer.
    // This is only required if a fast data transfer is necessary.
    if cSendPeriod < 16 then begin
         StartMMTimer;
    end;

    Result := (F_MSC_SetupExtendedDynamicChannel(pDevice, Opcode, nChannels, 1, @ucDummy ) = MSC_STATUS_SUCCESS);

    if Result then begin
      SetLength(rcvBufferDyn, nChannels);
      ulNBytes := maxMeasValues * 4;

      for ulI := 0 to nChannels - 1 do begin
        SetLength(rcvBufferDyn[ulI].measData, ulNBytes);
        Result := (F_MSC_AttachSubChannelBuffer(pDevice, Opcode, ulI, ulNBytes, @rcvBufferDyn[ulI].measData[0]) = MSC_STATUS_SUCCESS);
      end;
    end;
  end;
end;


//*****************************************************************************
// FUNCTION: TIrinos.GetDynBytesReceived
//-----------------------------------------------------------------------------
// Returns the number of bytes that have been received so far for the dynamic
// measurement given in the opcode.
// Note:
// Each measurement value is a 32 Bit signed value (even if it is 16 or 8 bit
// value inside the Irinos system). To get the number of values received per
// channel, use the following formula:
// nValsPerCchannelReceived = nBytesReceived / 4.
//-----------------------------------------------------------------------------
// PARAMETERS:
// Opcode: opcRDM1 or opcRDM2
// ulPosition: Number of bytes received per channel. This value is only changed
//             if the return value is true.
//-----------------------------------------------------------------------------
// Return: true if position was read successfully
//*****************************************************************************/
function TIrinos.GetDynBytesReceived( Opcode : byte; var ulPosition : Cardinal) : Boolean;
var
  ulPos : Cardinal;
begin
  Result := false;
  ulPos := 0;

  if eConnectStat = cscCommStarted then begin
    Result := (F_MSC_GetPosition(pDevice, Opcode, ulPos) = MSC_STATUS_SUCCESS);
    if Result then begin
      ulPosition := ulPos;
    end;
  end;
end;



//*****************************************************************************
// FUNCTION: TIrinos.StartMMTimer
//-----------------------------------------------------------------------------
// Start a Windows multimedia timer with a 1 ms resolution.
// Windows normally has a timer interval of 15.6 ms (64 Hz). For real
// high-speed communication, this is too long. By starting a multimedia
// timer with a lower resolution, the DLL communication will run faster.
// Notes:
// - This makes only sense, if a high data throughput rate is required.
//   It does not make any sense, if only static data transfer is used.
// - On battery powered systems, battery life will be much shorter.
//   It is assumed that the DLL is not used on battery powered systems in
//   the production environment.
//-----------------------------------------------------------------------------
// PARAMETERS: none
//-----------------------------------------------------------------------------
// Return: none
//*****************************************************************************/
procedure TIrinos.StartMMTimer;
begin
  if bMMTimerStarted = false then begin
    bMMTimerStarted := true;
    TimeSetEvent(1, 0, @MMTimerCallback, 0, TIME_PERIODIC);
  end;
end;


end.
