diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..4071bc3f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,47 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + name: Pytest (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.12",] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + cache-dependency-path: | + scripts/requirements.txt + scripts/tests/requirements.txt + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r scripts/requirements.txt + pip install -r scripts/tests/requirements.txt + + - name: Build mock testing libraries + run: | + chmod +x scripts/tests/build_testing_libs.sh + ./scripts/tests/build_testing_libs.sh + + - name: Run test suite + run: | + PYTHONPATH=scripts/tests python -m pytest -v scripts/tests/ diff --git a/firmware/RAMNV1/Core/Inc/ramn_canfd.h b/firmware/RAMNV1/Core/Inc/ramn_canfd.h index 89a978c7..9be3cba7 100644 --- a/firmware/RAMNV1/Core/Inc/ramn_canfd.h +++ b/firmware/RAMNV1/Core/Inc/ramn_canfd.h @@ -135,7 +135,14 @@ RAMN_Bool_t RAMN_FDCAN_IsTXBufferSpaceAvailable(uint8_t payloadSize); */ RAMN_Result_t RAMN_FDCAN_SendMessage(const FDCAN_TxHeaderTypeDef* header, const uint8_t* data); +// Origin identifiers for CAN frames (used in MessageMarker / IsFilterMatchingFrame) +#define RAMN_CAN_ORIGIN_BUS 0x00U +#define RAMN_CAN_ORIGIN_HOST 0xA1U +#define RAMN_CAN_ORIGIN_INTERNAL 0x02U + #if defined(TARGET_ECUA) +// Injects a host-originated CAN frame into local RX stream buffer so ECU A internal stacks process it. +RAMN_Result_t RAMN_FDCAN_InjectHostRxMessage(const FDCAN_TxHeaderTypeDef* txHeader, const uint8_t* data); // Setups the peripheral for communication with ROM FDCAN Bootloader. Cf AN5405. void RAMN_FDCAN_SetupForSTBootloader(void); #endif diff --git a/firmware/RAMNV1/Core/Inc/ramn_config.h b/firmware/RAMNV1/Core/Inc/ramn_config.h index 7da592e4..59f7d896 100644 --- a/firmware/RAMNV1/Core/Inc/ramn_config.h +++ b/firmware/RAMNV1/Core/Inc/ramn_config.h @@ -108,9 +108,10 @@ // If used with HARDENING, make sure you make the "#" slcan command available again. //#define START_IN_CLI_MODE -// Define this flag to let ECU A process slcan message that it receives as regular RX messages (and update their value on screen, for example). -// This is useful to demonstrate the impact of CAN fuzzing on ECU A's screen when using ECU A's slcan interface, even though ECU A did not actually receive the fuzzed CAN message (since it was the transmitter). -#define PROCESS_SLCAN_BY_DBC +// Enable PC interaction with ECU A over slcan and gs_usb. +// Injects host-transmitted frames into ECU A's local RX stream buffer (for UDS, J1939, DBC) +// and forwards ECU A's internal transmissions to the host PC over USB. +#define ENABLE_ECUA_HOST_INTERACTION // Define this flag to enable the USB debugging module. // Note that it also needs to be activated by a slcan command, or by setting RAMN_DEBUG_ENABLE in ramn_debug.c to True. diff --git a/firmware/RAMNV1/Core/Inc/ramn_j1939.h b/firmware/RAMNV1/Core/Inc/ramn_j1939.h index 410e61a0..220fc095 100644 --- a/firmware/RAMNV1/Core/Inc/ramn_j1939.h +++ b/firmware/RAMNV1/Core/Inc/ramn_j1939.h @@ -89,23 +89,56 @@ #define J1939_MANUFACTURER_NEBRASKA_TEST 522 #define J1939_MANUFACTURER_DLG_TEST 251 -/* Per-ECU primary source address for J1939 Address Claim */ +#define J1939_ECUA_IDENTITY 0xEC0A /* Identity number: EC0A */ +#define J1939_ECUB_IDENTITY 0xEC0B /* Identity number: EC0B */ +#define J1939_ECUC_IDENTITY 0xEC0C /* Identity number: EC0C */ +#define J1939_ECUD_IDENTITY 0xEC0D /* Identity number: EC0D */ + +/* Per-ECU primary source address for J1939 Address Claim and diagnostic routing */ #if defined(TARGET_ECUA) #define J1939_ECU_SA J1939_SA_HEADWAY_CTRL /* 42 */ #define J1939_ECU_FUNCTION J1939_FUNCTION_HEADWAY_CTRL /* 32 */ +#define J1939_ECU_IDENTITY J1939_ECUA_IDENTITY #define J1939_ECU_MANUFACTURER J1939_MANUFACTURER_NEVADA_TEST +#define J1939_ECU_KWP_SA J1939_ECU_SA +#define J1939_ECU_XCP_SA J1939_ECU_SA +#define J1939_ECU_KWP_XCP_SA J1939_ECU_SA #elif defined(TARGET_ECUB) #define J1939_ECU_SA J1939_SA_STEERING_CTRL /* 19 */ -#define J1939_ECU_FUNCTION J1939_FUNCTION_STEERING_CTRL /* 16 */ +#define J1939_ECU_FUNCTION J1939_FUNCTION_CHASSIS_CTRL /* 52: Chassis Controller */ +#define J1939_ECU_IDENTITY J1939_ECUB_IDENTITY #define J1939_ECU_MANUFACTURER J1939_MANUFACTURER_ISOBUS_TEST +#define J1939_ECU_KWP_SA J1939_ECU_SA +#define J1939_ECU_XCP_SA J1939_ECU_SA +#define J1939_ECU_KWP_XCP_SA J1939_ECU_SA #elif defined(TARGET_ECUC) #define J1939_ECU_SA J1939_SA_POWERTRAIN_CTRL /* 90 */ #define J1939_ECU_FUNCTION J1939_FUNCTION_ELECTRIC_POWERTRAIN /* 80 */ +#define J1939_ECU_IDENTITY J1939_ECUC_IDENTITY #define J1939_ECU_MANUFACTURER J1939_MANUFACTURER_NEBRASKA_TEST +#define J1939_ECU_KWP_SA J1939_ECU_SA +#define J1939_ECU_XCP_SA J1939_ECU_SA +#define J1939_ECU_KWP_XCP_SA J1939_ECU_SA #elif defined(TARGET_ECUD) #define J1939_ECU_SA J1939_SA_BODY_CTRL /* 33 */ #define J1939_ECU_FUNCTION J1939_FUNCTION_BODY_CTRL /* 26 */ +#define J1939_ECU_IDENTITY J1939_ECUD_IDENTITY +#define J1939_ECU_MANUFACTURER J1939_MANUFACTURER_DLG_TEST +#define J1939_ECU_KWP_SA J1939_ECU_SA +#define J1939_ECU_XCP_SA J1939_ECU_SA +#define J1939_ECU_KWP_XCP_SA J1939_ECU_SA +#else +/* Fallback definitions when no TARGET_ECUx macro is defined. + * This branch exists specifically for IDE code assistance / indexers, + * static analysis tools, and standalone / mock compilation units. + * Defaults safely to the primary ECU D (Body Controller) configuration. */ +#define J1939_ECU_SA J1939_SA_BODY_CTRL +#define J1939_ECU_FUNCTION J1939_FUNCTION_BODY_CTRL +#define J1939_ECU_IDENTITY J1939_ECUD_IDENTITY #define J1939_ECU_MANUFACTURER J1939_MANUFACTURER_DLG_TEST +#define J1939_ECU_KWP_SA J1939_ECU_SA +#define J1939_ECU_XCP_SA J1939_ECU_SA +#define J1939_ECU_KWP_XCP_SA J1939_ECU_SA #endif /* Proprietary A Payload Definitions */ diff --git a/firmware/RAMNV1/Core/Src/main.c b/firmware/RAMNV1/Core/Src/main.c index 5bc31401..74362742 100644 --- a/firmware/RAMNV1/Core/Src/main.c +++ b/firmware/RAMNV1/Core/Src/main.c @@ -1727,89 +1727,95 @@ void RAMN_ReceiveCANFunc(void *argument) #endif RAMN_CUSTOM_ProcessRxCANMessage(&CANRxHeader, CANRxData, xTaskGetTickCount()); -#if defined(ENABLE_CDC) - if (RAMN_USB_Config.slcanOpened) +#if defined(ENABLE_ECUA_HOST_INTERACTION) + // Do not echo host-originated frames back to USB to prevent host echo storms + if (CANRxHeader.IsFilterMatchingFrame != RAMN_CAN_ORIGIN_HOST) +#endif { - uint8_t index = 0; - - // Add prefix if frame is of CAN-FD type - if(CANRxHeader.FDFormat == FDCAN_FD_CAN) - { - if(CANRxHeader.BitRateSwitch == FDCAN_BRS_ON) slCAN_USBTxBuffer[index++] = '1'; - else slCAN_USBTxBuffer[index++] = '0'; - } - else +#if defined(ENABLE_CDC) + if (RAMN_USB_Config.slcanOpened) { - if (payloadSize > 8U) payloadSize = 8U; - } + uint8_t index = 0; - if(CANRxHeader.RxFrameType == FDCAN_DATA_FRAME) - { - // Message with Data - slCAN_USBTxBuffer[index++] = CANRxHeader.IdType == FDCAN_STANDARD_ID ? 't' : 'T'; - } - else - { - // FDCAN_REMOTE_FRAME is the only other option - slCAN_USBTxBuffer[index++] = CANRxHeader.IdType == FDCAN_STANDARD_ID ? 'r' : 'R'; - payloadSize = 0; //no payload will be sent. - } + // Add prefix if frame is of CAN-FD type + if(CANRxHeader.FDFormat == FDCAN_FD_CAN) + { + if(CANRxHeader.BitRateSwitch == FDCAN_BRS_ON) slCAN_USBTxBuffer[index++] = '1'; + else slCAN_USBTxBuffer[index++] = '0'; + } + else + { + if (payloadSize > 8U) payloadSize = 8U; + } - if (CANRxHeader.IdType == FDCAN_STANDARD_ID) - { - // Standard ID (FDCAN_STANDARD_ID) - index += uint12toASCII(CANRxHeader.Identifier,&slCAN_USBTxBuffer[index]); - } - else - { - // Extended ID (FDCAN_EXTENDED_ID) - index += uint32toASCII(CANRxHeader.Identifier,&slCAN_USBTxBuffer[index]); - } + if(CANRxHeader.RxFrameType == FDCAN_DATA_FRAME) + { + // Message with Data + slCAN_USBTxBuffer[index++] = CANRxHeader.IdType == FDCAN_STANDARD_ID ? 't' : 'T'; + } + else + { + // FDCAN_REMOTE_FRAME is the only other option + slCAN_USBTxBuffer[index++] = CANRxHeader.IdType == FDCAN_STANDARD_ID ? 'r' : 'R'; + payloadSize = 0; //no payload will be sent. + } - //TODO: unify - if (CANRxHeader.FDFormat == FDCAN_FD_CAN) - { - index += uint4toASCII((CANRxHeader.DataLength) & 0xF,&slCAN_USBTxBuffer[index]); - } - else - { - index += uint4toASCII((payloadSize) & 0xF,&slCAN_USBTxBuffer[index]); - } + if (CANRxHeader.IdType == FDCAN_STANDARD_ID) + { + // Standard ID (FDCAN_STANDARD_ID) + index += uint12toASCII(CANRxHeader.Identifier,&slCAN_USBTxBuffer[index]); + } + else + { + // Extended ID (FDCAN_EXTENDED_ID) + index += uint32toASCII(CANRxHeader.Identifier,&slCAN_USBTxBuffer[index]); + } - for(uint8_t i=0;i 8U) pSize = 8U; + } + + if (CANTxHeader.TxFrameType == FDCAN_DATA_FRAME) + { + slcanBuf[slcanIdx++] = (CANTxHeader.IdType == FDCAN_STANDARD_ID) ? 't' : 'T'; + } + else + { + slcanBuf[slcanIdx++] = (CANTxHeader.IdType == FDCAN_STANDARD_ID) ? 'r' : 'R'; + pSize = 0; + } + + if (CANTxHeader.IdType == FDCAN_STANDARD_ID) + { + slcanIdx += uint12toASCII(CANTxHeader.Identifier, &slcanBuf[slcanIdx]); + } + else + { + slcanIdx += uint32toASCII(CANTxHeader.Identifier, &slcanBuf[slcanIdx]); + } + + if (CANTxHeader.FDFormat == FDCAN_FD_CAN) + { + slcanIdx += uint4toASCII((CANTxHeader.DataLength) & 0xF, &slcanBuf[slcanIdx]); + } + else + { + slcanIdx += uint4toASCII((pSize) & 0xF, &slcanBuf[slcanIdx]); + } + + for (uint8_t i = 0; i < pSize; i++) + { + slcanIdx += uint8toASCII(CANTxData[i], &slcanBuf[slcanIdx]); + } + + if (RAMN_USB_Config.slcan_enableTimestamp != 0U) + { + slcanIdx += uint16toASCII((xTaskGetTickCount() * (1000 /*ms per sec*/ / configTICK_RATE_HZ)) % 0xEA60, &slcanBuf[slcanIdx]); + } + + if ((RAMN_USB_Config.addESIFlag != 0U) && (CANTxHeader.FDFormat == FDCAN_FD_CAN) && (CANTxHeader.ErrorStateIndicator == FDCAN_ESI_PASSIVE)) + { + slcanBuf[slcanIdx++] = 'i'; + } + + slcanBuf[slcanIdx++] = '\r'; + if (RAMN_USB_SendFromTask(slcanBuf, slcanIdx) != RAMN_OK) + { +#ifdef CLOSE_DEVICE_ON_USB_TX_OVERFLOW + RAMN_USB_Config.slcanOpened = False; +#endif + } } - RAMN_SocketCAN_SendTX(&CANTxHeader, CANTxData); +#endif + +#ifdef ENABLE_GSUSB + if (RAMN_USB_Config.gsusbOpened && GSUSB_IsConnected((USBD_HandleTypeDef*)hpcd_USB_FS.pData)) + { + if (RAMN_GSUSB_ProcessTX(&CANTxHeader, CANTxData) == RAMN_ERROR) + { + RAMN_USB_Config.queueErrorCnt++; + } + } +#endif } - */ #endif RAMN_FDCAN_Status.CANTXRequestCnt++; @@ -2439,7 +2516,7 @@ void RAMN_RxTask2Func(void *argument) CANTxHeader.FDFormat = FDCAN_CLASSIC_CAN; CANTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; - //CANTxHeader.MessageMarker = 0U; + CANTxHeader.MessageMarker = RAMN_CAN_ORIGIN_HOST; if (CANTxHeader.TxFrameType == FDCAN_DATA_FRAME) { @@ -2449,7 +2526,8 @@ void RAMN_RxTask2Func(void *argument) //TODO: implement better error reports if (RAMN_FDCAN_SendMessage(&CANTxHeader,CANTxData) == RAMN_OK) { - // for host candump + // for host candump (timestamps are ignored on host send echo) + recvFrame->timestamp_us = 0; ret = xQueueSendToBack(RAMN_GSUSB_SendQueueHandle, &recvFrame, CAN_QUEUE_TIMEOUT); if (ret != pdPASS) { @@ -2457,6 +2535,9 @@ void RAMN_RxTask2Func(void *argument) // Drop frame and return buffer to pool xQueueSendToBack(RAMN_GSUSB_PoolQueueHandle, &recvFrame, portMAX_DELAY); } +#if defined(ENABLE_ECUA_HOST_INTERACTION) + RAMN_FDCAN_InjectHostRxMessage(&CANTxHeader, CANTxData); +#endif } } } diff --git a/firmware/RAMNV1/Core/Src/ramn_can_database.c b/firmware/RAMNV1/Core/Src/ramn_can_database.c index 9aa368b3..a6946347 100644 --- a/firmware/RAMNV1/Core/Src/ramn_can_database.c +++ b/firmware/RAMNV1/Core/Src/ramn_can_database.c @@ -5,6 +5,8 @@ #include "ramn_traffic.h" #ifdef CPYTHON_TESTING +#include +#include #ifndef RAMN_memcpy #define RAMN_memcpy memcpy #ifndef RAMN_memset diff --git a/firmware/RAMNV1/Core/Src/ramn_canfd.c b/firmware/RAMNV1/Core/Src/ramn_canfd.c index 11261a9b..f4c706c3 100644 --- a/firmware/RAMNV1/Core/Src/ramn_canfd.c +++ b/firmware/RAMNV1/Core/Src/ramn_canfd.c @@ -565,6 +565,47 @@ RAMN_Result_t RAMN_FDCAN_SendMessage(const FDCAN_TxHeaderTypeDef* header, const } #if defined(TARGET_ECUA) +RAMN_Result_t RAMN_FDCAN_InjectHostRxMessage(const FDCAN_TxHeaderTypeDef* txHeader, const uint8_t* data) +{ + FDCAN_RxHeaderTypeDef rxHeader; + size_t xBytesSent; + size_t messageSize; + uint8_t dlc; + + rxHeader.Identifier = txHeader->Identifier; + rxHeader.IdType = txHeader->IdType; + rxHeader.RxFrameType = txHeader->TxFrameType; + rxHeader.DataLength = txHeader->DataLength; + rxHeader.ErrorStateIndicator = txHeader->ErrorStateIndicator; + rxHeader.BitRateSwitch = txHeader->BitRateSwitch; + rxHeader.FDFormat = txHeader->FDFormat; + rxHeader.RxTimestamp = 0U; + rxHeader.FilterIndex = 0U; + rxHeader.IsFilterMatchingFrame = RAMN_CAN_ORIGIN_HOST; + + dlc = DLCtoUINT8(rxHeader.DataLength); + if (rxHeader.RxFrameType == FDCAN_REMOTE_FRAME) dlc = 0U; + messageSize = sizeof(rxHeader) + dlc; + + if (xStreamBufferSpacesAvailable(CANRxDataStreamBufferHandle) < messageSize) + { + RAMN_FDCAN_Status.CANRxOverrunCnt++; + return RAMN_TRY_LATER; + } + + xBytesSent = xStreamBufferSend(CANRxDataStreamBufferHandle, (void*)&rxHeader, sizeof(rxHeader), 0U); + if (dlc > 0U) + { + xBytesSent += xStreamBufferSend(CANRxDataStreamBufferHandle, (void*)data, dlc, 0U); + } + if (xBytesSent != messageSize) + { + return RAMN_ERROR; + } + + return RAMN_OK; +} + void RAMN_FDCAN_SetupForSTBootloader(void) { //Assumes a 40MHz clock diff --git a/firmware/RAMNV1/Core/Src/ramn_cdc.c b/firmware/RAMNV1/Core/Src/ramn_cdc.c index 4b2cf96b..4eccd860 100644 --- a/firmware/RAMNV1/Core/Src/ramn_cdc.c +++ b/firmware/RAMNV1/Core/Src/ramn_cdc.c @@ -1,4 +1,4 @@ -/* +/* * ramn_cdc.c ****************************************************************************** * @attention @@ -1096,7 +1096,7 @@ RAMN_Bool_t RAMN_CDC_ProcessSLCANBuffer(uint8_t* USBRxBuffer, uint32_t commandLe else CANTxHeader.ErrorStateIndicator = RAMN_FDCAN_Status.ErrorStateIndicator; CANTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; - CANTxHeader.MessageMarker = 0U; + CANTxHeader.MessageMarker = RAMN_CAN_ORIGIN_HOST; // Sending and Receiving are the most likely commands, so check for them first @@ -1161,8 +1161,8 @@ RAMN_Bool_t RAMN_CDC_ProcessSLCANBuffer(uint8_t* USBRxBuffer, uint32_t commandLe RAMN_USB_SendFromTask(USBRxBuffer,commandLength); #endif -#if defined(PROCESS_SLCAN_BY_DBC) - RAMN_DBC_ProcessCANMessage(CANTxHeader.Identifier,DLCtoUINT8(dlc),(RAMN_CANFrameData_t*)CANTxData); +#if defined(ENABLE_ECUA_HOST_INTERACTION) + RAMN_FDCAN_InjectHostRxMessage(&CANTxHeader,CANTxData); #endif } else RAMN_USB_SendFromTask((uint8_t*)"\a",1U); @@ -1216,8 +1216,8 @@ RAMN_Bool_t RAMN_CDC_ProcessSLCANBuffer(uint8_t* USBRxBuffer, uint32_t commandLe RAMN_USB_SendFromTask(USBRxBuffer,commandLength); #endif -#if defined(PROCESS_SLCAN_BY_DBC) - RAMN_DBC_ProcessCANMessage(CANTxHeader.Identifier,DLCtoUINT8(dlc),(RAMN_CANFrameData_t*)CANTxData); +#if defined(ENABLE_ECUA_HOST_INTERACTION) + RAMN_FDCAN_InjectHostRxMessage(&CANTxHeader,CANTxData); #endif } else RAMN_USB_SendFromTask((uint8_t*)"\a",1U); diff --git a/firmware/RAMNV1/Core/Src/ramn_gsusb.c b/firmware/RAMNV1/Core/Src/ramn_gsusb.c index 5d71ba66..55cf18c8 100644 --- a/firmware/RAMNV1/Core/Src/ramn_gsusb.c +++ b/firmware/RAMNV1/Core/Src/ramn_gsusb.c @@ -85,11 +85,24 @@ RAMN_Result_t RAMN_GSUSB_ProcessTX(FDCAN_TxHeaderTypeDef *canTxHeader, uint8_t * } frameData->can_id = canTxHeader->Identifier; + if (canTxHeader->IdType != FDCAN_STANDARD_ID) frameData->can_id |= CAN_EFF_FLAG; + if (canTxHeader->TxFrameType != FDCAN_DATA_FRAME) frameData->can_id |= CAN_RTR_FLAG; + frameData->echo_id = 0xFFFFFFFF; frameData->channel = 0; + frameData->flags = 0; frameData->can_dlc = canTxHeader->DataLength; - frameData->timestamp_us = 0; // timestamps are ignored on send - RAMN_memcpy(frameData->data, canRxData, frameData->can_dlc); + + if (canTxHeader->MessageMarker == RAMN_CAN_ORIGIN_HOST) + { + frameData->timestamp_us = 0; // Timestamps are ignored on host send + } + else + { + frameData->timestamp_us = (xTaskGetTickCount() * (1000000 /*us per sec*/ / configTICK_RATE_HZ)); + } + + if (!(frameData->can_id & CAN_RTR_FLAG)) RAMN_memcpy(frameData->data, canRxData, DLCtoUINT8(canTxHeader->DataLength)); // Send to task qret = xQueueSendToBack(RAMN_GSUSB_SendQueueHandle, &frameData, CAN_QUEUE_TIMEOUT); diff --git a/firmware/RAMNV1/Core/Src/ramn_j1939.c b/firmware/RAMNV1/Core/Src/ramn_j1939.c index e09f9ba8..f72f7adc 100644 --- a/firmware/RAMNV1/Core/Src/ramn_j1939.c +++ b/firmware/RAMNV1/Core/Src/ramn_j1939.c @@ -33,30 +33,40 @@ static uint32_t J1939_MakeId(uint8_t prio, uint8_t pf, uint8_t da, uint8_t sa) } /* - * J1939 NAME (8 bytes). + * J1939 NAME (8 bytes, transmitted Least Significant Byte (LSB) first per SAE J1939-81). + * + * CAN Data Byte layout (Little-Endian / LSB first): + * Byte 0: Identity Number bits 7-0 + * Byte 1: Identity Number bits 15-8 + * Byte 2: Identity Number bits 20-16 (bits 4-0), Manufacturer Code bits 2-0 (bits 7-5) + * Byte 3: Manufacturer Code bits 10-3 + * Byte 4: ECU Instance bits 2-0 (bits 2-0), Function Instance bits 4-0 (bits 7-3) + * Byte 5: Function (bits 7-0) + * Byte 6: Reserved bit 48 (bit 0, must be 0), Vehicle System bits 6-0 (bits 7-1) + * Byte 7: Vehicle System Instance bits 3-0 (bits 3-0), Industry Group bits 2-0 (bits 6-4), Arbitrary Address Capable (bit 7) */ static const uint8_t j1939_name[8] = { #if defined(TARGET_ECUA) - 0x10, 0x02, J1939_ECU_FUNCTION, 0x00, 0x0F, 0xA0, 0x00, 0x01 /* Identity=1, MC=125, Function=32 (Headway Ctrl) */ + 0x0A, 0xEC, 0xA0, 0x0F, 0x00, J1939_ECU_FUNCTION, 0x02, 0x10 /* Identity=0xEC0A, MC=125, Function=32 (Headway Ctrl) */ #elif defined(TARGET_ECUB) - 0x10, 0x02, J1939_ECU_FUNCTION, 0x00, 0x3F, 0x60, 0x00, 0x02 /* Identity=2, MC=507, Function=16 (Steering Ctrl) */ + 0x0B, 0xEC, 0x60, 0x3F, 0x00, J1939_ECU_FUNCTION, 0x02, 0x10 /* Identity=0xEC0B, MC=507, Function=52 (Chassis Ctrl) */ #elif defined(TARGET_ECUC) - 0x10, 0x02, J1939_ECU_FUNCTION, 0x00, 0x41, 0x40, 0x00, 0x03 /* Identity=3, MC=522, Function=80 (Powertrain Ctrl) */ + 0x0C, 0xEC, 0x40, 0x41, 0x00, J1939_ECU_FUNCTION, 0x02, 0x10 /* Identity=0xEC0C, MC=522, Function=80 (Powertrain Ctrl) */ #elif defined(TARGET_ECUD) - 0x10, 0x02, J1939_ECU_FUNCTION, 0x00, 0x1F, 0x60, 0x00, 0x04 /* Identity=4, MC=251, Function=26 (Body Ctrl) */ + 0x0D, 0xEC, 0x60, 0x1F, 0x00, J1939_ECU_FUNCTION, 0x02, 0x10 /* Identity=0xEC0D, MC=251, Function=26 (Body Ctrl) */ #endif }; /* ECU Identification string for PGN 64965 (fields delimited by '*') */ static const char j1939_ecu_id[] = #if defined(TARGET_ECUA) - "RAMN*ECU_A*0001*UNIT1*"; + "RAMN*ECU_A*EC0A*UNIT1*"; #elif defined(TARGET_ECUB) - "RAMN*ECU_B*0002*UNIT2*"; + "RAMN*ECU_B*EC0B*UNIT2*"; #elif defined(TARGET_ECUC) - "RAMN*ECU_C*0003*UNIT3*"; + "RAMN*ECU_C*EC0C*UNIT3*"; #elif defined(TARGET_ECUD) - "RAMN*ECU_D*0004*UNIT4*"; + "RAMN*ECU_D*EC0D*UNIT4*"; #else "RAMN*ECU_UNKNOWN*"; #endif diff --git a/firmware/RAMNV1/Core/Src/ramn_kwp2000.c b/firmware/RAMNV1/Core/Src/ramn_kwp2000.c index 8ff97076..9a837332 100644 --- a/firmware/RAMNV1/Core/Src/ramn_kwp2000.c +++ b/firmware/RAMNV1/Core/Src/ramn_kwp2000.c @@ -51,9 +51,17 @@ #define KWP_COMMAND_CONTROL_DTC_SETTINGS 0x85 #define KWP_COMMAND_RESPONSE_ON_EVENT 0x86 -// KWP Errors +// KWP Errors (ISO 14230-3) #define KWP_NRC_SERVICE_NOT_SUPPORTED 0x11 #define KWP_NRC_SUBFUNCTION_NOT_SUPPORTED_IF 0x12 +#define KWP_NRC_INCORRECT_MESSAGE_LENGTH 0x13 +#define KWP_NRC_CONDITIONS_NOT_CORRECT 0x22 +#define KWP_NRC_REQUEST_SEQUENCE_ERROR 0x24 +#define KWP_NRC_REQUEST_OUT_OF_RANGE 0x31 +#define KWP_NRC_SECURITY_ACCESS_DENIED 0x33 +#define KWP_NRC_INVALID_KEY 0x35 +#define KWP_NRC_EXCEED_NUMBER_OF_ATTEMPTS 0x36 +#define KWP_NRC_REQUIRED_TIME_DELAY_NOT_EXPIRED 0x37 // TODO: refactor module // Common pointer to avoid passing answer data as argument each sub-function diff --git a/firmware/RAMNV1/Core/Src/ramn_xcp.c b/firmware/RAMNV1/Core/Src/ramn_xcp.c index 0a4bdcd1..c6db9c2b 100644 --- a/firmware/RAMNV1/Core/Src/ramn_xcp.c +++ b/firmware/RAMNV1/Core/Src/ramn_xcp.c @@ -409,12 +409,12 @@ RAMN_Bool_t RAMN_XCP_ProcessRxCANMessage(const FDCAN_RxHeaderTypeDef* pHeader, c // Accept XCP over both addressing schemes regardless of the live traffic profile: // J1939 proprietary-A (PF 0xEF, physical, TSA 0x3F or 0x5A) or standard 11-bit XCP_RX_CANID. - if (pHeader->IdType == FDCAN_EXTENDED_ID && pf == 0xEF && da == J1939_ECU_SA) + if (pHeader->IdType == FDCAN_EXTENDED_ID && pf == 0xEF && da == J1939_ECU_XCP_SA) { - // Avoid ECUC (SA 0x5A) responding to itself if another ECU is using TSA 0x5A - if ((sa == 0x3F || sa == 0x5A) && (sa != J1939_ECU_SA)) + // Avoid responding to self if tester uses same SA + if ((sa == 0x3F || sa == 0x5A) && (sa != J1939_ECU_XCP_SA)) { - RAMN_XCP_TxMsgHeader.Identifier = J1939_UCAST_ID(prio, 0xEF00, sa, J1939_ECU_SA); + RAMN_XCP_TxMsgHeader.Identifier = J1939_UCAST_ID(prio, 0xEF00, sa, J1939_ECU_XCP_SA); RAMN_XCP_TxMsgHeader.IdType = FDCAN_EXTENDED_ID; matched = True; } diff --git a/scripts/tests/build_testing_libs.sh b/scripts/tests/build_testing_libs.sh index 12325d0b..d420a40d 100755 --- a/scripts/tests/build_testing_libs.sh +++ b/scripts/tests/build_testing_libs.sh @@ -28,7 +28,7 @@ touch scripts/tests/mocks/eeprom_emul_conf.h echo "Compiling Serdes Shared Library..." # Both codecs (RAMN_*_Default and RAMN_*_J1939) live in the single library; tests pick by suffix. -gcc $CFLAGS -D__MAIN_H $INCLUDES -o scripts/tests/librbd_can_db.so firmware/RAMNV1/Core/Src/ramn_can_database.c +gcc $CFLAGS -D__MAIN_H -include stddef.h -include string.h $INCLUDES -o scripts/tests/librbd_can_db.so firmware/RAMNV1/Core/Src/ramn_can_database.c echo "Compiling ECU-specific Diagnostic Shared Libraries..." diff --git a/scripts/tests/j1939_mapping_report.json b/scripts/tests/j1939_mapping_report.json index a7fc1249..0637a088 100644 --- a/scripts/tests/j1939_mapping_report.json +++ b/scripts/tests/j1939_mapping_report.json @@ -1,238 +1 @@ -[ - { - "PGN": "XBR(1024)", - "SA": "Headway Controller( 42)", - "DA": "Brakes - System Controller( 11)", - "Priority": "1", - "External Acceleration Demand": "1.1718750000611067e-05 [m/s2]", - "Bytes": "FEFAFFFFFFFFFFFF", - "RAMN_Signal": "Command_Brake", - "Tx_ECU": "ECUA", - "Rx_ECU": [ - "ECUC" - ] - }, - { - "PGN": "EBC1(61441)", - "SA": "Powertrain Control Module( 90)", - "DA": "All(255)", - "Brake Pedal Position": "0.0 [%]", - "Bytes": "FF00FFFFFFFFFFFF", - "RAMN_Signal": "Control_Brake", - "Tx_ECU": "ECUC", - "Rx_ECU": [ - "ECUA", - "ECUD" - ] - }, - { - "PGN": "TSC1(0)", - "SA": "Headway Controller( 42)", - "DA": "Engine #1( 0)", - "Priority": "3", - "Engine Requested Speed/Speed Limit": "0.0 [rpm]", - "Bytes": "FF0000FFFFFFFFFF", - "RAMN_Signal": "Command_Accel", - "Tx_ECU": "ECUA", - "Rx_ECU": [ - "ECUC" - ] - }, - { - "PGN": "EEC2(61443)", - "SA": "Powertrain Control Module( 90)", - "DA": "All(255)", - "Accelerator Pedal Position 1": "0.0 [%]", - "Bytes": "FF00FFFFFFFFFFFF", - "RAMN_Signal": "Control_Accel", - "Tx_ECU": "ECUC", - "Rx_ECU": [ - "ECUA" - ] - }, - { - "PGN": "EEC1(61444)", - "SA": "Headway Controller( 42)", - "DA": "All(255)", - "Priority": "3", - "Engine Speed": "0.0 [rpm]", - "Bytes": "FFFFFF0000FFFFFF", - "RAMN_Signal": "Status_Rpm", - "Tx_ECU": "ECUA", - "Rx_ECU": [ - "ECUB", - "ECUC", - "ECUD" - ] - }, - { - "PGN": "PropA(61184)", - "SA": "Headway Controller( 42)", - "DA": "Steering Controller( 19)", - "Priority": "2", - "Manufacturer Specific Information (PropA_PDU1)": "0xff0fffffffffffff", - "Bytes": "FF0FFFFFFFFFFFFF", - "RAMN_Signal": "Command_Steering", - "Tx_ECU": "ECUA", - "Rx_ECU": [ - "ECUB" - ] - }, - { - "PGN": "VDC2(61449)", - "SA": "Steering Controller( 19)", - "DA": "All(255)", - "Steering Wheel Angle": "-1.9999765624999988 [rad]", - "Bytes": "7F75FFFFFFFFFFFF", - "RAMN_Signal": "Control_Steering", - "Tx_ECU": "ECUB", - "Rx_ECU": [ - "ECUA" - ] - }, - { - "PGN": "TC1(256)", - "SA": "Headway Controller( 42)", - "DA": "Transmission #1( 3)", - "Priority": "3", - "Transmission Requested Gear": "0 [gear value]", - "Bytes": "FFFF7DFFFFFFFFFF", - "RAMN_Signal": "Command_Shift", - "Tx_ECU": "ECUA", - "Rx_ECU": [ - "ECUC" - ] - }, - { - "PGN": "ETC2(61445)", - "SA": "Transmission #1( 3)", - "DA": "All(255)", - "Transmission Current Gear": "0 [gear value]", - "Bytes": "FFFFFF7DFFFFFFFF", - "RAMN_Signal": "Control_Shift_Joystick", - "Tx_ECU": "ECUC", - "Rx_ECU": [ - "ECUA" - ] - }, - { - "PGN": "CM3(64980)", - "SA": "Powertrain Control Module( 90)", - "DA": "All(255)", - "Horn Switch": "1 (horn switch is on)", - "Bytes": "FFFFFFF7FFFFFFFF", - "RAMN_Signal": "Command_Horn", - "Tx_ECU": "ECUC", - "Rx_ECU": [ - "ECUA" - ] - }, - { - "PGN": "PropA(61184)", - "SA": "Headway Controller( 42)", - "DA": "Powertrain Control Module( 90)", - "Priority": "2", - "Manufacturer Specific Information (PropA_PDU1)": "0xffffffffffffffff", - "Bytes": "FFFFFFFFFFFFFFFF", - "RAMN_Signal": "Control_Horn", - "Tx_ECU": "ECUA", - "Rx_ECU": [ - "ECUC" - ] - }, - { - "PGN": "OEL(64972)", - "SA": "Shift Console - Primary( 5)", - "DA": "All(255)", - "Priority": "3", - "Turn Signal Switch": "0 (no turn being signaled)", - "Bytes": "FFF0FFFFFFFFFFFF", - "RAMN_Signal": "Command_Turnindicator", - "Tx_ECU": "ECUC", - "Rx_ECU": [ - "ECUD" - ] - }, - { - "PGN": "CCVS1(65265)", - "SA": "Headway Controller( 42)", - "DA": "All(255)", - "Parking Brake Switch": "1 (parking brake set)", - "Bytes": "F7FFFFFFFFFFFFFF", - "RAMN_Signal": "Command_Sidebrake", - "Tx_ECU": "ECUA", - "Rx_ECU": [ - "ECUB", - "ECUC" - ] - }, - { - "PGN": "B1(65274)", - "SA": "Brakes - Drive axle #1( 13)", - "DA": "All(255)", - "Parking Brake Actuator": "1 (parking brake actuator active)", - "Bytes": "FFFFFFFDFFFFFFFF", - "RAMN_Signal": "Control_Sidebrake", - "Tx_ECU": "ECUB", - "Rx_ECU": [ - "ECUA", - "ECUD" - ] - }, - { - "PGN": "LCMD(65089)", - "SA": "Chassis Controller #1( 71)", - "DA": "All(255)", - "Priority": "3", - "Running Light Command": "0 (de-activate)", - "Alternate Beam Head Light Command": "0 (de-activate)", - "Low Beam Head Light Command": "0 (de-activate)", - "High Beam Head Light Command": "0 (de-activate)", - "Bytes": "00FFFFFFFFFFFFFF", - "RAMN_Signal": "Command_Lights", - "Tx_ECU": "ECUB", - "Rx_ECU": [ - "ECUA", - "ECUB", - "ECUD" - ] - }, - { - "PGN": "CM3(64980)", - "SA": "Body Controller( 33)", - "DA": "All(255)", - "Operator Key Switch Accessory Power": "1 (accessory power state active)", - "Operator Key Switch Ignition Power": "0 (ignition state is not active)", - "Bytes": "FFFFC7FFFFFFFFFF", - "RAMN_Signal": "Control_Enginekey", - "Tx_ECU": "ECUD", - "Rx_ECU": [ - "ECUA", - "ECUC" - ] - }, - { - "PGN": "PropB(65282)", - "SA": "Shift Console - Primary( 5)", - "DA": "All(255)", - "Manufacturer Defined Usage (PropB_PDU2)": "0x01fcffffffffffff", - "Bytes": "01FCFFFFFFFFFFFF", - "RAMN_Signal": "Joystick_Buttons", - "Tx_ECU": "ECUC", - "Rx_ECU": [ - "ECUA" - ] - }, - { - "PGN": "PropB(65280)", - "SA": "Body Controller( 33)", - "DA": "All(255)", - "Manufacturer Defined Usage (PropB_PDU2)": "0xfa00000000000000", - "Bytes": "FA00000000000000", - "RAMN_Signal": "Control_Lights", - "Tx_ECU": "ECUD", - "Rx_ECU": [ - "ECUA" - ] - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/scripts/tests/mocks/main.h b/scripts/tests/mocks/main.h index 91320a04..5b9259f4 100644 --- a/scripts/tests/mocks/main.h +++ b/scripts/tests/mocks/main.h @@ -54,6 +54,10 @@ typedef struct { #define FDCAN_DLC_BYTES_0 0 #define FDCAN_DLC_BYTES_8 8 +#define RAMN_CAN_ORIGIN_BUS 0x00U +#define RAMN_CAN_ORIGIN_HOST 0xA1U +#define RAMN_CAN_ORIGIN_INTERNAL 0x02U + // ISO-TP Config #define ISOTP_RXBUFFER_SIZE 4096 #define ISOTP_TXBUFFER_SIZE 4096 diff --git a/scripts/tests/mocks/ramn_diag_mocks.c b/scripts/tests/mocks/ramn_diag_mocks.c index c231ea7f..b5825240 100644 --- a/scripts/tests/mocks/ramn_diag_mocks.c +++ b/scripts/tests/mocks/ramn_diag_mocks.c @@ -137,6 +137,15 @@ size_t xStreamBufferSend(StreamBufferHandle_t xStreamBuffer, const void *pvTxDat RAMN_KWP_ISOTPHandler.txSize = ans_size; RAMN_KWP_ISOTPHandler.txStatus = ISOTP_TX_TRANSFERRING; RAMN_KWP_Continue_TX(0); + // Complete any multi-frame transfer the same way a tester would: after the + // FirstFrame, feed a FlowControl "Continue To Send" and pump ConsecutiveFrames. + for (int guard = 0; (RAMN_KWP_ISOTPHandler.txStatus != ISOTP_TX_IDLE) && (guard < 8192); guard++) { + if (RAMN_KWP_ISOTPHandler.txStatus == ISOTP_TX_WAITING_FLAG) { + uint8_t fc[3] = {0x30, 0x00, 0x00}; // FlowControl: CTS, BS=0, STmin=0 + RAMN_ISOTP_ProcessRxMsg(&RAMN_KWP_ISOTPHandler, 3, fc, False, 0); + } + RAMN_KWP_Continue_TX(0); + } } kwp_expected = 0; } diff --git a/scripts/tests/ramn_firmware_bus.py b/scripts/tests/ramn_firmware_bus.py index 2e31dbc3..1de562db 100644 --- a/scripts/tests/ramn_firmware_bus.py +++ b/scripts/tests/ramn_firmware_bus.py @@ -120,10 +120,11 @@ def _tx_callback_handler(self, header_ptr, data_ptr): self.responses.append({ 'id': header.Identifier, 'data': data, - 'is_extended': bool(header.IdType == 1) + 'is_extended': bool(header.IdType == 1), + 'marker': header.MessageMarker, }) - def process_msg(self, can_id, data, is_extended=True, tick=0, is_fd=False): + def process_msg(self, can_id, data, is_extended=True, tick=0, is_fd=False, origin=0): """Feed a CAN message into the firmware's diagnostic stack.""" FDCAN_FD_CAN = 0x00200000 # matches mocks/main.h all_responses = [] @@ -133,6 +134,7 @@ def process_msg(self, can_id, data, is_extended=True, tick=0, is_fd=False): header.IdType = 1 if is_extended else 0 header.DataLength = len(data) header.FDFormat = FDCAN_FD_CAN if is_fd else 0 + header.IsFilterMatchingFrame = origin c_data = (ctypes.c_uint8 * len(data))(*data) diff --git a/scripts/tests/requirements.txt b/scripts/tests/requirements.txt new file mode 100644 index 00000000..afb0c2c2 --- /dev/null +++ b/scripts/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest>=8.0.0 +scapy>=2.5.0 +pretty_j1939>=0.0.5 diff --git a/scripts/tests/test_ecua_host_interaction.py b/scripts/tests/test_ecua_host_interaction.py new file mode 100644 index 00000000..92b605bd --- /dev/null +++ b/scripts/tests/test_ecua_host_interaction.py @@ -0,0 +1,102 @@ +import unittest +from ramn_firmware_bus import RAMNFirmwareBus + +# --------------------------------------------------------------------------- +# Constants matching firmware/RAMNV1/Core/Inc/ramn_canfd.h +# --------------------------------------------------------------------------- +RAMN_CAN_ORIGIN_BUS = 0x00 +RAMN_CAN_ORIGIN_HOST = 0xA1 +RAMN_CAN_ORIGIN_INTERNAL = 0x02 + +UDS_PHYS_REQ_ID = 0x7E0 +UDS_PHYS_RESP_ID = 0x7E8 +UDS_FUNC_REQ_ID = 0x7DF + +J1939_PGN_REQUEST = 59904 # 0xEA00 +J1939_PGN_ADDR_CLAIMED = 60928 # 0xEE00 +J1939_PGN_COMP_ID = 65259 # 0xFEEB +ECUA_J1939_SA = 42 # Headway Controller (0x2A) + + +class TestECUAHostInteraction(unittest.TestCase): + """ + Validates host PC interaction with ECU A over slcan / gs_usb software routing: + - Host-originated frames (origin=RAMN_CAN_ORIGIN_HOST) reach ECU A's diagnostic + and custom protocol stacks. + - Responses from ECU A are marked as internal transmissions (marker != HOST) + so they are forwarded to the USB host. + - Anti-storm behavior: local transmissions do not trigger runaway recursive loops. + """ + + def test_uds_physical_host_interaction(self): + """Host sends UDS Tester Present to ECU A (0x7E0) with ORIGIN_HOST.""" + bus = RAMNFirmwareBus('A', mode='std') + # UDS Tester Present (SF) + req_data = [0x02, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + + responses = bus.process_msg(UDS_PHYS_REQ_ID, req_data, is_extended=False, origin=RAMN_CAN_ORIGIN_HOST) + self.assertEqual(len(responses), 1, "ECU A did not respond to physical UDS request injected by host") + + resp = responses[0] + self.assertEqual(resp['id'], UDS_PHYS_RESP_ID) + self.assertFalse(resp['is_extended']) + # Positive response: 0x02, 0x7E, 0x00... + self.assertEqual(resp['data'][0], 0x02) + self.assertEqual(resp['data'][1], 0x7E) + # MessageMarker must NOT be RAMN_CAN_ORIGIN_HOST so it gets forwarded to USB + self.assertNotEqual(resp['marker'], RAMN_CAN_ORIGIN_HOST, + "Firmware response should not be marked as ORIGIN_HOST") + + def test_uds_functional_host_interaction_all_ecus(self): + """Host sends functional UDS Tester Present (0x7DF) with ORIGIN_HOST to network.""" + req_data = [0x02, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + + # ECU A processes via software loopback + bus_a = RAMNFirmwareBus('A', mode='std') + responses_a = bus_a.process_msg(UDS_FUNC_REQ_ID, req_data, is_extended=False, origin=RAMN_CAN_ORIGIN_HOST) + self.assertEqual(len(responses_a), 1, "ECU A did not respond to functional request") + self.assertEqual(responses_a[0]['id'], 0x7E8) + self.assertNotEqual(responses_a[0]['marker'], RAMN_CAN_ORIGIN_HOST) + + # ECU B, C, D receive from physical bus + expected_ids = {'B': 0x7E9, 'C': 0x7EA, 'D': 0x7EB} + for letter, resp_id in expected_ids.items(): + bus = RAMNFirmwareBus(letter, mode='std') + resps = bus.process_msg(UDS_FUNC_REQ_ID, req_data, is_extended=False, origin=RAMN_CAN_ORIGIN_BUS) + self.assertEqual(len(resps), 1, f"ECU {letter} did not respond to functional request") + self.assertEqual(resps[0]['id'], resp_id) + + def test_j1939_address_claim_request_by_host(self): + """Host sends Request for Address Claimed (PGN 60928) to ECU A (DA=42).""" + bus = RAMNFirmwareBus('A', mode='j1939') + # PGN Request: PDU Format 234 (0xEA), PDU Specific DA=42 (0x2A), SA=Host (e.g. 0xF9) + # Priority 6 -> CAN ID 0x18EA2AF9 + req_id = (6 << 26) | (0xEA << 16) | (ECUA_J1939_SA << 8) | 0xF9 + req_data = [0x00, 0xEE, 0x00] # PGN 60928 in little-endian + + responses = bus.process_msg(req_id, req_data, is_extended=True, origin=RAMN_CAN_ORIGIN_HOST) + self.assertTrue(len(responses) >= 1, "ECU A did not respond to J1939 Address Claimed request from host") + + # Response must be Address Claimed (PGN 60928 / 0xEE00) from SA 42 + addr_claim = next((r for r in responses if ((r['id'] >> 16) & 0xFF) == 0xEE), None) + self.assertIsNotNone(addr_claim, "Address Claimed response missing") + self.assertEqual(addr_claim['id'] & 0xFF, ECUA_J1939_SA, "Response SA mismatch") + self.assertNotEqual(addr_claim['marker'], RAMN_CAN_ORIGIN_HOST) + + def test_anti_storm_no_recursive_echo(self): + """Feeding ECU A's own response back into ECU A must not generate recursive frames.""" + bus = RAMNFirmwareBus('A', mode='std') + # ECU A's physical response ID + resp_data = [0x02, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + # Feed response back with INTERNAL origin + responses = bus.process_msg(UDS_PHYS_RESP_ID, resp_data, is_extended=False, origin=RAMN_CAN_ORIGIN_INTERNAL) + self.assertEqual(len(responses), 0, "ECU A must not respond to its own response frame (anti-storm violation)") + + def test_host_origin_suppression_tag(self): + """Verify host origin marker tag distinction.""" + self.assertNotEqual(RAMN_CAN_ORIGIN_HOST, RAMN_CAN_ORIGIN_BUS) + self.assertNotEqual(RAMN_CAN_ORIGIN_HOST, RAMN_CAN_ORIGIN_INTERNAL) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/tests/test_j1939_protocol.py b/scripts/tests/test_j1939_protocol.py index 46d6e215..b5e7a60c 100644 --- a/scripts/tests/test_j1939_protocol.py +++ b/scripts/tests/test_j1939_protocol.py @@ -49,17 +49,17 @@ # J1939 NAME per ECU: (function, identity_number) — all other fields zero ECU_NAME_FIELDS = { - 'A': {'function': 42, 'identity': 1}, - 'B': {'function': 19, 'identity': 2}, - 'C': {'function': 90, 'identity': 3}, - 'D': {'function': 33, 'identity': 4}, + 'A': {'function': 42, 'identity': 0xEC0A}, + 'B': {'function': 52, 'identity': 0xEC0B}, + 'C': {'function': 90, 'identity': 0xEC0C}, + 'D': {'function': 33, 'identity': 0xEC0D}, } ECU_ID_STRINGS = { - 'A': "RAMN*ECU_A*0001*UNIT1*", - 'B': "RAMN*ECU_B*0002*UNIT2*", - 'C': "RAMN*ECU_C*0003*UNIT3*", - 'D': "RAMN*ECU_D*0004*UNIT4*", + 'A': "RAMN*ECU_A*EC0A*UNIT1*", + 'B': "RAMN*ECU_B*EC0B*UNIT2*", + 'C': "RAMN*ECU_C*EC0C*UNIT3*", + 'D': "RAMN*ECU_D*EC0D*UNIT4*", } # --------------------------------------------------------------------------- diff --git a/scripts/tests/test_lighting_switch_encoding.py b/scripts/tests/test_lighting_switch_encoding.py index 886a2835..af4b46aa 100644 --- a/scripts/tests/test_lighting_switch_encoding.py +++ b/scripts/tests/test_lighting_switch_encoding.py @@ -36,7 +36,7 @@ def build_bigendian_lib(): includes = "-I scripts/tests/mocks/ -I firmware/RAMNV1/Core/Inc/" # -D__MAIN_H blocks the real main.h (same as build_testing_libs.sh) # -DUSE_BIG_ENDIAN_CAN matches the firmware's ramn_config.h setting - cflags = "-shared -fPIC -DCPYTHON_TESTING -D__MAIN_H -DUSE_BIG_ENDIAN_CAN" + cflags = "-shared -fPIC -DCPYTHON_TESTING -D__MAIN_H -DUSE_BIG_ENDIAN_CAN -include stddef.h -include string.h" src = "firmware/RAMNV1/Core/Src/ramn_can_database.c" cmd = f"gcc {cflags} {includes} -o {LIB_PATH} {src}" result = subprocess.run( diff --git a/scripts/tests/test_serdes_roundtrip.py b/scripts/tests/test_serdes_roundtrip.py index 6e981d46..26c1b4cf 100644 --- a/scripts/tests/test_serdes_roundtrip.py +++ b/scripts/tests/test_serdes_roundtrip.py @@ -54,8 +54,9 @@ def save_report(): - with open(REPORT_PATH, "w") as f: - json.dump(MAPPING_REPORT, f, indent=4) + if MAPPING_REPORT: + with open(REPORT_PATH, "w") as f: + json.dump(MAPPING_REPORT, f, indent=4) atexit.register(save_report) @@ -64,6 +65,9 @@ def save_report(): class RecordingDescriber: def __init__(self, signal_name, *args, **kwargs): self.signal_name = signal_name + da_json_path = kwargs.get("da_json") + if da_json_path and not os.path.exists(da_json_path): + pytest.skip(f"Skipping because database file '{da_json_path}' is missing.") self._describer = pretty_j1939.describe.get_describer(*args, **kwargs) def __call__(self, data, can_id): diff --git a/scripts/tests/test_turnindicator_encoding.py b/scripts/tests/test_turnindicator_encoding.py index f5a9cb10..30f6761e 100644 --- a/scripts/tests/test_turnindicator_encoding.py +++ b/scripts/tests/test_turnindicator_encoding.py @@ -35,7 +35,7 @@ def build_bigendian_lib(): USE_BIG_ENDIAN_CAN) to reproduce the on-target byte layout. """ includes = "-I scripts/tests/mocks/ -I firmware/RAMNV1/Core/Inc/" - cflags = "-shared -fPIC -DCPYTHON_TESTING -D__MAIN_H -DUSE_BIG_ENDIAN_CAN" + cflags = "-shared -fPIC -DCPYTHON_TESTING -D__MAIN_H -DUSE_BIG_ENDIAN_CAN -include stddef.h -include string.h" src = "firmware/RAMNV1/Core/Src/ramn_can_database.c" cmd = f"gcc {cflags} {includes} -o {LIB_PATH} {src}" result = subprocess.run(