unit CANdrvD;
//***************************************************************************************
// Project Name: TCanDriver component for Borland Delphi
//  Description: Encapsulates Vector's CANlib v4.3 into a VCL component
//    File Name: CANdrvD.pas
//
//---------------------------------------------------------------------------------------
//                          C O P Y R I G H T
//---------------------------------------------------------------------------------------
//   Copyright (c) 2011 by Feaser    http://www.feaser.com    All rights reserved
//
//   This software has been carefully tested, but is not guaranteed for any particular
// purpose. The author does not offer any warranties and does not guarantee the accuracy,
//   adequacy, or completeness of the software and is not responsible for any errors or
//              omissions or the results obtained from use of the software.
//
//---------------------------------------------------------------------------------------
//                            L I C E N S E
//---------------------------------------------------------------------------------------
// This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with OpenBLT.
// If not, see <http://www.gnu.org/licenses/>.
//
// A special exception to the GPL is included to allow you to distribute a combined work
// that includes OpenBLT without being obliged to provide the source code for any
// proprietary components. The exception text is included at the bottom of the license
// file <license.html>.
//
//***************************************************************************************
interface


//***************************************************************************************
// Includes
//***************************************************************************************
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, CANlibD;


//***************************************************************************************
// Type Definitions
//***************************************************************************************
type
  TChannel = ( channel0, channel1 );
  THardware = ( Virtual, CANcardX, CANcardXL, CANcaseXL, CANboardXL,
                CANboardXL_Compact, CANac2, CANac2Pci, CANpari, CANdongle,
                CANcard, CANcardY, CANcard2, EDICcard );
  TDirection = ( Tx, Rx );
  TCanMsg = packed record
    id    : LongInt;
    dlc   : Byte;
    data  : array [0..MAX_MSG_LEN-1] of Byte;
    time  : LongInt;
    ext   : Boolean;
  end;

type
  TMessageEvent = procedure( Sender: TObject; Direction: TDirection; Message: TCanMsg ) of object;
  TErrorFrameEvent = procedure( Sender: TObject; time: LongInt ) of object;
  TBusOffEvent = procedure( Sender: TObject; time: LongInt ) of object;

type
  TCanEventThread = class(TThread)
  private
    { Private declarations }
    FMethod: TThreadMethod;
  protected
    FEventHndl: LongInt;
    procedure Execute; override;
  public
    property Method     : TThreadMethod read FMethod write FMethod;
    property EventHandle: LongInt       read FEventHndl write FEventHndl;
  end;

type
  TCanDriver = class(TComponent)
  private
    { Private declarations }
    FPortHandle    : VPortHandle;
    FChannelMask   : Vaccess;
    FPermissionMask: Vaccess;
    FCanEventThread: TCanEventThread;
    FThreadRunning : boolean;
    FEventHandle   : LongInt;
    function IsThreadRunning: boolean;
    procedure ProcessEvents;
    procedure CopyMessage(event: Vevent; var msg: TCanMsg);
  protected
    { Protected declarations }
    FBaudRate    : LongInt;
    FChannel     : TChannel;
    FHardware    : THardware;
    FFilterMask  : LongInt;
    FFilterCode  : LongInt;
    FPriority    : TThreadPriority;
    FExtendedId  : Boolean;
    FOnMessage   : TMessageEvent;
    FOnErrorFrame: TErrorFrameEvent;
    FOnBusOff    : TBusOffEvent;
    procedure SetBaudRate( Value: LongInt );
    procedure SetChannel( Value: TChannel );
    procedure SetHardware( Value: THardware );
    procedure SetFilterMask( Value: LongInt );
    procedure SetFilterCode( Value: LongInt );
    procedure SetPriority( Value: TThreadPriority );
    procedure SetExtendedId( Value: Boolean );
  public
    { Public declarations }
    constructor Create( AOwner: TComponent ); override;
    destructor  Destroy; override;
    function    Connect: boolean; virtual;
    procedure   Disconnect; virtual;
    function    Transmit( Message: TCanMsg): boolean; virtual;
    function    IsConnected: boolean; virtual;
  published
    { Published declarations }
    property BaudRate    : LongInt          read FBaudRate     write SetBaudRate   default 500000;
    property Channel     : TChannel         read FChannel      write SetChannel    default channel0;
    property Hardware    : THardware        read FHardware     write SetHardware   default Virtual;
    property FilterMask  : LongInt          read FFilterMask   write SetFilterMask default 0;
    property FilterCode  : LongInt          read FFilterCode   write SetFilterCode default 0;
    property Priority    : TThreadPriority  read FPriority     write SetPriority   default tpNormal;
    property ExtendedId  : Boolean          read FExtendedId   write SetExtendedId default False;
    property OnMessage   : TMessageEvent    read FOnMessage    write FOnMessage;
    property OnErrorFrame: TErrorFrameEvent read FOnErrorFrame write FOnErrorFrame;
    property OnBusOff    : TBusOffEvent     read FOnBusOff     write FOnBusOff;
  end;


