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.