This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM2434: EtherNet/IP assembly Set CB not called during Idle state

Part Number: AM2434

Hello,

I'm having issue with callback for assembly set. Assembly is configurated to have 32bit Run/Idle header to detect Idle state. During run state callback is handled without issue and I can handle data I got from PLC. But during idle state, callback is called once and never again even if I switch to run and back to idle. As I tryed to understand code for discrete IO example, I didn't see any flags to clead or special methods to call for Idle handling. Could you point me in right direction to make callback being called during Idle state? 

Here is my assembly creation:

static uint32_t createAsmOutput(EI_API_CIP_NODE_T* pCipNode) {
	uint32_t errCode = EI_API_CIP_eERR_OK;
	uint16_t len;
	EI_API_CIP_SAssemCustomMap_t assembCustomMapCfg = {0};

	errCode = EI_API_CIP_createAssembly(pCipNode, DEVICE_asmCfgParam.asmOutputInst, EI_API_CIP_eAR_GET_AND_SET);
	if (errCode != EI_API_CIP_eERR_OK) {
		OSAL_printf("Error: %s:%d ErrCode:0x%08X\r\n", __FILE__, __LINE__, errCode);
		return errCode;
	}
	errCode = EI_API_CIP_setAssemblyFormat(pCipNode, DEVICE_asmCfgParam.asmOutputInst, ASSEMB_FORMAT_32BITHEADER);
	if (errCode != EI_API_CIP_eERR_OK) {
		OSAL_printf("Error: %s:%d ErrCode:0x%08X\r\n", __FILE__, __LINE__, errCode);
		return errCode;
	}

	assembCustomMapCfg.assemblyMemberLength =DEVICE_asmCfgParam.asmOutputLen;
	assembCustomMapCfg.mappedClassId = DEVICE_PROFILE_CIP_CLASS_ID_SOLENOID;
	assembCustomMapCfg.mappedInstanceId = DEVICE_PROFILE_CIP_INSTANCE_ID_01;
	assembCustomMapCfg.mappedAttributeId = k_INST_ATTR_ID_SOLENOID_VALUE;
	assembCustomMapCfg.fuCustomGet = cbGetCustomMapAsmOut;
	assembCustomMapCfg.fuCustomSet = cbSetCustomMapAsmOut;
	errCode = EI_API_CIP_addAssemblyMemberCustomMapped(pCipNode, DEVICE_asmCfgParam.asmOutputInst, &assembCustomMapCfg);
	if (errCode != EI_API_CIP_eERR_OK) {
		OSAL_printf("Error: %s:%d ErrCode:0x%08X\r\n", __FILE__, __LINE__, errCode);
		return errCode;
	}
	EI_API_CIP_getAssemblySize(pCipNode, DEVICE_asmCfgParam.asmOutputInst, &len);
	if (len != DEVICE_asmCfgParam.asmOutputLen) {
		OSAL_printf("Wrong assembly output length!\r\n");
		return -1;
	}

	return errCode;
}

and here is handling of set callback:

static EI_API_CIP_EAssemb_Return_Code_t cbSetCustomMapAsmOut(EI_API_CIP_SAssemMapData_t* pAttrMapData, const EI_API_CIP_STransferBuffer_t* pConsumeBuffer) {
	EI_API_CIP_EAssemb_Return_Code_t retValue = ASSEMB_SERVICE_NO_RESPONSE;
	uint16_t dataLen;

	OSAL_printf("ASM data set\r\n");
	if (DEVICE_PROFILE_CIP_CLASS_ID_SOLENOID != pAttrMapData->classId) {
		return retValue;
	}

	if (pAttrMapData->instanceId > 0) {
		if (k_INST_ATTR_ID_SOLENOID_VALUE == pAttrMapData->attributeId)
		{
			OSAL_printf("ASM data set\r\n");
			if(pConsumeBuffer->p8uDataBuf[0] == 0) //Idle state
			{
				OSAL_printf("Idle Detected\r\n");
				if(ConnectionIdle == false)
				{
					// change state from run to idle
					DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, DEVICE_CONNECTION_DELETED);
				}
				ConnectionIdle = true;
				retValue = ASSEMB_SERVICE_RESPONSE_OK;
			}
			else
			{
				if(ConnectionIdle == true)
				{
					// change state from idle to run
					DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, DEVICE_CONNECTION_ESTABLISHED_IO);
				}
				ConnectionIdle = false;

				dataLen = pConsumeBuffer->u16uMaxData;
				if (dataLen > MAX_SOLENOID_BYTES) {
					dataLen = MAX_SOLENOID_BYTES;
				}
				OSAL_MEMORY_memcpy(ab_BitMapSolenoidValue, pConsumeBuffer->p8uDataBuf + 4, dataLen); // +4 for header
				retValue = ASSEMB_SERVICE_RESPONSE_OK;
			}
		}
	}
	return retValue;
}