//***************************************************************************************
// Prototypes
//***************************************************************************************
procedure Register;

implementation
//***************************************************************************************
// Constants
//***************************************************************************************
const
  Channels: array[channel0..channel1] of integer = ( 0, 1 );
  HardwareTypes: array[Virtual..EDICcard] of integer = (
    HWTYPE_VIRTUAL, HWTYPE_CANCARDX, HWTYPE_CANCARDXL, HWTYPE_CANCASEXL, HWTYPE_CANBOARDXL,
    HWTYPE_CANBOARDXL_COMPACT, HWTYPE_CANAC2, HWTYPE_CANAC2PCI, HWTYPE_CANPARI,
    HWTYPE_CANDONGLE, HWTYPE_CANCARD, HWTYPE_CANCARDY, HWTYPE_CANCARD2, HWTYPE_EDICCARD);


//***************************************************************************************
// NAME:           Execute
// PRECONDITIONS:  none
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Overriden Execute function for the CanEventThread. Calls and synchro-
//                 nizes with the TCanDriver.ProcessEvents function.
//
//***************************************************************************************
procedure TCanEventThread.Execute;
begin
  while not Terminated do
  begin
    if FEventHndl <> 0 then // make sure event is configured
    begin
      // wait for receive event
      WaitForSingleObject(FEventHndl, 1000);

      if Assigned(Method) then // make sure TCanDriver.ProcessEvents is set
        Synchronize(Method); // call and synchronize
    end;
  end;
end; //*** end of Execute ***


//***************************************************************************************
// NAME:           Create
// PRECONDITIONS:  none
// PARAMETER:      AOwner : owner of the component
// RETURN VALUE:   none
// DESCRIPTION:    Component constructor. Calls TComponent's constructor and initializes
//                 the private property variables to their default values.
//
//***************************************************************************************
constructor TCanDriver.Create( AOwner: TComponent );
begin
  // call inherited constructor
  inherited Create( AOwner );

  // set defaults for internal variables
  FPortHandle    := INVALID_PORTHANDLE;
  FChannelMask   := 0;
  FPermissionMask:= 0;
  FThreadRunning := False;
  FEventHandle   := 0; 

  // set defaults for properties
  FBaudRate := 500000;
  FChannel := channel0;
  FHardware := Virtual;
  FFilterMask := 0;
  FFilterCode := 0;
  FPriority := tpNormal;
  FExtendedId := False;
end; //*** end of Create ***


//***************************************************************************************
// NAME:           Destroy
// PRECONDITIONS:  none
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Component destructor. Calls TComponent's destructor
//
//***************************************************************************************
destructor TCanDriver.Destroy;
begin
  Disconnect; // close the port and driver
  inherited Destroy; // call inherited destructor
end; //*** end of Destroy ***


//***************************************************************************************
// NAME:           IsConnected
// PRECONDITIONS:  none
// PARAMETER:      none
// RETURN VALUE:   True or False
// DESCRIPTION:    Determines whether or not the CAN driver is connected and active
//
//***************************************************************************************
function TCanDriver.IsConnected: boolean;
begin
  if FPortHandle <> INVALID_PORTHANDLE then
    Result := True
  else
    Result := False;
end; //*** end of IsConnected ***


//***************************************************************************************
// NAME:           IsThreadRunning
// PRECONDITIONS:  none
// PARAMETER:      none
// RETURN VALUE:   True or False
// DESCRIPTION:    Determines whether or not the CAN event thread is running
//
//***************************************************************************************
function TCanDriver.IsThreadRunning: boolean;
begin
  if FThreadRunning = True then
    Result := True
  else
    Result := False;
