unit template_userstatemachine;


{$I Options.inc}

{.$DEFINE DEBUG_DISCOVER_STATEMACHINE}
{.$DEFINE TRACE_TRACTION_REPLIES}

uses
  Float16,
  opstacktypes,
  opstackdefines,
  template_node,
  opstack_api,
  nmranetdefines,
  NMRAnetCabBridgeDefines,
  NMRAnetCabBridge,
  NMRAnetXpressnet,
  nmranetutilities;

procedure UserStateMachine_Initialize;
procedure AppCallback_UserStateMachine_Process(Node: PNMRAnetNode);
procedure AppCallback_NodeInitialize(Node: PNMRAnetNode);
procedure UART_RX_StateMachine;
procedure CabBus_Timeout;

// Called every 100ms typically from another thread so only use to update flags
procedure AppCallback_Timer_1s;

// These message are called from the mainstatemachine loop.  They have been stored in
// internal storage buffers.  See the notes to understand the implications of this and how to use them correctly
procedure AppCallback_SimpleNodeInfoReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
procedure AppCallBack_ProtocolSupportReply(Node: PNMRAnetNode; AMessage: POPStackMessage);  // This could be 2 replies per call.. read docs
procedure AppCallback_RemoteButtonReply(Node: PNMRAnetNode; var Source: TNodeInfo; DataBytes: PSimpleBuffer);
{$IFDEF SUPPORT_TRACTION}
procedure AppCallback_TractionProtocol(Node: PNMRAnetNode; AMessage: POPStackMessage);
procedure AppCallback_TractionProtocolReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
procedure AppCallback_SimpleTrainNodeInfoReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
{$ENDIF}
{$IFDEF SUPPORT_TRACTION_PROXY}
function AppCallback_TractionProxyProtocol(Node: PNMRAnetNode; AMessage: POPStackMessage; SourceHasLock: Boolean): Boolean;
procedure AppCallback_TractionProxyProtocolReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
{$ENDIF}

// These messages are called directly from the hardware receive buffer.  See the notes to understand the
// implications of this and how to use them correctly
procedure AppCallback_InitializationComplete(var Source: TNodeInfo; NodeID: PNodeID);
procedure AppCallback_VerifiedNodeID(var Source: TNodeInfo; NodeID: PNodeID);
procedure AppCallback_ConsumerIdentified(var Source: TNodeInfo; MTI: Word; EventID: PEventID);
procedure AppCallback_ProducerIdentified(var Source: TNodeInfo; MTI: Word; EventID: PEventID);
procedure AppCallback_LearnEvent(var Source: TNodeInfo; EventID: PEventID);
procedure AppCallBack_PCEventReport(var Source: TNodeInfo; EventID: PEventID);

procedure AppCallBack_ConfigMemReadReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean);
procedure AppCallBack_ConfigMemStreamReadReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean);
procedure AppCallBack_ConfigMemWriteReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean);
procedure AppCallBack_ConfigMemStreamWriteReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean);

  procedure Hardware_EnableInterrupts; external;
  procedure Hardware_DisableInterrupts; external;

const
  FUNCTION_HORN = 1;
  FUNCTION_BELL = 2;

const
  CONFIG_OFFSET_SPEED_STEP     = 128;
  CONFIG_OFFSET_ADDRESS_TYPE   = 129;
  

var
  CabBus_RS485_Select              : sbit; sfr; external;
  CabBus_RS485_Select_Direction    : sbit; sfr; external;

var
  GlobalTimer_1s: Word;
  RS485_Watchdog_1s: Byte;
  
implementation

const
  STATE_RS485_READ_HEADER_BYTE  = 0;  // State machine states for the RS485 receiver
  STATE_RS485_READ_MESSAGE_BYTE = 1;
  STATE_RS485_READ_XOR_BYTE     = 2;
  STATE_RS485_FULL              = 3;

const
  STATE_CAB_IDLE             = 1;
  STATE_CAB_SELECT_LOCO      = 2;
  STATE_CAB_RUN_MACRO        = 3;
  STATE_CAB_RUN_CMD          = 4;
  STATE_CAB_CLEAR_MSG        = 5;

// *****************************************************************************
// Called from the UART RX Interrupt
// The Pin Change Interrupt stopped the Timeout Timer so we are free to handle
// the UART RX at our leasure
// *****************************************************************************
procedure UART_RX_StateMachine;
var
  ReceivedByte, ErrorByte: Byte;
  i: Integer;
begin
  while (URXDA_U2STA_bit = 1) do
  begin
    ReceivedByte := U2RXREG;
    case CabBridge.iIncomingStateMachine of
      STATE_RS485_READ_HEADER_BYTE :
        begin
          CabBridge.IncomingStarted := True;
          CabBridge.iIncomingCount := ReceivedByte;                             // Use only the lower nibble for the count, upper is instruction
          CabBridge.iIncomingByteIndex := 0;
          if CabBridge.iIncomingCount and $0F = 0 then                          // If the count is 0 then jump to the XOR byte state, only the lower nibble is the couny
            CabBridge.iIncomingStateMachine := STATE_RS485_READ_XOR_BYTE
          else
            CabBridge.iIncomingStateMachine := STATE_RS485_READ_MESSAGE_BYTE;
        end;
      STATE_RS485_READ_MESSAGE_BYTE :
        begin
          CabBridge.IncomingBuffer[CabBridge.iIncomingByteIndex] := ReceivedByte;
          Inc(CabBridge.iIncomingByteIndex);
          if CabBridge.iIncomingByteIndex >= (CabBridge.iIncomingCount and $0F) then
            CabBridge.iIncomingStateMachine := STATE_RS485_READ_XOR_BYTE;
        end;
      STATE_RS485_READ_XOR_BYTE :
        begin
          ErrorByte := CabBridge.iIncomingCount;
          for i := 0 to (CabBridge.iIncomingCount and $0F) - 1 do
            ErrorByte := ErrorByte xor CabBridge.IncomingBuffer[i];
          if ErrorByte <> ReceivedByte then
            CabBridge.iStateMachine := STATE_SUB_BRIDGE_NEXT_CAB                // Throw it away
          else
            CabBridge.iStateMachine := STATE_SUB_BRIDGE_CAB_REPLIED;            // Tell cab what the message was
          CabBridge.iIncomingStateMachine := STATE_RS485_READ_HEADER_BYTE;      // Ready for the next
        end;
    end
  end;