DEVICE_receiveConnectionEvent only change state machite to determine which values should by used for output to either Run or Idle.  

Thank you for help.

Best regards, 

Jan

  • Hi Jan,

    The initialization function "createAsmOutput" appears to be implemented correctly. Your set callback implementation is also mostly correct, though there is one minor issue: you should read the data length from "pConsumeBuffer->u16uActData" member rather than "pConsumeBuffer->u16uMaxData", which only indicates the maximum available size in the pConsumeBuffer->p8uDataBuf.

    I would expect you to receive both the Idle and Run headers correctly once the packet arrives. I cannot reproduce the scenario you described in the Discrete IO example.

    Since you have a printf message for the Idle command, I suggest adding a similar printf statement for the Run command to verify that both commands are being received correctly.

    The issue likely lies in your state machine implementation, specifically with either the global flag ConnectionIdle or the function DEVICE_receiveConnectionEvent, could you verify that?
    Best regards,
    Pourya

  • Hello Pourya,

    Run mode is recieved without issue. For idle, my observations are that callback is only called when I switch from recaverable fault to connection established while PLC is in idle state. 

    For my state machine I only have simple output swiching. Is there any function I should call for API to know current state? 

    Here is state machine I have implemented:

    static IMI_DEVICE_SmStates_t processState(IMI_DEVICE_SmStates_t state, IMI_DEVICE_ConnectionEvent_t event) {
    	if (event == IMI_DEVICE_CONNECTION_ESTABLISHED_IO) {
    		return IMI_DEVICE_SM_RUN_IO;
    	}
    
    	if (event == IMI_DEVICE_CONNECTION_ESTABLISHED_EXP) {
    		if (state == IMI_DEVICE_SM_RUN_IO) {
    			return IMI_DEVICE_SM_IDLE;
    		}
    		return IMI_DEVICE_SM_RUN_EXP;
    	}
    
    	if (event == IMI_DEVICE_CONNECTION_DELETED) {
    		return IMI_DEVICE_SM_IDLE;
    	}
    
    	if (event == IMI_DEVICE_CONNECTION_TIMEDOUT) {
    		return IMI_DEVICE_SM_RECOVERABLEFAULT;
    	}
    
    	return IMI_DEVICE_SM_UNRECOVERABLEFAULT;
    }
    
    DEVICE_SmStates_t IMI_DEVICE_getDeviceState(void) {
    	return connPathIDstate[0];
    }
    
    void DEVICE_receiveConnectionEvent(uint16_t connPathID, IMI_DEVICE_ConnectionEvent_t connectionEvent) {
    	switch (connPathID) {
    		case 1:
    			connPathIDstate[0] = processState(connPathIDstate[0], connectionEvent);
    			break;
    		case 2:
    			connPathIDstate[1] = processState(connPathIDstate[1], connectionEvent);
    			break;
    		case 3:
    			connPathIDstate[2] = processState(connPathIDstate[2], connectionEvent);
    			break;
    		case 4:
    			connPathIDstate[3] = processState(connPathIDstate[3], connectionEvent);
    			break;
    		default:
    			break;
    	}
    }
    
    void DEVICE_taskRun(EI_API_CIP_NODE_T* pCipNode) {
    	stateNow = IMI_DEVICE_getDeviceState();
    	switch (stateNow) {
    		case DEVICE_SM_RECOVERABLEFAULT:
    			for (i = 0; i < MAX_SOLENOID_BYTES * 8; i++) {
    				DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapFaultAction);
    				if (val) {
    					DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapFaultValue);
    					DEVICE_setABBitMapBit(i, val, ab_BitMapSolenoidValue);
    				}
    			}
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		case DEVICE_SM_IDLE:
    			for (i = 0; i < MAX_SOLENOID_BYTES * 8; i++) {
    				DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapIdleAction);
    				if (val) {
    					DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapIdleValue);
    					DEVICE_setABBitMapBit(i, val, ab_BitMapSolenoidValue);
    				}
    			}
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		case DEVICE_SM_READY:
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		case DEVICE_SM_RUN_IO:
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		case DEVICE_SM_RUN_EXP:
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		default:
    			for (i = 0; i < MAX_SOLENOID_BYTES * 8; i++) {
    				DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapFaultAction);
    				if (val) {
    					DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapFaultValue);
    					DEVICE_setABBitMapBit(i, val, ab_BitMapSolenoidValue);
    				}
    			}
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    	}
    }

    Thank you for help.

    Best regards, 

    Jan

  • Hi Jan,

    Let's first focus on the callback functionality before examining the state machine.

    Do the printf messages from your callback confirm that both Idle and Run states are being received correctly?

    Regarding your state machine implementation, I notice that during the Idle state, you're calling:
    "DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, DEVICE_CONNECTION_DELETED);"

    However, I question whether "pAttrMapData->attributeId" is the correct field to use here. Looking at your state machine design, it appears you should be passing the Instance ID instead. Furthermore, only Instance ID: 1 would work in this case, since your state machine only checks connPathIDstate[0] when running (based on the return value from IMI_DEVICE_getDeviceState()).

    Before proceeding further, please confirm whether the printf statements for both Run and Idle states are displaying correctly within the callback, as I expect they should be.

    Best regards,
    Pourya
  • Hello Pourya,

    My prinf shows that Idle state is not received in callback (it never enters callback according to printf before any conditions are checked) 

    Run state is recieved and I can read sucessfuly read data send by PLC.

    Thank you for help.

    Best regards, 

    Jan

  • Hi Jan,

    In this case, I need additional information to properly diagnose the issue. Please provide:

    1. A Wireshark log capturing the communication between the PLC and your device (use a network TAP to capture the packets being exchanged)

    2. The Assembly Instance IDs for both the O→T and T→O connections

    3. If available, the EDS file for your device

    With this information, we'll be better positioned to understand and address the underlying issue.

    Best regards,
    Pourya
  • Hello Pourya,

    Here are screenshots with wireshark logs

    Run mode: 

    Idle mode:

     

    My new printf are independent of state: 

    static EI_API_CIP_EAssemb_Return_Code_t cbSetCustomMapAsmOut(EI_API_CIP_SAssemMapData_t* pAttrMapData, const EI_API_CIP_STransferBuffer_t* pConsumeBuffer) {
    	EI_API_CIP_EAssemb_Return_Code_t retValue = ASSEMB_SERVICE_NO_RESPONSE;
    	uint16_t dataLen;
    
    	OSAL_printf("ASM data set\r\n");
    	
    	if (DEVICE_PROFILE_CIP_CLASS_ID_SOLENOID != pAttrMapData->classId) {
    		return retValue;
    	}
    
    	if (pAttrMapData->instanceId > 0) {
    		if (k_INST_ATTR_ID_SOLENOID_VALUE == pAttrMapData->attributeId)
    		{
    			OSAL_printf("ASM data set ID = 0x%X, Idle data 0x%X\r\n", pAttrMapData->instanceId, pConsumeBuffer->p8uDataBuf[0]);
    			if(pConsumeBuffer->p8uDataBuf[0] == 0) //Idle state
    			{
    				OSAL_printf("Idle Detected\r\n");
    				if(ConnectionIdle == false)
    				{
    					// change state from run to idle
    					IMI_DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, IMI_DEVICE_CONNECTION_DELETED);
    				}
    				ConnectionIdle = true;
    				retValue = ASSEMB_SERVICE_RESPONSE_OK;
    			}
    			else
    			{
    				if(ConnectionIdle == true)
    				{
    					// change state from idle to run
    					IMI_DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, IMI_DEVICE_CONNECTION_ESTABLISHED_IO);
    				}
    				ConnectionIdle = false;
    
    				dataLen = pConsumeBuffer->u16uActData;
    				if (dataLen > MAX_SOLENOID_BYTES) {
    					dataLen = MAX_SOLENOID_BYTES;
    				}
    				OSAL_MEMORY_memcpy(ab_BitMapSolenoidValue, pConsumeBuffer->p8uDataBuf + 4, dataLen); // +4 for header
    				retValue = ASSEMB_SERVICE_RESPONSE_OK;
    			}
    		}
    	}
    	return retValue;
    }
    1157.IdleMode.zip

    Thank you for help.

    Best regards, 

    Jan

  • Hello Pourya,

    Here are screenshots with wireshark logs

    Run mode: 

    Idle mode:

     

    My new printf are independent of state: 

    static EI_API_CIP_EAssemb_Return_Code_t cbSetCustomMapAsmOut(EI_API_CIP_SAssemMapData_t* pAttrMapData, const EI_API_CIP_STransferBuffer_t* pConsumeBuffer) {
    	EI_API_CIP_EAssemb_Return_Code_t retValue = ASSEMB_SERVICE_NO_RESPONSE;
    	uint16_t dataLen;
    
    	OSAL_printf("ASM data set\r\n");
    	
    	if (DEVICE_PROFILE_CIP_CLASS_ID_SOLENOID != pAttrMapData->classId) {
    		return retValue;
    	}
    
    	if (pAttrMapData->instanceId > 0) {
    		if (k_INST_ATTR_ID_SOLENOID_VALUE == pAttrMapData->attributeId)
    		{
    			OSAL_printf("ASM data set ID = 0x%X, Idle data 0x%X\r\n", pAttrMapData->instanceId, pConsumeBuffer->p8uDataBuf[0]);
    			if(pConsumeBuffer->p8uDataBuf[0] == 0) //Idle state
    			{
    				OSAL_printf("Idle Detected\r\n");
    				if(ConnectionIdle == false)
    				{
    					// change state from run to idle
    					IMI_DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, IMI_DEVICE_CONNECTION_DELETED);
    				}
    				ConnectionIdle = true;
    				retValue = ASSEMB_SERVICE_RESPONSE_OK;
    			}
    			else
    			{
    				if(ConnectionIdle == true)
    				{
    					// change state from idle to run
    					IMI_DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, IMI_DEVICE_CONNECTION_ESTABLISHED_IO);
    				}
    				ConnectionIdle = false;
    
    				dataLen = pConsumeBuffer->u16uActData;
    				if (dataLen > MAX_SOLENOID_BYTES) {
    					dataLen = MAX_SOLENOID_BYTES;
    				}
    				OSAL_MEMORY_memcpy(ab_BitMapSolenoidValue, pConsumeBuffer->p8uDataBuf + 4, dataLen); // +4 for header
    				retValue = ASSEMB_SERVICE_RESPONSE_OK;
    			}
    		}
    	}
    	return retValue;
    }
    6177.IdleMode.zip

    edit: added eds file

    Thank you for help.

    Best regards, 

    Jan

  • Hi Jan,

    Please ensure that the ForwardOpen request for the connection is captured in Wireshark (maybe start the Wireshark capture first then reset the device to make sure everything is captured), as this contains crucial information I need to diagnose the issue.

    Could you please re-upload the Wireshark logs?

    Best regards,
    Pourya

  • Hello Pourya,

    sorry for incomplete data, here are new logs.

     RunMode.zip 

    Thank you for help.

    Best regards, 

    Jan

  • Hi Jan,

    I believe I've found the problem. It seems your PLC is not increasing the CIP Sequence Count between packets, which is why the packets are not considered new and are therefore being dropped.

    There must be some setting in your PLC to configure it to increment the CIP Sequence Count between packets. You can also use the Hilscher tool to send your IO data to confirm that the callback is triggered correctly.

    Best regards,
    Pourya

  • Hello Pourya,

    you are right, after I used Hilsher tool, I managed to test it correctly. But is there any way to get information about dropped packets from stack to detect this state from PLC?

    Thank you for help.

    Best regards, 

    Jan

  • Hi Jan,

    no record is kept regarding the dropped packets because of duplicate data in the stack.

    Best regards,
    Pourya

  • Hello Pourya,

    Thank you for information.

    Best regards, 

    Jan