end; //*** end of IsThreadRunning ***


//***************************************************************************************
// NAME:           CopyMessage
// PRECONDITIONS:  none
// PARAMETER:      event: msg tx or rx event information (source)
//                 msg: buffer to copy message to (destination)
// RETURN VALUE:   none
// DESCRIPTION:    Copies a CAN message from an event type to a TCanMsg type.
//
//***************************************************************************************
procedure TCanDriver.CopyMessage(event: Vevent; var msg: TCanMsg);
var
  cnt: integer;
begin
  if (event.msg.id and EXT_MSG) = EXT_MSG then // 29-bit id?
  begin
    msg.id := (event.msg.id and not EXT_MSG); // reset ext bit
    msg.ext := True; // this is an 29-bit id
  end
  else
  begin
    msg.id := event.msg.id; // store id
    msg.ext := False; // this is an 11-bit id
  end;
  msg.dlc := event.msg.dlc;
  msg.time := event.timeStamp;

  // copy the data bytes
  for cnt :=0 to MAX_MSG_LEN-1 do
  begin
    if cnt < event.msg.dlc then
      msg.data[cnt] := event.msg.data[cnt]
    else
      msg.data[cnt] := 0;
  end;
end; //*** end of CopyMessage ***


//***************************************************************************************
// NAME:           SetBaudRate
// PRECONDITIONS:  none
// PARAMETER:      Value : new baudrate value [0 - 1000000 bps]
// RETURN VALUE:   none
// DESCRIPTION:    Configures the baudrate
//
//  |------------------------------------------------------------------------------------
//  | Update baudrate configuration
//  |------------------------------------------------------------------------------------
//***************************************************************************************
procedure TCanDriver.SetBaudRate( Value: LongInt );
begin
  FBaudRate := Value; // update property
end; //*** end of SetBaudRate ***


//***************************************************************************************
// NAME:           SetChannel
// PRECONDITIONS:  none
// PARAMETER:      Value : channel0 or channel1
// RETURN VALUE:   none
// DESCRIPTION:    Configures the used CAN channel
//
//  |------------------------------------------------------------------------------------
//  | Update channel configuration
//  |------------------------------------------------------------------------------------
//***************************************************************************************
procedure TCanDriver.SetChannel( Value: TChannel );
begin
  FChannel := Value;
end; //*** end of SetChannel ***


//***************************************************************************************
// NAME:           SetHardware
// PRECONDITIONS:  none
// PARAMETER:      Value : type of CAN hardware (Virtual, CANcardXL, etc.)
// RETURN VALUE:   none
// DESCRIPTION:    Configures the used CAN hardware
//
//  |------------------------------------------------------------------------------------
//  | Update hardware configuration
//  |------------------------------------------------------------------------------------
//***************************************************************************************
procedure TCanDriver.SetHardware( Value: THardware );
begin
  FHardware := Value;
end; //*** end of SetHardware ***


//***************************************************************************************
// NAME:           SetFilterMask
// PRECONDITIONS:  none
// PARAMETER:      Value : acceptance filter mask
// RETURN VALUE:   none
// DESCRIPTION:    Configures the acceptance filter mask for the CAN channel
//
//  |------------------------------------------------------------------------------------
//  | Update filter mask value
//  |------------------------------------------------------------------------------------
//***************************************************************************************
procedure TCanDriver.SetFilterMask( Value: LongInt );
begin
  FFilterMask := Value;
end; //*** end of SetFilterMask ***


//***************************************************************************************
// NAME:           SetFilterCode
// PRECONDITIONS:  none
// PARAMETER:      Value : acceptance filter code
// RETURN VALUE:   none
// DESCRIPTION:    Configures the acceptance filter code for the CAN channel
//
//  |------------------------------------------------------------------------------------
//  | Update filter code value
//  |------------------------------------------------------------------------------------
//***************************************************************************************
procedure TCanDriver.SetFilterCode( Value: LongInt );
begin
  FFilterCode := Value;
end; //*** end of SetFilterCode ***


