From 44444208b749dfa5137536cc56ea5024ed8d66c7 Mon Sep 17 00:00:00 2001 From: Alex Lisitsyn Date: Tue, 10 Dec 2019 14:27:09 +0800 Subject: [PATCH] freemodbus: update poll event processing update modbus poll event loop processing to process multiple events --- components/freemodbus/modbus/include/mb.h | 4 +- components/freemodbus/modbus/mb.c | 7 +- components/freemodbus/modbus/mb_m.c | 127 ++++++++---------- components/freemodbus/port/port.h | 4 + components/freemodbus/port/portevent_m.c | 27 ++-- components/freemodbus/port/portserial.c | 2 +- components/freemodbus/port/portserial_m.c | 21 ++- components/freemodbus/port/porttimer_m.c | 3 +- .../protocols/modbus/serial/example_test.py | 2 +- tools/ci/config/target-test.yml | 2 +- 10 files changed, 100 insertions(+), 99 deletions(-) diff --git a/components/freemodbus/modbus/include/mb.h b/components/freemodbus/modbus/include/mb.h index d44b4c07f..754048f86 100644 --- a/components/freemodbus/modbus/include/mb.h +++ b/components/freemodbus/modbus/include/mb.h @@ -71,7 +71,9 @@ PR_BEGIN_EXTERN_C /*! \ingroup modbus * \brief Use the default Modbus TCP port (502) */ -#define MB_TCP_PORT_USE_DEFAULT 0 +#define MB_TCP_PORT_USE_DEFAULT 0 + +#define MB_FUNC_CODE_MAX 127 /* ----------------------- Type definitions ---------------------------------*/ #ifndef _MB_M_H diff --git a/components/freemodbus/modbus/mb.c b/components/freemodbus/modbus/mb.c index e64c85785..d847b97e2 100644 --- a/components/freemodbus/modbus/mb.c +++ b/components/freemodbus/modbus/mb.c @@ -376,7 +376,8 @@ eMBPoll( void ) if ( !ucMBFrame ) { return MB_EILLSTATE; } - ESP_LOGD(MB_PORT_TAG, "%s:EV_EXECUTE", __func__); ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; + ESP_LOGD(MB_PORT_TAG, "%s:EV_EXECUTE", __func__); + ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; eException = MB_EX_ILLEGAL_FUNCTION; for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) { @@ -385,7 +386,7 @@ eMBPoll( void ) { break; } - else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) + if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) { eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); break; @@ -418,7 +419,7 @@ eMBPoll( void ) case EV_FRAME_SENT: ESP_LOGD(MB_PORT_TAG, "%s:EV_FRAME_SENT", __func__); break; - + default: break; } diff --git a/components/freemodbus/modbus/mb_m.c b/components/freemodbus/modbus/mb_m.c index f9f5f5d1b..cc86f66c9 100644 --- a/components/freemodbus/modbus/mb_m.c +++ b/components/freemodbus/modbus/mb_m.c @@ -269,7 +269,6 @@ eMBMasterPoll( void ) static UCHAR ucFunctionCode; static USHORT usLength; static eMBException eException; - int i; int j; eMBErrorCode eStatus = MB_ENOERR; @@ -284,46 +283,31 @@ eMBMasterPoll( void ) /* Check if there is a event available. If not return control to caller. * Otherwise we will handle the event. */ - if( xMBMasterPortEventGet( &eEvent ) == TRUE ) + if ( xMBMasterPortEventGet( &eEvent ) == TRUE ) { - switch ( eEvent ) - { - case EV_MASTER_NO_EVENT: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_NO_EVENT", __func__); - // Something went wrong and task unblocked but there are no any events set - assert(0); - break; - case EV_MASTER_PROCESS_SUCCESS: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_PROCESS_SUCCESS", __func__); - break; - case EV_MASTER_ERROR_RESPOND_TIMEOUT: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_ERROR_RESPOND_TIMEOUT", __func__); - break; - case EV_MASTER_ERROR_RECEIVE_DATA: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_ERROR_RECEIVE_DATA", __func__); - break; - case EV_MASTER_ERROR_EXECUTE_FUNCTION: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_ERROR_EXECUTE_FUNCTION", __func__); - break; - case EV_MASTER_READY: + // In some cases it is possible that more than one event set + // together (even from one subset mask) than process them consistently + if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_READY ) ) { ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_READY", __func__); - break; - case EV_MASTER_FRAME_RECEIVED: - eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); + MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_READY ); + } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_FRAME_RECEIVED ) ) { + eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength); + ESP_LOG_BUFFER_HEX_LEVEL("POLL RCV buffer:", (void*)ucMBFrame, (uint16_t)usLength, ESP_LOG_DEBUG); // Check if the frame is for us. If not ,send an error process event. if ( ( eStatus == MB_ENOERR ) && ( ucRcvAddress == ucMBMasterGetDestAddress() ) ) { ESP_LOGD(MB_PORT_TAG, "%s: Packet data received successfully (%u).", __func__, eStatus); ( void ) xMBMasterPortEventPost( EV_MASTER_EXECUTE ); - } + } else { vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA); - ESP_LOGD(MB_PORT_TAG, "%s: Packet data receive failed (addr=%u)(%u).", __func__, ucRcvAddress, eStatus); + ESP_LOGD( MB_PORT_TAG, "%s: Packet data receive failed (addr=%u)(%u).", + __func__, ucRcvAddress, eStatus); ( void ) xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS ); } - break; - case EV_MASTER_EXECUTE: + MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_FRAME_RECEIVED ); + } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_EXECUTE ) ) { if ( !ucMBFrame ) { return MB_EILLSTATE; @@ -335,10 +319,8 @@ eMBMasterPoll( void ) if (ucFunctionCode & MB_FUNC_ERROR) { eException = (eMBException)ucMBFrame[MB_PDU_DATA_OFF]; - } - else - { - for (i = 0; i < MB_FUNC_HANDLERS_MAX; i++) + } else { + for ( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) { /* No more function handlers registered. Abort. */ if (xMasterFuncHandlers[i].ucFunctionCode == 0) @@ -362,9 +344,9 @@ eMBMasterPoll( void ) } else { - eException = xMasterFuncHandlers[i].pxHandler(ucMBFrame, &usLength); + eException = xMasterFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); } - vMBMasterSetCBRunInMasterMode(FALSE); + vMBMasterSetCBRunInMasterMode( FALSE ); break; } } @@ -380,54 +362,58 @@ eMBMasterPoll( void ) vMBMasterCBRequestSuccess( ); vMBMasterRunResRelease( ); } - break; - case EV_MASTER_FRAME_TRANSMIT: + MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_EXECUTE ); + } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_FRAME_TRANSMIT ) ) { ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_FRAME_TRANSMIT", __func__); /* Master is busy now. */ vMBMasterGetPDUSndBuf( &ucMBFrame ); eStatus = peMBMasterFrameSendCur( ucMBMasterGetDestAddress(), ucMBFrame, usMBMasterGetPDUSndLength() ); if (eStatus != MB_ENOERR) { - ESP_LOGD(MB_PORT_TAG, "%s:Frame send error. %d", __func__, eStatus); + ESP_LOGE( MB_PORT_TAG, "%s:Frame send error. %d", __func__, eStatus ); } - break; - case EV_MASTER_FRAME_SENT: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_FRAME_SENT", __func__); - break; - case EV_MASTER_ERROR_PROCESS: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_ERROR_PROCESS", __func__); + MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_FRAME_TRANSMIT ); + } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_FRAME_SENT ) ) { + ESP_LOGD( MB_PORT_TAG, "%s:EV_MASTER_FRAME_SENT", __func__ ); + MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_FRAME_SENT ); + } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_ERROR_PROCESS ) ) { + ESP_LOGD( MB_PORT_TAG, "%s:EV_MASTER_ERROR_PROCESS", __func__ ); /* Execute specified error process callback function. */ - errorType = eMBMasterGetErrorType(); + errorType = eMBMasterGetErrorType( ); vMBMasterGetPDUSndBuf( &ucMBFrame ); - switch (errorType) + switch ( errorType ) { - case EV_ERROR_RESPOND_TIMEOUT: - vMBMasterErrorCBRespondTimeout(ucMBMasterGetDestAddress(), - ucMBFrame, usMBMasterGetPDUSndLength()); - break; - case EV_ERROR_RECEIVE_DATA: - vMBMasterErrorCBReceiveData(ucMBMasterGetDestAddress(), - ucMBFrame, usMBMasterGetPDUSndLength()); - break; - case EV_ERROR_EXECUTE_FUNCTION: - vMBMasterErrorCBExecuteFunction(ucMBMasterGetDestAddress(), - ucMBFrame, usMBMasterGetPDUSndLength()); - break; - default: - ESP_LOGD(MB_PORT_TAG, "%s: incorrect error type.", __func__); - break; + case EV_ERROR_RESPOND_TIMEOUT: + vMBMasterErrorCBRespondTimeout( ucMBMasterGetDestAddress( ), + ucMBFrame, usMBMasterGetPDUSndLength( ) ); + break; + case EV_ERROR_RECEIVE_DATA: + vMBMasterErrorCBReceiveData( ucMBMasterGetDestAddress( ), + ucMBFrame, usMBMasterGetPDUSndLength( ) ); + break; + case EV_ERROR_EXECUTE_FUNCTION: + vMBMasterErrorCBExecuteFunction( ucMBMasterGetDestAddress( ), + ucMBFrame, usMBMasterGetPDUSndLength( ) ); + break; + default: + ESP_LOGE( MB_PORT_TAG, "%s: incorrect error type = %d.", __func__, errorType); + break; } - vMBMasterRunResRelease(); - break; - default: - ESP_LOGD(MB_PORT_TAG, "%s: incorrect event triggered = %d.", __func__, eEvent); - break; + vMBMasterRunResRelease( ); + MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_ERROR_PROCESS ); + } + if ( eEvent ) { + // Event processing is done, but some poll events still set then + // postpone its processing for next poll cycle (rare case). + ESP_LOGW( MB_PORT_TAG, "%s: Unprocessed event %d.", __func__, eEvent ); + ( void ) xMBMasterPortEventPost( eEvent ); } } else { - // xMBMasterPortEventGet has unbloked the task but the event bits are not set - ESP_LOGD(MB_PORT_TAG, "%s: task event wait failure.", __func__); + // Something went wrong and task unblocked but there are no any correct events set + ESP_LOGE( MB_PORT_TAG, "%s: Unexpected event triggered %d.", __func__, eEvent ); + eStatus = MB_EILLSTATE; } - return MB_ENOERR; + return eStatus; } // Get whether the Modbus Master is run in master mode. @@ -497,7 +483,8 @@ eMBMasterTimerMode MB_PORT_ISR_ATTR xMBMasterGetCurTimerMode( void ) } /* The master request is broadcast? */ -BOOL MB_PORT_ISR_ATTR xMBMasterRequestIsBroadcast( void ){ +BOOL MB_PORT_ISR_ATTR xMBMasterRequestIsBroadcast( void ) +{ return xFrameIsBroadcast; } diff --git a/components/freemodbus/port/port.h b/components/freemodbus/port/port.h index e442874e2..1337dfc8e 100644 --- a/components/freemodbus/port/port.h +++ b/components/freemodbus/port/port.h @@ -31,6 +31,7 @@ #define MB_SERIAL_TX_TOUT_MS (100) #define MB_SERIAL_TX_TOUT_TICKS pdMS_TO_TICKS(MB_SERIAL_TX_TOUT_MS) // timeout for transmission #define MB_SERIAL_RX_TOUT_TICKS pdMS_TO_TICKS(1) // timeout for rx from buffer +#define MB_SERIAL_RESP_LEN_MIN (4) #define MB_PORT_CHECK(a, ret_val, str, ...) \ if (!(a)) { \ @@ -70,6 +71,9 @@ void vMBPortExitCritical(void); #define EXIT_CRITICAL_SECTION( ) { vMBPortExitCritical(); \ ESP_LOGD(MB_PORT_TAG,"%s: Port exit critical", __func__); } +#define MB_PORT_CHECK_EVENT( event, mask ) ( event & mask ) +#define MB_PORT_CLEAR_EVENT( event, mask ) do { event &= ~mask; } while(0) + #ifdef __cplusplus PR_END_EXTERN_C #endif /* __cplusplus */ diff --git a/components/freemodbus/port/portevent_m.c b/components/freemodbus/port/portevent_m.c index ed023cbbb..2e54c926c 100644 --- a/components/freemodbus/port/portevent_m.c +++ b/components/freemodbus/port/portevent_m.c @@ -62,7 +62,6 @@ EV_MASTER_ERROR_RECEIVE_DATA | \ EV_MASTER_ERROR_EXECUTE_FUNCTION ) -#define MB_CHECK_EVENT(event, mask) (event & mask) /* ----------------------- Variables ----------------------------------------*/ static SemaphoreHandle_t xSemaphorMasterHdl; @@ -118,19 +117,19 @@ xMBMasterPortEventGet( eMBMasterEventType * eEvent) { EventBits_t uxBits; BOOL xEventHappened = FALSE; - uxBits = xEventGroupWaitBits( - xEventGroupMasterHdl, // The event group being tested. - MB_EVENT_POLL_MASK, // The bits within the event group to wait for. - pdTRUE, // Masked bits should be cleared before returning. - pdFALSE, // Don't wait for both bits, either bit will do. - portMAX_DELAY); // Wait forever for either bit to be set. + uxBits = xEventGroupWaitBits( xEventGroupMasterHdl, // The event group being tested. + MB_EVENT_POLL_MASK, // The bits within the event group to wait for. + pdTRUE, // Masked bits should be cleared before returning. + pdFALSE, // Don't wait for both bits, either bit will do. + portMAX_DELAY); // Wait forever for either bit to be set. // Check if poll event is correct - if (uxBits & MB_EVENT_POLL_MASK) { - *eEvent = (eMBMasterEventType)(uxBits); + if (MB_PORT_CHECK_EVENT(uxBits, MB_EVENT_POLL_MASK)) { + *eEvent = (eMBMasterEventType)(uxBits & MB_EVENT_POLL_MASK); xEventHappened = TRUE; } else { ESP_LOGE(MB_PORT_TAG,"%s: Incorrect event triggered = %d.", __func__, uxBits); + *eEvent = (eMBMasterEventType)uxBits; xEventHappened = FALSE; } return xEventHappened; @@ -258,16 +257,16 @@ eMBMasterReqErrCode eMBMasterWaitRequestFinish( void ) { if (xRecvedEvent) { ESP_LOGD(MB_PORT_TAG,"%s: returned event = 0x%x", __func__, xRecvedEvent); if (!(xRecvedEvent & MB_EVENT_REQ_MASK)) { - // if we wait for certain event bits but get other then set in mask + // if we wait for certain event bits but get from poll subset ESP_LOGE(MB_PORT_TAG,"%s: incorrect event set = 0x%x", __func__, xRecvedEvent); } - if MB_CHECK_EVENT(xRecvedEvent, EV_MASTER_PROCESS_SUCCESS) { + if (MB_PORT_CHECK_EVENT(xRecvedEvent, EV_MASTER_PROCESS_SUCCESS)) { eErrStatus = MB_MRE_NO_ERR; - } else if MB_CHECK_EVENT(xRecvedEvent, EV_MASTER_ERROR_RESPOND_TIMEOUT){ + } else if (MB_PORT_CHECK_EVENT(xRecvedEvent, EV_MASTER_ERROR_RESPOND_TIMEOUT)) { eErrStatus = MB_MRE_TIMEDOUT; - } else if MB_CHECK_EVENT(xRecvedEvent, EV_MASTER_ERROR_RECEIVE_DATA){ + } else if (MB_PORT_CHECK_EVENT(xRecvedEvent, EV_MASTER_ERROR_RECEIVE_DATA)) { eErrStatus = MB_MRE_REV_DATA; - } else if MB_CHECK_EVENT(xRecvedEvent, EV_MASTER_ERROR_EXECUTE_FUNCTION){ + } else if (MB_PORT_CHECK_EVENT(xRecvedEvent, EV_MASTER_ERROR_EXECUTE_FUNCTION)) { eErrStatus = MB_MRE_EXE_FUN; } } else { diff --git a/components/freemodbus/port/portserial.c b/components/freemodbus/port/portserial.c index 57d107d47..4c2eeaa4d 100644 --- a/components/freemodbus/port/portserial.c +++ b/components/freemodbus/port/portserial.c @@ -109,7 +109,7 @@ static void vMBPortSerialRxPoll(size_t xEventSize) USHORT usCnt = 0; if (bRxStateEnabled) { - if (xEventSize > 0) { + if (xEventSize > MB_SERIAL_RESP_LEN_MIN) { xEventSize = (xEventSize > MB_SERIAL_BUF_SIZE) ? MB_SERIAL_BUF_SIZE : xEventSize; // Get received packet into Rx buffer for(usCnt = 0; xReadStatus && (usCnt < xEventSize); usCnt++ ) { diff --git a/components/freemodbus/port/portserial_m.c b/components/freemodbus/port/portserial_m.c index e6566bedc..0359d6fdc 100644 --- a/components/freemodbus/port/portserial_m.c +++ b/components/freemodbus/port/portserial_m.c @@ -76,6 +76,9 @@ static UCHAR ucUartNumber = UART_NUM_MAX - 1; static BOOL bRxStateEnabled = FALSE; // Receiver enabled flag static BOOL bTxStateEnabled = FALSE; // Transmitter enabled flag +static UCHAR ucBuffer[MB_SERIAL_BUF_SIZE]; // Temporary buffer to transfer received data to modbus stack +static USHORT uiRxBufferPos = 0; // position in the receiver buffer + void vMBMasterPortSerialEnable(BOOL bRxEnable, BOOL bTxEnable) { // This function can be called from xMBRTUTransmitFSM() of different task @@ -99,12 +102,14 @@ static void vMBMasterPortSerialRxPoll(size_t xEventSize) USHORT usCnt = 0; if (bRxStateEnabled) { - if (xEventSize > 0) { + if (xEventSize > MB_SERIAL_RESP_LEN_MIN) { xEventSize = (xEventSize > MB_SERIAL_BUF_SIZE) ? MB_SERIAL_BUF_SIZE : xEventSize; // Get received packet into Rx buffer - for(usCnt = 0; xReadStatus && (usCnt < xEventSize); usCnt++ ) { + USHORT usLength = uart_read_bytes(ucUartNumber, &ucBuffer[0], xEventSize, portMAX_DELAY); + uiRxBufferPos = 0; + for(usCnt = 0; xReadStatus && (usCnt < usLength); usCnt++ ) { // Call the Modbus stack callback function and let it fill the stack buffers. - xReadStatus = pxMBMasterFrameCBByteReceived(); // calls callback xMBRTUReceiveFSM() + xReadStatus = pxMBMasterFrameCBByteReceived(); // callback to receive FSM state machine } // The buffer is transferred into Modbus stack and is not needed here any more uart_flush_input(ucUartNumber); @@ -124,7 +129,7 @@ BOOL xMBMasterPortSerialTxPoll(void) // Continue while all response bytes put in buffer or out of buffer while((bNeedPoll) && (usCount++ < MB_SERIAL_BUF_SIZE)) { // Calls the modbus stack callback function to let it fill the UART transmit buffer. - bNeedPoll = pxMBMasterFrameCBTransmitterEmpty( ); // calls callback xMBRTUTransmitFSM(); + bNeedPoll = pxMBMasterFrameCBTransmitterEmpty( ); // callback to transmit FSM state machine } ESP_LOGD(TAG, "MB_TX_buffer sent: (%d) bytes.", (uint16_t)(usCount - 1)); // Waits while UART sending the packet @@ -254,6 +259,7 @@ BOOL xMBMasterPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, } else { vTaskSuspend(xMbTaskHandle); // Suspend serial task while stack is not started } + uiRxBufferPos = 0; ESP_LOGD(MB_PORT_TAG,"%s Init serial.", __func__); return TRUE; } @@ -276,6 +282,9 @@ BOOL xMBMasterPortSerialPutByte(CHAR ucByte) BOOL xMBMasterPortSerialGetByte(CHAR* pucByte) { assert(pucByte != NULL); - USHORT usLength = uart_read_bytes(ucUartNumber, (uint8_t*)pucByte, 1, MB_SERIAL_RX_TOUT_TICKS); - return (usLength == 1); + MB_PORT_CHECK((uiRxBufferPos < MB_SERIAL_BUF_SIZE), + FALSE, "mb stack serial get byte failure."); + *pucByte = ucBuffer[uiRxBufferPos]; + uiRxBufferPos++; + return TRUE; } diff --git a/components/freemodbus/port/porttimer_m.c b/components/freemodbus/port/porttimer_m.c index e663a2990..85ab1234f 100644 --- a/components/freemodbus/port/porttimer_m.c +++ b/components/freemodbus/port/porttimer_m.c @@ -145,7 +145,6 @@ static BOOL xMBMasterPortTimersEnable(USHORT usTimerTics50us) MB_PORT_CHECK((xErr == ESP_OK), FALSE, "timer start failure, timer_start() returned (0x%x).", (uint32_t)xErr); - ESP_LOGD(MB_PORT_TAG,"%s Init timer.", __func__); return TRUE; } @@ -198,4 +197,4 @@ void vMBMasterPortTimerClose(void) ESP_ERROR_CHECK(timer_pause(usTimerGroupIndex, usTimerIndex)); ESP_ERROR_CHECK(timer_disable_intr(usTimerGroupIndex, usTimerIndex)); ESP_ERROR_CHECK(esp_intr_free(xTimerIntHandle)); -} \ No newline at end of file +} diff --git a/examples/protocols/modbus/serial/example_test.py b/examples/protocols/modbus/serial/example_test.py index 0902e1cb8..e0e21c3f0 100644 --- a/examples/protocols/modbus/serial/example_test.py +++ b/examples/protocols/modbus/serial/example_test.py @@ -165,7 +165,7 @@ def test_check_mode(dut=None, mode_str=None, value=None): return False -@ttfw_idf.idf_example_test(env_tag='Example_T2_RS485') +@ttfw_idf.idf_example_test(env_tag='UT_T2_RS485') def test_modbus_communication(env, comm_mode): global logger diff --git a/tools/ci/config/target-test.yml b/tools/ci/config/target-test.yml index 82657c638..b910be532 100644 --- a/tools/ci/config/target-test.yml +++ b/tools/ci/config/target-test.yml @@ -276,7 +276,7 @@ example_test_011: extends: .example_debug_template tags: - ESP32 - - Example_T2_RS485 + - UT_T2_RS485 artifacts: when: always expire_in: 1 week