end;

// *****************************************************************************
// Called from Dynamically set Interrupt timer after 1800us
// *****************************************************************************
procedure CabBus_Timeout;
begin
  // The Ping timed out without a reply from the Cab move to the next state (depends of which wait state we are in)
  DisableCabBusTimer;
 
  if RIDLE_U2STA_bit = 0 then Exit;
  if CabBridge.IncomingStarted then Exit;
  if URXDA_U2STA_bit = 1 then Exit;

  case CabBridge.iStateMachine of
    STATE_SUB_BRIDGE_WAIT_FOR_RESPONSE : begin {UART1_Write_Text('Timed out'+LF); }CabBridge.iStateMachine := STATE_SUB_BRIDGE_TIMEOUT; end;
  end;
  
end;

// *****************************************************************************
// *****************************************************************************
function DispatchMessage(CabNode: PNMRAnetNode): Boolean;
var
  i: Integer;
  Cab: PCab;
  DataCount, Instruction: Byte;
begin     
  Result := True;                                                               // Assume we are moving on   
  if CabNode <> nil then
  begin
    Cab := PCab( PByte( CabNode^.UserData));
    DataCount := CabBridge.iIncomingCount and $0F;
    Instruction := CabBridge.iIncomingCount and $F0;
    // Divide and Conquer
    case Instruction of       // Extract the Instruction
      %00100000:  // 0010 xxxx {$2x}
        begin
          case DataCount of    // Extract the Count
            1: begin
                 case CabBridge.IncomingBuffer[0] of
                   %10000001 (*$81*): Result := ResumeOperationsRequest(CabNode);                       // 2.2.2  Resume operations request
                   %10000000 (*$80*): Result := StopOperationsRequest(CabNode);                         // 2.2.3  Stop operations request (emergency off)
                   %00010000 (*$10*): Result := ServiceModeResultsRequest(CabNode);                              // 2.2.10 Request for Service Mode results;
                   %00100001 (*$21*): Result := CommandStationSoftwareVersionRequest(CabNode); // 2.2.14 Command station software-version request
                   %00100100 (*$24*): Result := CommandStationStatusRequest(CabNode)           // 2.2.15 Command station status request
                 else
                   Result := Send_InstructionNotSupported(CabNode);
                 end;
               end;
            2: begin
                 case CabBridge.IncomingBuffer[0] of
                   %00010001 (*$11*): Result := RegisterModeReadRequest(CabNode);      // 2.2.7  Register Mode read request (Register Mode)
                   %00010101,(*$15*)                                                                   // 2.2.8  Direct Mode CV read request (CV mode)
                   %00011000,(*$18*)                                                                   // 4-Byte-Format (CV 1-255 und CV1024) (v3.6)
                   %00011001,(*$19*)                                                                   // 4-Byte-Format (CV 256-511)) (v3.6)
                   %00011010,(*$1A*)                                                                   // 4-Byte-Format (CV 512-767) (v3.6)
                   %00011011 (*$1B*): Result := DirectModeReadRequest(CabNode);        // 4-Byte-Format (CV 768-1023) (v3.6)
                   %00010100 (*$14*): Result := PagedModeReadRequest(CabNode);         // 2.2.9  Paged Mode read request (Paged Mode)
                   %00100010 (*$22*): Result := SetCommandStationPowerUpMode(CabNode)          // 2.2.16 Set command station power-up mode
                 else
                   Result := Send_InstructionNotSupported(CabNode);
                 end;
               end;
            3: begin
                 case CabBridge.IncomingBuffer[0] of
                   %00010010 (*$12*): Result := RegisterModeWriteRequest(CabNode);     // 2.2.11 Register Mode write request (Register Mode)
                   %00011100,(*$16*)                                                                   // 2.2.12 Direct Mode write request (CV Mode) (CV 1-255) and 256)
                   %00011111,(*$1C*)                                                                   // 4-Byte-Format (CV 1-255 and CV1024) (v3.6)
                   %00011110,(*$1D*)                                                                   // 4-Byte-Format (CV 256-511)) (v3.6)
                   %00011101,(*$1E*)                                                                   // 4-Byte-Format (CV 512-767) (v3.6)
                   %00010110 (*$1F*): Result := DirectModeWriteRequest(CabNode);       // 4-Byte-Format (CV 768-1023) (v3.6)
                   %00010111 (*$17*): Result := PagedModeWriteRequest(CabNode)         // 2.2.13 Register Mode write request (Paged Mode)
                 else
                   Result := Send_InstructionNotSupported(CabNode);
                 end;
               end
            else
              Result := Send_InstructionNotSupported(CabNode);
          end;
        end;
      %10000000:  // 1000 xxxx {$8x}
        begin
          case DataCount of                                // Extract the Count
            0: Result := StopAllLocomotivesRequest(CabNode)                                   // 2.2.4  Stop all locomotives request (emergency stop)
          else
            Result := Send_InstructionNotSupported(CabNode);
          end
        end;
      %10010000:  // 1001 xxxx {$9x}
        begin
          case DataCount of
            1: Result := EmergencyStopLocomotiveRequestV2_Down(CabNode);                       // 2.2.5.1 Emergency stop a locomotive (X-Bus V1 and V2)
            2: Result := EmergencyStopLocomotiveRequestV3(CabNode)                             // 2.2.5.2 Emergency stop a locomotive (XpressNet)
          else
            Result := Send_InstructionNotSupported(CabNode);
          end;
        end;
      %10100000:  // 1010 xxxx {$Ax}
        begin
          case DataCount of
            1: Result := LocomotiveInformationRequestV1(CabNode);                                                // 2.2.19.1 Locomotive information requests (X-Bus V1)
            2: Result := LocomotiveInformationRequestV2(CabNode)                                                 // 2.2.19.2 Locomotive information requests (X-Bus V1 and V2)
          else
            Send_InstructionNotSupported(CabNode);
          end;
        end;
      %11100000:  // 1110 xxxx {$Ex}
        begin
          case DataCount of
            3: begin
                 case CabBridge.IncomingBuffer[0] of
                   %00000000 (*$00*): Result := LocomotiveInformationRequestV3(CabNode);       // 2.2.19.3 Locomotive information requests (XpressNet only) [QUERIES the Function on/off State state (F0-F12)]
                   %00000111 (*$07*): Result := FunctionStatusRequest(CabNode);                // 2.2.19.4 Function momentary/continious status request (XpressNet only)  [QUERIES the momentary or on/off state] [QUERIES the momentary or on/off state (F0-F12)]
                   %00001000 (*$08*): Result := FunctionStateRequestEx(CabNode);               // 2.2.19.5 Function momentary/continious status request (XpressNet only v3.6; 2.2.25.2 in the German document)    [QUERIES the momentary or on/off state (F13-F28)]
                   %00001001 (*$09*): Result := FunctionOperationRequestEx(CabNode);           // 2.2.19.6 Function on/off status request (XpressNet only v3.6; 2.2.25.3 in the German document)    [QUERIES the Function State state (F13-F28)]
                   %00000101, %00000110 (*$05, $06*): Result := AddressInquiryLocoStack(CabNode, CabBridge.IncomingBuffer[0] = %00000110);  // 2.2.25.3 Address inquiry locomotive at command station stack request
                   %01000100 (*$68*): Result := AddressInquiryLocoDeleteFromStack(CabNode)     // 2.2.26 Delete locomotive from command station stack request
                 else
                   Result := Send_InstructionNotSupported(CabNode);
                 end;
               end;
            4: begin
                 case CabBridge.IncomingBuffer[0] of
                   %00010000, (*$10*) // 14 Step
                   %00010001, (*$11*) // 27 Step
                   %00010010, (*$12*) // 28 Step
                   %00010011: (*$13*) // 128 Step
                     Result := LocomotiveOperationsRequest(CabNode);                   // 2.2.20.3  Format - Speed and direction instruction
                   %00100000, (*$20*)   // Set Function Operation on Group 1 ( on/off )
                   %00100001, (*$21*)   // Set Function Operation on Group 1 ( on/off )
                   %00100010, (*$22*)   // Set Function Operation on Group 1 ( on/off )
                   %00100011, (*$23*)   // Set Function Operation on Group 1 ( on/off )
                   %00101000: (*$28*)   // Set Function Operation on Group 1 ( on/off )
                      Result := FunctionOperationRequest(CabNode);                          // 2.2.20.4 Format - Function instruction group 1-5:  [SETS the momentary or on/off state]
                   %00100100, (*$24*)  // Set Function State on Group 1 ( momentary/continious )
                   %00100101, (*$25*)  // Set Function State on Group 2 ( momentary/continious )
                   %00100110, (*$26*)  // Set Function State on Group 3 ( momentary/continious )
                   %00100111, (*$27*)  // Set Function State on Group 4 ( momentary/continious )
                   %00101100: (*$2C*)  // Set Function State on Group 5 ( momentary/continious )
                     Result := SetFunctionStateRequest(CabNode);                               // 2.2.20.5 Format - Set Function state group 5: (XpressNet only v3.6; 2.2.26.4 in the German document) [SETS the Function State]
                   %01000000, %01000001 (*$40, $41*): AddLocomotiveToMU_Request(CabNode, CabBridge.IncomingBuffer[0] = %01000001);  // 2.2.24.1 Add a locomotive to a multi-unit request [SETS the Function State]
                   %01000010 (*$42*): Result := RemoveLocomotiveFromMU_Request(CabNode);                    // 2.2.24.2 Remove a locomotive from a Multi-unit request
                   %00000001, %00000010: Result := AddressInquiryOfMember_MU_Request(CabNode, CabBridge.IncomingBuffer[0] = %00000010);       // 2.2.25.1 Address inquiry member of a Multi-unit request
                   %00000011, %00000100: Result := AddressInquiryOf_MU_Request(CabNode, CabBridge.IncomingBuffer[0] = %00000100)              // 2.2.25.2 Address inquiry Multi-unit request
                 else
                   Result := Send_InstructionNotSupported(CabNode);
                 end;
               end;
            5: begin
                 case CabBridge.IncomingBuffer[0] of
                   %01000011 (*$43*):
                     begin
                       if (CabBridge.IncomingBuffer[3] = 0) and (CabBridge.IncomingBuffer[4] = 0) then
                         Result := DissolveDoubleHeaderV3(CabNode)            // 2.2.22.2 Dissolve Double Header
                       else
                         Result := EstablishDoubleHeaderV3(CabNode)           // 2.2.22.1 Establish Double Header
                     end;
                   %00101111 (*$2F*): FunctionRefreshMode(CabNode) // ?????? ?????  (XpressNet only v3.6; 2.2.26.5 in the German document)
                 else
                   Result := Send_InstructionNotSupported(CabNode);
                 end;
               end;
            6: begin
                 case CabBridge.IncomingBuffer[0] of
                   %00110000 (*$30*): Result := OperationsModeRequest(CabNode) // 2.2.23.1  Operations Mode Programming
                   else
                    Result :=  Send_InstructionNotSupported(CabNode);
                 end;
               end
            else
              Result := Send_InstructionNotSupported(CabNode);
          end;
        end;
      %10110000:  // 1011 xxxx {$Bx}
        begin
          case DataCount of
            3: Result := LocomotiveOperationRequestV1(CabNode);            // 2.2.20.1 Locomotive operations (X-Bus V1)
            4: Result := LocomotiveOperationRequestV2(CabNode)             // 2.2.20.2 Locomotive operations (X-Bus V2)
          else
            Result := Send_InstructionNotSupported(CabNode);
          end;
        end;
      %01000000:  // 0100 xxxx {$4x}
        begin
          case DataCount of
            2: begin
                 Result := AccessoryInformationRequest(CabNode);           // 2.2.17 Accessory Decoder information request
                 Result := AccessoryOperationRequest(CabNode)              // 2.2.18 Accessory Decoder operation request
               end
          else
            Result := Send_InstructionNotSupported(CabNode);
          end;
        end;
      %11000000:  // 1100 xxxx {$Cx}
        begin
          case DataCount of
            3: begin
                 case CabBridge.IncomingBuffer[0] of
                   %00000101: Result := EstablishDoubleHeaderV2(CabNode);  // 2.2.21.1 Establish Double Header
                   %00000100: Result := DisolveDoubleHeaderV2(CabNode)     // 2.2.21.2 Dissolve Double Header
                 else
                   Result := Send_InstructionNotSupported(CabNode);
                 end;
               end
            else
              Result := Send_InstructionNotSupported(CabNode);
          end;
        end;
      %11110000:  // 1111 xxxx {$Fx}   // Sent by the PC interface for information about the PC to XpressBus interface (LI101F)
        begin
          case DataCount of
            0: PC_Interface_VersionNumber(CabNode);              // 1.5.4 Determining the Version number of the LI100F and LI101
            2: begin
                 case CabBridge.IncomingBuffer[0] of
                   1: Result := PC_Interface_SetAddress(CabNode);                 // 1.5.5 Determing and changing the XpressNet address for the LI101
                   2: Result := PC_Interface_SetBaudRate(CabNode)                 // 1.5.6 Determing and changing the Baud Rate for the LI101
                 else
                   Result := Send_InstructionNotSupported(CabNode);
                 end;
              end
           else
             Result := Send_InstructionNotSupported(CabNode);
           end;
        end
      else
        Result := Send_InstructionNotSupported(CabNode);
    end
  end