//***************************************************************************************
// NAME:           SetPriority
// PRECONDITIONS:  none
// PARAMETER:      Value : thread priority
// RETURN VALUE:   none
// DESCRIPTION:    Configures the priority for the CAN event thread
//
//  |------------------------------------------------------------------------------------
//  |  y\                      Is Thread running?                                    /n
//  |------------------------------------------------------------------------------------
//  | Stop Thread                                      |
//  | Update Thread priority                           | Update Thread priority
//  | Restart Thread                                   |
//  |------------------------------------------------------------------------------------
//***************************************************************************************
procedure TCanDriver.SetPriority( Value: TThreadPriority );
begin
  if IsThreadRunning then
  begin
   FCanEventThread.Suspend; // suspend the thread
   FPriority := Value; // update the priority
   FCanEventThread.Resume; // resume the thread
  end
  else
  begin
   FPriority := Value; // update the priority
  end;
end; //*** end of SetPriority ***


//***************************************************************************************
// NAME:           SetExtendedId
// PRECONDITIONS:  none
// PARAMETER:      Value : true = support only 29-bit id's, false = support only 11-bit
// RETURN VALUE:   none
// DESCRIPTION:    Configures the support of extended 29-bit identifiers
//
//  |------------------------------------------------------------------------------------
//  | Update extended id support selection
//  |------------------------------------------------------------------------------------
//***************************************************************************************
procedure TCanDriver.SetExtendedId( Value: Boolean );
begin
  FExtendedId := Value;
end; //*** end of SetExtendedId ***/


//***************************************************************************************
// NAME:           Connect
// PRECONDITIONS:  Disconnected from CAN bus
// PARAMETER:      none
// RETURN VALUE:   True or False for succees or error, respectively
// DESCRIPTION:    Initializes the CAN driver and synchronizes the hardware with the CAN
//                 bus.
//
//  |------------------------------------------------------------------------------------
//  |  y\                    Connected?                                              /n
//  |------------------------------------------------------------------------------------
//  | Open the driver (ncdOpenDriver)
//  | Obtain mask to channel (ncdGetChannelMask)
//  | Open the port using this mask (ncdOpenPort)
//  |------------------------------------------------------------------------------------
//  |  y\                    Permission to change settings?                          /n
//  |------------------------------------------------------------------------------------
//  | Configure baudrate (ncdSetChannelBitrate)                   |
//  |------------------------------------------------------------------------------------
//  | Configure acceptance filter (ncdSetChannelAcceptance)
//  | Enable error frames and chipstate events (ncdSetReceiveMode)
//  | Create synchronizatio object (ncdSetNotification)
//  | Reset internal clock (ncdResetClock)
//  | Sync to the CAN bus (ncdActivateChannel)
//  | Empty transmit and receive queue's (ncdFlushXxxQueue)
//  |------------------------------------------------------------------------------------
//  |  y\                    Errors occurred during init?                            /n
//  |------------------------------------------------------------------------------------
//  |  y\          Port opened?                               /n  |
//  |-------------------------------------------------------------| Start CAN event thread
//  | Close port (ncdClosePort)                        |          | Return TRUE
//  |-------------------------------------------------------------|
//  | Return FALSE                                                |
//  |------------------------------------------------------------------------------------
//***************************************************************************************
function TCanDriver.Connect: boolean;
var
  vErr : Vstatus;
  acc  : VsetAcceptance;
label
  error;
begin
  // reset internal variables
  FPortHandle    := INVALID_PORTHANDLE;
  FChannelMask   := 0;
  FPermissionMask:= 0;
  FThreadRunning := False;
  FEventHandle   := 0;

  //-------------------------- open the driver ------------------------------------------
  vErr := ncdOpenDriver;
  if vErr <> VSUCCESS then goto error;

  //-------------------------- select a channel -----------------------------------------
  FChannelMask := ncdGetChannelMask(HardwareTypes[FHardware], 0, Channels[FChannel]);
  if FChannelMask=0 then goto error;

  //-------------------------- open a port ----------------------------------------------
  FPermissionMask := FChannelMask;
  vErr := ncdOpenPort(FPortHandle, 'TCanDriver0', FChannelMask, FPermissionMask,
                      FPermissionMask, 1024);
  if vErr <> VSUCCESS then goto error;

  //-------------------------- set baudrate ---------------------------------------------
  if FPermissionMask<>0 then
  begin
    vErr := ncdSetChannelBitrate(FPortHandle, FPermissionMask, FBaudRate);
    if vErr <> VSUCCESS then goto error;
  end;

  //-------------------------- set the acceptance filter --------------------------------
  acc.mask := FFilterMask;
  acc.code := FFilterCode;
  if FExtendedId = True then // 29-bit id used?
  begin
    acc.mask := acc.mask or LongInt(EXT_MSG);
    acc.code := acc.code or LongInt(EXT_MSG);
  end;
  vErr := ncdSetChannelAcceptance(FPortHandle, FChannelMask, acc);
  if vErr <> VSUCCESS then goto error;

  //-------------------------- enable error frames and chipstate events -----------------
  vErr := ncdSetReceiveMode(FPortHandle, 0, 0);
  if vErr <> VSUCCESS then goto error;

  //-------------------------- create synchronisation object ----------------------------
  FEventHandle := CreateEvent(nil, FALSE, FALSE, nil);
  if FEventHandle = 0 then goto error;
  vErr := ncdSetNotification(FPortHandle, FEventHandle, 1);
  if vErr<>VSUCCESS then goto error;

  //-------------------------- reset the clock ------------------------------------------
  vErr := ncdResetClock(FPortHandle);
  if vErr <> VSUCCESS then goto error;

  //-------------------------- sync with bus --------------------------------------------
  vErr := ncdActivateChannel(FPortHandle, FChannelMask);
  if vErr <> VSUCCESS then goto error;

  //-------------------------- flush queue's --------------------------------------------
  vErr := ncdFlushReceiveQueue(FPortHandle);
  if vErr <> VSUCCESS then goto error;
  vErr := ncdFlushTransmitQueue(FPortHandle, FChannelMask);
  if vErr <> VSUCCESS then goto error;

  //-------------------------- start CAN event thread -----------------------------------
  FCanEventThread := TCanEventThread.Create(True); // create and suspend
  FCanEventThread.FreeOnTerminate := True; // auto free on termination
  FCanEventThread.Method := ProcessEvents; // set method
  FCanEventThread.FEventHndl := FEventHandle; // set event handle
  FCanEventThread.Resume; // start
  FThreadRunning := True;

  //-------------------------- success --------------------------------------------------
  Result := True; // successfully initialized the driver
  exit; // stop here

  //-------------------------- error occurred -------------------------------------------
  error:
  if FEventHandle <> 0 then
    CloseHandle(FEventHandle);
  if FPortHandle <> INVALID_PORTHANDLE then
  begin
    ncdClosePort(FPortHandle);
    FPortHandle := INVALID_PORTHANDLE;
  end;
  Result := False;
end; //*** end of Connect ***


//***************************************************************************************
// NAME:           Disconnect
// PRECONDITIONS:  none
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Disconnects the CAN driver
//
//  |------------------------------------------------------------------------------------
//  |  y\                    Connected?                                              /n
//  |------------------------------------------------------------------------------------
//  | Deactivate the channel (ncdDeactivateChannel)               |
//  | Close port (ncdClosePort)                                   |
//  |------------------------------------------------------------------------------------
//  | Close the driver (ncdCloseDriver)
//  |------------------------------------------------------------------------------------
//  |  y\                    CAN event thread active?                                /n
//  |------------------------------------------------------------------------------------
//  | Stop CAN event thread                                       |
//  |------------------------------------------------------------------------------------
//***************************************************************************************
procedure TCanDriver.Disconnect;
begin
  if IsConnected = True then begin // are we connected?
    ncdDeactivateChannel(FPortHandle, FChannelMask); // deactivate channel
    if FEventHandle <> 0 then
      CloseHandle(FEventHandle);
    ncdClosePort(FPortHandle); // close the port
    FPortHandle := INVALID_PORTHANDLE; // invalidate handle
  end;
  ncdCloseDriver; // close the driver
  if IsThreadRunning then
  begin
    FCanEventThread.FEventHndl := 0; // reset event handle
    FCanEventThread.Terminate; // stop
    FThreadRunning := False;
  end;
end; //*** end of Disconnect ***