end;

// *****************************************************************************
//  procedure UserStateMachine_Initialize
//     Parameters: : None
//     Returns     : None
//     Description : Called once when the library is starting.  Use to initalize
//                   variables, etc
// *****************************************************************************
procedure UserStateMachine_Initialize;
begin
  NMRAnetCabBridge_Initialize;
  GlobalTimer_1s := 0;
end;

// *****************************************************************************
//  procedure AppCallback_UserStateMachine_Process
//     Parameters: : None
//     Returns     : None
//     Description : Called as often as possible to run the user statemachine
// *****************************************************************************
procedure AppCallback_UserStateMachine_Process(Node: PNMRAnetNode);
var
  i: Integer;
  CabData: PCab;
  Address: Word;
  SpeedStep, AddressType: Byte;
  NewSpeed: THalfFloat;
begin
  if Node = GetPhysicalNode then
  begin
    case Node^.iUserStateMachine of
      STATE_BRIDGE_USER_START :  // Create the minimum number of Pings to put on the NCE bus to make it happy
          begin
            if Node^.State and NS_PERMITTED <> 0 then
            begin
              GlobalTimer_1s := 0;
              if TrySendIdentifyProducer(Node^.Info, @EVENT_IS_PROXY) then
                Node^.iUserStateMachine := STATE_BRIDGE_FIND_PROXY;
            end;
            Exit;
          end;
      STATE_BRIDGE_FIND_PROXY :   // Find the Proxy node (Command Station) on the network before progressing
          begin
            if (Node^.TrainData.ControllerLink.AliasID > 0) or (Node^.TrainData.ControllerLink.ID[0] > 0) or (Node^.TrainData.ControllerLink.ID[1] > 0) then
              Node^.iUserStateMachine := STATE_BRIDGE_CREATE_REQUIRED_CABS
            else begin
              if GlobalTimer_1s > 1 then
                Node^.iUserStateMachine := STATE_BRIDGE_USER_START                     // Try again
            end;
            Exit;
          end;
      STATE_BRIDGE_CREATE_REQUIRED_CABS :
          begin
            for i := ID_MIN_DEVICE_XPRESSNET to ID_MIN_DEVICE_XPRESSNET + XPRESSNET_CAB_BUS_PADDING - 1 do
              CreateCab(i);                                                     // Build and assign cabs 2 - N so make the throttle hardware happy
            Node^.iUserStateMachine := STATE_BRIDGE_POLL_CABS;
            Exit;
          end;
      STATE_BRIDGE_POLL_CABS :
          begin
            case CabBridge.iStateMachine of   // I could use the Train Data machine for this if I wanted
              STATE_SUB_BRIDGE_INITIALIZE :
                  begin {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_INITIALIZE'+LF); {$ENDIF}

     LATB0_bit := 1;    // NICE SYNC PIN
                     
                      CabBridge.Discovering := False;
               {
                    if (CabBridge.iAssignedCabCount = 0) or (CabBridge.DiscoverTimer >= REDISCOVERY_TIME) then
                    begin
                      CabBridge.Discovering := True;
                      CabBridge.DiscoverTimer := 0;
                    end;
              }
                    if CabBridge.Discovering then
                      CabBridge.CurrentCabID := ID_MIN_DEVICE_XPRESSNET
                    else begin
                      CabBridge.iAssignedCab := 0;
                      CabBridge.CurrentCabID := PCab( CabBridge.AssignedCabs[0]^.UserData)^.ID;  // One must exist if we get to here, see above test
                    end;
                    CabBridge.iStateMachine := STATE_SUB_BRIDGE_SYNC_TRANSMIT_BYTE;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_SYNC_TRANSMIT_BYTE :
                  begin {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_SYNC_TRANSMIT_BYTE..............'+LF); {$ENDIF}
                  
     LATB0_bit := 0;    // NICE SYNC PIN
     
                    // Need a final test to make sure any existing Cab Nodes have fully
                    // booted or have been taken off line before pinging them
                    // ExistingCab is used further down so don't modify it
                    CabBridge.ExistingCab := FindCab(CabBridge.CurrentCabID);  // this may return null when discovering!!!!
                    
                    if CabNodeAssignedAndPermitted(CabBridge.ExistingCab) then
                    begin
                      // Select the 485 chip to transmit
                      CabBus_RS485_Select := 1;
                      Delay_us(10);
                      // Setup Pin Change Interrupt for the Receiver Pin
                      CabBridge.LastPortRead := PortB;
                      CNIF_bit := 0;
                      CNIE_bit := 1;                                               // Pin Change Interrupt enable
                      // Setup the incoming message buffer
                      CabBridge.iIncomingStateMachine := 0;
                      CabBridge.iIncomingCount := 0;
                      CabBridge.iIncomingByteIndex := 0;
                      CabBridge.IncomingStarted := False;

                      U2TXIE_bit := 0;                                            // Setup the Transmit End Interrupt
                      U2TXREG := $0100 or InsertXpressnetHiBitParity(CALLBYTE_INQUIRY_XPRESSNET or CabBridge.CurrentCabID);
                      UTXEN_bit := 1;
                      U2TXIF_bit := 0;
                      U2TXIE_bit := 1;

                      U2RXIF_Bit         := 0;                                    // Enable the RX Interrupt
                      U2RXIE_bit         := 1;
                      CabBridge.iStateMachine := STATE_SUB_BRIDGE_SYNC_WAITFOR_HARDWARE_BUFFER_EMPTY;  // Wait until the last byte is transmitted
                    end else
                      CabBridge.iStateMachine := STATE_SUB_BRIDGE_NEXT_CAB;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_SYNC_WAITFOR_HARDWARE_BUFFER_EMPTY :
                  begin
                    if (U2TXIF_bit = 1) then // The last byte was sent setup the wait period (plus or minus)
                    begin {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_SYNC_WAITFOR_HARDWARE_BUFFER_EMPTY..............'+LF); {$ENDIF}
                      EnableCabBusTimer(2880);      // 31.25ns * 2880 = 90us      Need delay, the cabs are slow microprocessors
                      RS485_Watchdog_1s := 0;
                      CabBridge.iStateMachine := STATE_SUB_BRIDGE_WAIT_FOR_RESPONSE;
                    end;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_WAIT_FOR_RESPONSE :
                  begin {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_WAIT_FOR_RESPONSE'+LF); {$ENDIF}
                    // Waiting for the Timer to expire or we detect a reply....
                    // See CabBus_Timeout function for the Timeout or CabBus_UART_RX_StateMachine for a reply and what the next state is
                    if RS485_Watchdog_1s > TIMEOUT_MESSAGE_REPLY_WAIT then                                  // Something is corrupted or hung
                    begin
                       CabBridge.iIncomingStateMachine :=  STATE_RS485_READ_HEADER_BYTE;
                       CabBridge.iStateMachine := STATE_SUB_BRIDGE_TIMEOUT;
                    end;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_TIMEOUT :
                  begin {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_TIMEOUT'+LF); {$ENDIF}
                    // Need to search the Allocated Cab List to find the CabBridge.iDiscoveryCabID matching Node to pull this
                    CabBridge.iStateMachine := STATE_SUB_BRIDGE_NEXT_CAB;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_CAB_REPLIED :
                  begin   {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_CAB_REPLIED'+LF); {$ENDIF}
                    if CabBridge.ExistingCab = nil then                         // Loaded earlier to test if an existing node was Permited before pinging
                    begin
                      CreateCab(CabBridge.CurrentCabID);                        // Need to wait until it is in the Permitted state
                      CabBridge.iStateMachine := STATE_SUB_BRIDGE_NEXT_CAB
                    end else
                    begin
                      CabBridge.iOutGoingCount := 0;
                      PCab( CabBridge.ExistingCab^.UserData)^.iStateMachine := 0;
                      CabBridge.iStateMachine := STATE_SUB_BRIDGE_DISPATCH_MESSAGE
                    end;
     LATB4_bit := 1;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_DISPATCH_MESSAGE :
                  begin {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_DISPATCH_MESSAGE'+LF); {$ENDIF}
                    if DispatchMessage(CabBridge.ExistingCab) then              // Need to dispatch to succeed to move on, else spin here until done
                    begin
                    
   LATB4_bit := 0;
                    
                      if CabBridge.iOutGoingCount > 0 then                      // See if there is anything to send
                      begin
                        CabBridge.iOutGoingByteIndex := -1;
                        CabBus_RS485_Select := 1;
                        CabBridge.iStateMachine := STATE_SUB_BRIDGE_PING_TRANSMIT_BYTE;
                      end else
                        CabBridge.iStateMachine := STATE_SUB_BRIDGE_NEXT_CAB;
                    end;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_PING_TRANSMIT_BYTE :
                  begin {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_PING_TRANSMIT_BYTE..............'+LF); {$ENDIF}
                    if TRMT_U2STA_bit = 1 then
                    begin
                      if CabBridge.iOutGoingByteIndex < 0 then
                          U2TXREG := $0100 or InsertXpressnetHiBitParity(CALLBYTE_RESPONSE_XPRESSNET or PCab( CabBridge.ExistingCab^.UserData)^.ID)  // Call Byte
                        else
                          U2TXREG := CabBridge.OutGoingBuffer[CabBridge.iOutGoingByteIndex];
                        Inc(CabBridge.iOutGoingByteIndex);
                    end;

                    if CabBridge.iOutGoingByteIndex >= (CabBridge.iOutGoingCount and $0F) then
                    begin
                      // Special case if we are sending the last byte.  Need to setup the interrupt to catch the last byte out
                      U2TXIF_bit := 0;
                      U2TXIE_bit := 1;
                      CabBridge.iStateMachine := STATE_SUB_BRIDGE_PING_WAITFOR_HARDWARE_BUFFER_EMPTY;  // Wait until the last byte is transmitted
                    end;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_PING_WAITFOR_HARDWARE_BUFFER_EMPTY :
                  begin
                    if (U2TXIF_bit = 1) then // The last byte was sent setup the wait period (plus or minus)
                    begin {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_PING_WAITFOR_HARDWARE_BUFFER_EMPTY..............'+LF); {$ENDIF}
                      CabBridge.iStateMachine := STATE_SUB_BRIDGE_NEXT_CAB;
                    end;
                    Exit;
                  end;
              STATE_SUB_BRIDGE_NEXT_CAB :
                  begin
                  
                  LATB4_bit := 0;
                  
                    {$IFDEF DEBUG_DISCOVER_STATEMACHINE} UART1_Write_Text('STATE_SUB_BRIDGE_NEXT_CAB'+LF); {$ENDIF}
                    if CabBridge.Discovering then
                    begin
                      Inc(CabBridge.CurrentCabID);
                      if CabBridge.CurrentCabID > ID_MAX_DEVICE_XPRESSNET then
                      begin
                        CabBridge.Discovering := False;
                        CabBridge.DiscoverTimer := 0;
                        CabBridge.iStateMachine := STATE_SUB_BRIDGE_INITIALIZE;       // Start over and send the Sync again
                      end else
                        CabBridge.iStateMachine := STATE_SUB_BRIDGE_SYNC_TRANSMIT_BYTE;   // Ping the next one
                    end else
                    begin
                      Inc(CabBridge.iAssignedCab);
                      if CabBridge.iAssignedCab >= CabBridge.iAssignedCabCount then
                        CabBridge.iStateMachine := STATE_SUB_BRIDGE_INITIALIZE      // Start over and send the Sync again
                      else begin
                        CabBridge.CurrentCabID := PCab( CabBridge.AssignedCabs[CabBridge.iAssignedCab]^.UserData)^.ID;  // One must exist if we get to here, see above test
                        CabBridge.iStateMachine := STATE_SUB_BRIDGE_SYNC_TRANSMIT_BYTE;  // Ping the next known one
                      end;
                    end;
                    Exit;
                  end;
            end;
            Exit;
          end;
    end;
  end else
  begin
    // Cab Nodes, These states are entered by key presses and set by the Physical
    // node's interaction with the Cab Bus
    CabData := PCab( Node^.UserData);
    case Node^.iUserStateMachine of
      STATE_BRIDGE_USER_START :
          begin
            if Node^.State and NS_PERMITTED <> 0 then
            begin
              Node^.TrainData.ControllerLink := NodePool.Pool[0].TrainData.ControllerLink;  // Proxy Node
              Node^.iUserStateMachine := STATE_CAB_IDLE;
            end;
            Exit;
          end;
      STATE_CAB_IDLE :
          begin
            // Nothing going on....
            // All the work in Xpressnet is done during the Ping of the Throttle by
            // the Bridge Node
            Exit;
          end;
    end;
  end;
end;

// *****************************************************************************
//  procedure AppCallback_NodeInitialize
//     Parameters: : Node : Pointer to the node that needs to be initilized to its intial value
//     Returns     : None
//     Description : Typically called when a node is being intialized to be
//                   logged into the network.  It is possible the node can be
//                   discarded then reused so it may be called more than once for
//                   virtual nodes
// *****************************************************************************
procedure AppCallback_NodeInitialize(Node: PNMRAnetNode);
begin
  // Assign the user data record to the Node for future use
   Node^.UserData := @CabArray[Node^.iIndex];
   Node^.iUserStateMachine := STATE_BRIDGE_USER_START;

   // Initialize the data, every time the node is reused!
   ZeroizeNceCabData( PCab (Node^.UserData))
end;

{$IFDEF SUPPORT_TRACTION}
// *****************************************************************************
//  procedure AppCallback_TractionControlReply
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   Dest   : Full Node ID (and Alias if on CAN) of the dest node for the message
//                   DataBytes: pointer to the raw data bytes
//     Returns     : None
//     Description : Called when a Traction Protocol request comes in
// *****************************************************************************
procedure AppCallback_TractionProtocol(Node: PNMRAnetNode; AMessage: POPStackMessage);
var
 MultiFrameBuffer: PMultiFrameBuffer;
begin
  MultiFrameBuffer := PMultiFrameBuffer( PByte( AMessage^.Buffer));
  case MultiFrameBuffer^.DataArray[0] of
    TRACTION_CONTROLLER_CONFIG :
        begin
          case MultiFrameBuffer^.DataArray[1] of
            TRACTION_CONTROLLER_CONFIG_NOTIFY :
                begin

                end;
          end;
        end;
  end;
end;

// *****************************************************************************
//  procedure AppCallback_TractionProtocolReply
//     Parameters: : Node           : Pointer to the node that the traction protocol has been called on
//                   ReplyMessage   : The Reply Message that needs to be allocated, populated and returned so it can be sent
//                   RequestingMessage    : Message that was sent to the node containing the requested information
//     Returns     : True if the RequestingMessage is handled and the ReplyMessage is ready to send
//                   False if the request has not been completed due to no available buffers or waiting on other information
//     Description : Called in response to a Traction Protcool request
// *****************************************************************************
procedure AppCallback_TractionProtocolReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
var
 MultiFrameBuffer: PMultiFrameBuffer;
 CabData: PCab;
 FunctionAddress, Mask: DWord;
 FunctionValue: Word;
begin
  MultiFrameBuffer := PMultiFrameBuffer( PByte( AMessage^.Buffer));
  CabData := PCab( Node^.UserData);
  case MultiFrameBuffer^.DataArray[0] of
    TRACTION_QUERY_SPEED :
        begin    {$IFDEF TRACE_TRACTION_REPLIES} UART1_Write_Text('TRACTION_QUERY_SPEED' + LF); {$ENDIF}
          Node^.TrainData.SpeedDir := Word(MultiFrameBuffer^.DataArray[1] shl 8) or Word(MultiFrameBuffer^.DataArray[2]);
          
          if CabData^.QueryType = QUERY_FOR_ALLOCATION then
          begin
            CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_SEND_TRACTION_QUERY_FUNCTIONS
          end else
          begin
            CabData^.iSubStateMachine := STATE_CAB_DONE_QUERY                   // Default is to end the substatemachine
          end;
        end;
    TRACTION_QUERY_FUNCTION :
        begin  {$IFDEF TRACE_TRACTION_REPLIES}UART1_Write_Text('TRACTION_QUERY_FUNCTION' + LF);{$ENDIF}
          FunctionAddress := DWord(MultiFrameBuffer^.DataArray[1] shr 16) or DWord(MultiFrameBuffer^.DataArray[2] shr 8) or DWord(MultiFrameBuffer^.DataArray[3]);
          FunctionValue := Word(MultiFrameBuffer^.DataArray[4] shr 8) or Word(MultiFrameBuffer^.DataArray[5]);

          Mask := $00000001;
          Mask := Mask shl FunctionAddress;

          if FunctionValue = 0 then
            Node^.TrainData.Functions := Node^.TrainData.Functions and not Mask
          else
            Node^.TrainData.Functions := Node^.TrainData.Functions or Mask;
            
          if CabData^.QueryType = QUERY_FOR_ALLOCATION then
          begin
            if CabData^.iQueryFunction < 28 then
            begin
              CabData^.iQueryFunction := FunctionAddress + 1;
              CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_SEND_TRACTION_QUERY_FUNCTIONS;
            end else
              CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_SEND_TRACTION_MANAGE_UNLOCK
          end else
          begin
            CabData^.iSubStateMachine := STATE_CAB_DONE_QUERY                   // Default is to end the substatemachine
          end;
        end;
    TRACTION_CONTROLLER_CONFIG :
        begin {$IFDEF TRACE_TRACTION_REPLIES}UART1_Write_Text('TRACTION_CONTROLLER_CONFIG' + LF);{$ENDIF}
          case MultiFrameBuffer^.DataArray[1] of
            TRACTION_CONTROLLER_CONFIG_ASSIGN :
                begin
                  if MultiFrameBuffer^.DataArray[2] = TRACTION_CONTROLLER_ASSIGN_REPLY_OK then
                    CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_SEND_TRACTION_QUERY_SPEED
                  else
                    CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_GENERIC_TIMEOUT_PROXY_UNLOCK   // Can't reserve now go back to normal polling
                end;
          end;
        end;
    TRACTION_CONSIST :
        begin  {$IFDEF TRACE_TRACTION_REPLIES}UART1_Write_Text('TRACTION_CONSIST' + LF);{$ENDIF}
          case MultiFrameBuffer^.DataArray[1] of
            TRACTION_CONSIST_ATTACH :
                begin
                end;
            TRACTION_CONSIST_DETACH :
                begin
                end;
            TRACTION_CONSIST_QUERY :
                begin
                end;
          end // case
        end;
    TRACTION_MANAGE :
        begin  {$IFDEF TRACE_TRACTION_REPLIES}UART1_Write_Text('TRACTION_MANAGE' + LF); {$ENDIF}
          case MultiFrameBuffer^.DataArray[1] of
            TRACTION_MANAGE_RESERVE :
                begin
                  if MultiFrameBuffer^.DataArray[2] = TRACTION_MANAGE_RESERVE_REPLY_OK then
                    CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_SEND_TRACTION_ASSIGN_CONTROLLER
                  else
                    CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_GENERIC_TIMEOUT_PROXY_UNLOCK   // Can't reserve now go back to normal polling
                end;
          end
        end;
    end;
end;
{$ENDIF}

{$IFDEF SUPPORT_TRACTION_PROXY}
// *****************************************************************************
//  procedure AppCallback_TractionProtocol
//     Parameters: : Node           : Pointer to the node that the traction protocol has been called on
//                   ReplyMessage   : The Reply Message that needs to be allocated, populated and returned so it can be sent
//                   RequestingMessage    : Message that was sent to the node containing the requested information
//     Returns     : True if the RequestingMessage is handled and the ReplyMessage is ready to send
//                   False if the request has not been completed due to no available buffers or waiting on other information
//     Description : Called when a Traction Protocol message is received
// *****************************************************************************
function AppCallback_TractionProxyProtocol(Node: PNMRAnetNode; AMessage: POPStackMessage; SourceHasLock: Boolean): Boolean;
begin
  Result := False;
end;

// *****************************************************************************
//  procedure AppCallback_TractionProxyProtocolReply
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   Dest   : Full Node ID (and Alias if on CAN) of the dest node for the message
//                   DataBytes: pointer to the raw data bytes
//     Returns     : None
//     Description : Called in response to a Traction Proxy request
// *****************************************************************************
procedure AppCallback_TractionProxyProtocolReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
var
  MultiFrameBuffer: PMultiFrameBuffer;
  CabData: PCab;
  i: Integer;
begin
  MultiFrameBuffer := PMultiFrameBuffer( PByte(AMessage^.Buffer));
  CabData := PCab( Node^.UserData);
  case AMessage^.Buffer^.DataArray[0] of
    TRACTION_PROXY_MANAGE :
        begin {$IFDEF TRACE_TRACTION_REPLIES}UART1_Write_Text('TRACTION_PROXY_MANAGE' + LF);{$ENDIF}
          if AMessage^.Buffer^.DataArray[1] = TRACTION_PROXY_MANAGE_RESERVE then
          begin
             if AMessage^.Buffer^.DataArray[2] = 0 then
               CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_SEND_PROXY_ALLOCATE         // Move to next state after reserving
             else
               CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_GENERIC_TIMEOUT_PROXY_UNLOCK   // Can't reserve now go back to normal polling
          end;
          Exit;
        end;
    TRACTION_PROXY_ALLOCATE :
        begin {$IFDEF TRACE_TRACTION_REPLIES}UART1_Write_Text('TRACTION_PROXY_ALLOCATE' + LF);{$ENDIF}
          Node^.TrainData.LinkedNode.AliasID := (MultiFrameBuffer^.DataArray[11] shl 8) or (MultiFrameBuffer^.DataArray[12]);
          NMRAnetUtilities_Load48BitNodeIDWithSimpleData(Node^.TrainData.LinkedNode.ID, PSimpleDataArray( PByte( @MultiFrameBuffer^.DataArray[5]))^);
          CabData^.iStateMachine := STATE_CAB_SELECT_LOCO_SEND_PROXY_MANAGE_UNLOCK;    // Now need to unlock the Proxy
          Exit;
        end;
  end; // case
end;
{$ENDIF}

// *****************************************************************************
//  procedure AppCallBack_ProtocolSupportReply
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   Dest   : Full Node ID (and Alias if on CAN) of the dest node for the message
//                   DataBytes: pointer Raw data bytes, Byte 0 and 1 are the Alias
//     Returns     : None
//     Description : Called in response to a Protocol Support Request
// *****************************************************************************
procedure AppCallBack_ProtocolSupportReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
begin

end;

// *****************************************************************************
//  procedure AppCallback_ConsumerIdentified
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   Dest   : Full Node ID (and Alias if on CAN) of the dest node for the message
//                   MTI    : MTI of the message
//                   EventID: pointer to the Event ID for the message
//     Returns     : None
//     Description : This is called directly from the Hardware receive buffer.  Do
//                   not do anything here that stalls the call.  This is called
//                   Asyncronously from the Statemachine loop and the Statemachine loop
//                   is stalled until this returns.  Set a flag and move on is the
//                   best stratagy or store info in a buffer and process in the
//                   main statemachine.
// *****************************************************************************
procedure AppCallback_ConsumerIdentified(var Source: TNodeInfo; MTI: Word; EventID: PEventID);
begin

end;

// *****************************************************************************
//  procedure AppCallback_ProducerIdentified
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   Dest   : Full Node ID (and Alias if on CAN) of the dest node for the message
//                   MTI    : MTI of the message
//                   EventID: pointer to the Event ID for the message
//     Returns     : None
//     Description : This is called directly from the Hardware receive buffer.  Do
//                   not do anything here that stalls the call.  This is called
//                   Asyncronously from the Statemachine loop and the Statemachine loop
//                   is stalled until this returns.  Set a flag and move on is the
//                   best stratagy or store info in a buffer and process in the
//                   main statemachine.
// *****************************************************************************
procedure AppCallback_ProducerIdentified(var Source: TNodeInfo; MTI: Word; EventID: PEventID);
var
  i: Integer;
begin
  if NMRAnetUtilities_EqualEventID(EventID, @EVENT_IS_PROXY) then
  begin
    for i := 0 to USER_MAX_NODE_COUNT - 1 do
    begin
      if NodePool.Pool[i].State and NS_ALLOCATED then
        NodePool.Pool[i].TrainData.ControllerLink := Source;
    end
  end
end;

// *****************************************************************************
//  procedure AppCallback_LearnEvent
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   EventID: pointer to the Event ID for the message
//     Returns     : None
//     Description : This is called directly from the Hardware receive buffer.  Do
//                   not do anything here that stalls the call.  This is called
//                   Asyncronously from the Statemachine loop and the Statemachine loop
//                   is stalled until this returns.  Set a flag and move on is the
//                   best stratagy or store info in a buffer and process in the
//                   main statemachine.
// *****************************************************************************
procedure AppCallback_LearnEvent(var Source: TNodeInfo; EventID: PEventID);
begin

end;

// *****************************************************************************
//  procedure AppCallBack_PCEventReport
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   EventID: pointer to the Event ID for the message
//     Returns     : None
//     Description : This is called directly from the Hardware receive buffer.  Do
//                   not do anything here that stalls the call.  This is called
//                   Asyncronously from the Statemachine loop and the Statemachine loop
//                   is stalled until this returns.  Set a flag and move on is the
//                   best stratagy or store info in a buffer and process in the
//                   main statemachine.
// *****************************************************************************
procedure AppCallBack_PCEventReport(var Source: TNodeInfo; EventID: PEventID);
begin

end;

// *****************************************************************************
//  procedure AppCallBack_ConfigMemReadReply
//     Parameters: : Node : Pointer to the node that needs to be initilized to its intial value
//     Returns     : None
//     Description : Typically called when a node is being intialized to be
//                   logged into the network.  It is possible the node can be
//                   discarded then reused so it may be called more than once for
//                   virtual nodes
// *****************************************************************************
procedure AppCallBack_ConfigMemReadReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean);
begin

end;

// *****************************************************************************
//  procedure AppCallBack_ConfigMemStreamReadReply
//     Parameters: : Node : Pointer to the node that needs to be initilized to its intial value
//     Returns     : None
//     Description : Typically called when a node is being intialized to be
//                   logged into the network.  It is possible the node can be
//                   discarded then reused so it may be called more than once for
//                   virtual nodes
// *****************************************************************************
procedure AppCallBack_ConfigMemStreamReadReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean);
begin

end;

// *****************************************************************************
//  procedure AppCallBack_ConfigMemWriteReply
//     Parameters: : Node : Pointer to the node that needs to be initilized to its intial value
//     Returns     : None
//     Description : Typically called when a node is being intialized to be
//                   logged into the network.  It is possible the node can be
//                   discarded then reused so it may be called more than once for
//                   virtual nodes
// *****************************************************************************
procedure AppCallBack_ConfigMemWriteReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean);
begin

end;

// *****************************************************************************
//  procedure AppCallBack_ConfigMemStreamWriteReply
//     Parameters: : Node : Pointer to the node that needs to be initilized to its intial value
//     Returns     : None
//     Description : Typically called when a node is being intialized to be
//                   logged into the network.  It is possible the node can be
//                   discarded then reused so it may be called more than once for
//                   virtual nodes
// *****************************************************************************
procedure AppCallBack_ConfigMemStreamWriteReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean);
begin

end;

// *****************************************************************************
//  procedure AppCallback_RemoteButtonReply
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   Dest   : Full Node ID (and Alias if on CAN) of the dest node for the message
//                   DataBytes: pointer to the raw data bytes
//     Returns     : None
//     Description : Called in response to a Remote Button request
// *****************************************************************************
procedure AppCallback_RemoteButtonReply(Node: PNMRAnetNode; var Source: TNodeInfo; DataBytes: PSimpleBuffer);
begin

end;

{$IFDEF SUPPORT_TRACTION}
// *****************************************************************************
//  procedure AppCallback_SimpleTrainNodeInfoReply
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   Dest   : Full Node ID (and Alias if on CAN) of the dest node for the message
//                   TrainNodeInfo: pointer to the null terminated strings
//     Returns     : None
//     Description : Called in response to a STNIP request
// *****************************************************************************
procedure AppCallback_SimpleTrainNodeInfoReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
begin

end;
{$ENDIF}

// *****************************************************************************
//  procedure AppCallback_Timer_1s
//     Parameters: : None
//     Returns     : None
//     Description : Typcally called from another thread or interrupt, only use
//                   to update asyncronous flags
// *****************************************************************************
procedure AppCallback_Timer_1s;
var
  i: Integer;
  Cab: PCab;
begin
  Inc(GlobalTimer_1s);
  Inc(RS485_Watchdog_1s);

   // Count up to the time out then freeze.  The Timer Count will be reset after the
  // main loop is done rediscovering
  if CabBridge.DiscoverTimer < REDISCOVERY_TIME then
    Inc(CabBridge.DiscoverTimer);

  for i := 0 to CabBridge.iAssignedCabCount - 1 do
  begin
    Cab := PCab( CabBridge.AssignedCabs[i]^.UserData);
    Inc( Cab^.WatchDog_1s);
  end;
end;

// *****************************************************************************
//  procedure AppCallback_SimpleNodeInfoReply
//     Parameters: : Source   : Full Node ID (and Alias if on CAN) of the source node for the message
//                   Dest     : Full Node ID (and Alias if on CAN) of the dest node for the message
//                   NodeInfo : pointer to the null terminated strings
//     Returns     : None
//     Description : Called in response to a SNIP Request
// *****************************************************************************
procedure AppCallback_SimpleNodeInfoReply(Node: PNMRAnetNode; AMessage: POPStackMessage);
begin

end;

// *****************************************************************************
//  procedure AppCallback_VerifiedNodeID
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   EventID: pointer to the Event ID for the message
//     Returns     : None
//     Description : This is called directly from the Hardware receive buffer.  Do
//                   not do anything here that stalls the call.  This is called
//                   Asyncronously from the Statemachine loop and the Statemachine loop
//                   is stalled until this returns.  Set a flag and move on is the
//                   best stratagy or store info in a buffer and process in the
//                   main statemachine.
// *****************************************************************************
procedure AppCallback_VerifiedNodeID(var Source: TNodeInfo; NodeID: PNodeID);
begin

end;

// *****************************************************************************
//  procedure AppCallback_InitializationComplete
//     Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message
//                   EventID: pointer to the Event ID for the message
//     Returns     : None
//     Description : This is called directly from the Hardware receive buffer.  Do
//                   not do anything here that stalls the call.  This is called
//                   Asyncronously from the Statemachine loop and the Statemachine loop
//                   is stalled until this returns.  Set a flag and move on is the
//                   best stratagy or store info in a buffer and process in the
//                   main statemachine.
// *****************************************************************************
procedure AppCallback_InitializationComplete(var Source: TNodeInfo; NodeID: PNodeID);
begin

end;

end.