//***************************************************************************************
// NAME:           Transmit
// PRECONDITIONS:  Driver initialized using 'Connect'
// PARAMETER:      Message: CAN message that is to be transmitted
// RETURN VALUE:   True or False for succees or error, respectively
// DESCRIPTION:    Transmits a CAN message.
//
//  |------------------------------------------------------------------------------------
//  |  y\                    Connected?                                              /n
//  |------------------------------------------------------------------------------------
//  | Transmit message using ncdTransmit           |
//  |----------------------------------------------| Return FALSE
//  | Return TRUE                                  |
//  |------------------------------------------------------------------------------------
//***************************************************************************************
function TCanDriver.Transmit( Message: TCanMsg): boolean;
var
  vErr : Vstatus;
  event : Vevent;
  cnt : integer;
begin
  // make sure the CAN driver is connected
  if not IsConnected then
  begin
    Result := False; // can't transmit it not connected
    exit; // no need to continue
  end;

  // configure message as tx with acknowledge
  event.tag := V_TRANSMIT_MSG;
  event.msg.flags := MSGFLAG_TX;

  // set the message identifier
  if Message.ext = True then
    event.msg.id := Message.id or LongInt(EXT_MSG)
  else
    event.msg.id := Message.id;

  // set the data length
  event.msg.dlc := Message.dlc;

  // store the data bytes
  for cnt :=0 to MAX_MSG_LEN-1 do
  begin
    event.msg.data[cnt] := Message.data[cnt];
  end;

  vErr := ncdTransmit(FPortHandle, FChannelMask, event);

  if vErr <> VSUCCESS then
    Result := False
  else
    Result := True;
end; //*** end of Transmit ***


//***************************************************************************************
// NAME:           ProcessEvents
// PRECONDITIONS:  thread running
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Called by the CAN event tread. This function traps and processes CAN
//                 events for OnMessage, OnBusOff, and OnErrorFrame.
//
//***************************************************************************************
procedure TCanDriver.ProcessEvents;
var
  vErr   : Vstatus;
  pEvent : PVEvent;
  msg    : TCanMsg;
begin
  while True do
  begin
    vErr := ncdReceive1(FPortHandle, pEvent);

    if (vErr<>VSUCCESS) and (vErr<>VERR_QUEUE_IS_EMPTY) then break;
    if vErr=VERR_QUEUE_IS_EMPTY then break;

    case pEvent^.tag of
      V_RECEIVE_MSG, V_TRANSMIT_MSG:
      begin
        if (pEvent^.msg.flags and MSGFLAG_ERROR_FRAME) = MSGFLAG_ERROR_FRAME then
        begin
          //---------------- process errorframe -----------------------------------------
          if Assigned( FOnErrorFrame ) then
          begin
            FOnErrorFrame( Self, pEvent^.timeStamp ); // call application's event handler
          end;
        end
        else if pEvent^.msg.flags = 0 then // msg rx indication
        begin
          //---------------- process reception indication -------------------------------
          CopyMessage(pEvent^, msg);
          if Assigned( FOnMessage ) then
          begin
            FOnMessage( Self, Rx, msg );  // call application's event handler
          end;
        end
        else if (pEvent^.msg.flags and MSGFLAG_TX) = MSGFLAG_TX then // msg tx confirmation
        begin
          //---------------- process transmission confirmation --------------------------
          CopyMessage(pEvent^, msg);
          if Assigned( FOnMessage ) then
          begin
            FOnMessage( Self, Tx, msg );  // call application's event handler
          end;
        end;
      end;
      V_CHIP_STATE:
      begin
        if (pEvent^.chipState.busStatus and CHIPSTAT_BUSOFF) = CHIPSTAT_BUSOFF then
        begin
          //---------------- process bus off event --------------------------------------
          if Assigned( FOnBusOff ) then
          begin
            FOnBusOff( Self, pEvent^.timeStamp );  // call application's event handler
          end;
        end;
      end;
    end;
  end;
end; //*** end of ProcessEvents ***


//***************************************************************************************
// NAME:           Register
// PRECONDITIONS:  none
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Registers the TCanDriver component into Borland Delphi's IDE.
//
//***************************************************************************************
procedure Register;
begin
  RegisterComponents('Feaser', [TCanDriver]);
end; //*** end of Register ***


end.
//********************************** end of CANdrvD.pas *********************************


