diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c9d1cb --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# HIL Tester for PER + +## Running + +- Code in `./TestBench` runs on the Arduino + - Basically it just reads commands over the serial port and either executs them or writes messages back over the serial port + - To flash it, use the Arduino IDE +- Code in `./scripts` runs on your laptop + - It uses all the Python files + - Each file in `./scripts` can run a Pytest script to test some board on the car + - Do `./test.sh [filename]` to run the script, or no filename to run all of them + - (Note: have to run from the `./scripts` directory) +- Make sure you correctly set `firmware_path` in `./hil_params.json` to the path of the primary PER firmware repo! + +## Notes + +### Input vs Output + +- `AI`/`DI` = inputs to hil (reads from the car/other board -> Arduino -> laptop/Python) +- `AO`/`DO` = outputs from hil (writes from laptop/Python -> Arduino -> car/other board) \ No newline at end of file diff --git a/TestBench/DFRobot_MCP4725.cpp b/TestBench/DFRobot_MCP4725.cpp new file mode 100644 index 0000000..ae212f5 --- /dev/null +++ b/TestBench/DFRobot_MCP4725.cpp @@ -0,0 +1,394 @@ +/*! + * @file DFRobot_MCP4725.cpp + * @brief Implementation of the MCP4725 function library class definition + * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) + * @license The MIT License (MIT) + * @author [TangJie]](jie.tang@dfrobot.com) + * @version V1.0.0 + * @date 2018-01-15 + * @url https://github.com/DFRobot/DFRobot_MCP4725 + */ +#include "DFRobot_MCP4725.h" + +const PROGMEM uint16_t DACLookup_FullSine_5Bit[32] = +{ + 2048, 2447, 2831, 3185, 3495, 3750, 3939, 4056, + 4095, 4056, 3939, 3750, 3495, 3185, 2831, 2447, + 2048, 1648, 1264, 910, 600, 345, 156, 39, + 0, 39, 156, 345, 600, 910, 1264, 1648 +}; + +const PROGMEM uint16_t DACLookup_FullSine_6Bit[64] = +{ + 2048, 2248, 2447, 2642, 2831, 3013, 3185, 3346, + 3495, 3630, 3750, 3853, 3939, 4007, 4056, 4085, + 4095, 4085, 4056, 4007, 3939, 3853, 3750, 3630, + 3495, 3346, 3185, 3013, 2831, 2642, 2447, 2248, + 2048, 1847, 1648, 1453, 1264, 1082, 910, 749, + 600, 465, 345, 242, 156, 88, 39, 10, + 0, 10, 39, 88, 156, 242, 345, 465, + 600, 749, 910, 1082, 1264, 1453, 1648, 1847 +}; + +const PROGMEM uint16_t DACLookup_FullSine_7Bit[128] = +{ + 2048, 2148, 2248, 2348, 2447, 2545, 2642, 2737, + 2831, 2923, 3013, 3100, 3185, 3267, 3346, 3423, + 3495, 3565, 3630, 3692, 3750, 3804, 3853, 3898, + 3939, 3975, 4007, 4034, 4056, 4073, 4085, 4093, + 4095, 4093, 4085, 4073, 4056, 4034, 4007, 3975, + 3939, 3898, 3853, 3804, 3750, 3692, 3630, 3565, + 3495, 3423, 3346, 3267, 3185, 3100, 3013, 2923, + 2831, 2737, 2642, 2545, 2447, 2348, 2248, 2148, + 2048, 1947, 1847, 1747, 1648, 1550, 1453, 1358, + 1264, 1172, 1082, 995, 910, 828, 749, 672, + 600, 530, 465, 403, 345, 291, 242, 197, + 156, 120, 88, 61, 39, 22, 10, 2, + 0, 2, 10, 22, 39, 61, 88, 120, + 156, 197, 242, 291, 345, 403, 465, 530, + 600, 672, 749, 828, 910, 995, 1082, 1172, + 1264, 1358, 1453, 1550, 1648, 1747, 1847, 1947 +}; + +const PROGMEM uint16_t DACLookup_FullSine_8Bit[256] = +{ + 2048, 2098, 2148, 2198, 2248, 2298, 2348, 2398, + 2447, 2496, 2545, 2594, 2642, 2690, 2737, 2784, + 2831, 2877, 2923, 2968, 3013, 3057, 3100, 3143, + 3185, 3226, 3267, 3307, 3346, 3385, 3423, 3459, + 3495, 3530, 3565, 3598, 3630, 3662, 3692, 3722, + 3750, 3777, 3804, 3829, 3853, 3876, 3898, 3919, + 3939, 3958, 3975, 3992, 4007, 4021, 4034, 4045, + 4056, 4065, 4073, 4080, 4085, 4089, 4093, 4094, + 4095, 4094, 4093, 4089, 4085, 4080, 4073, 4065, + 4056, 4045, 4034, 4021, 4007, 3992, 3975, 3958, + 3939, 3919, 3898, 3876, 3853, 3829, 3804, 3777, + 3750, 3722, 3692, 3662, 3630, 3598, 3565, 3530, + 3495, 3459, 3423, 3385, 3346, 3307, 3267, 3226, + 3185, 3143, 3100, 3057, 3013, 2968, 2923, 2877, + 2831, 2784, 2737, 2690, 2642, 2594, 2545, 2496, + 2447, 2398, 2348, 2298, 2248, 2198, 2148, 2098, + 2048, 1997, 1947, 1897, 1847, 1797, 1747, 1697, + 1648, 1599, 1550, 1501, 1453, 1405, 1358, 1311, + 1264, 1218, 1172, 1127, 1082, 1038, 995, 952, + 910, 869, 828, 788, 749, 710, 672, 636, + 600, 565, 530, 497, 465, 433, 403, 373, + 345, 318, 291, 266, 242, 219, 197, 176, + 156, 137, 120, 103, 88, 74, 61, 50, + 39, 30, 22, 15, 10, 6, 2, 1, + 0, 1, 2, 6, 10, 15, 22, 30, + 39, 50, 61, 74, 88, 103, 120, 137, + 156, 176, 197, 219, 242, 266, 291, 318, + 345, 373, 403, 433, 465, 497, 530, 565, + 600, 636, 672, 710, 749, 788, 828, 869, + 910, 952, 995, 1038, 1082, 1127, 1172, 1218, + 1264, 1311, 1358, 1405, 1453, 1501, 1550, 1599, + 1648, 1697, 1747, 1797, 1847, 1897, 1947, 1997 +}; + +const PROGMEM uint16_t DACLookup_FullSine_9Bit[512] = +{ + 2048, 2073, 2098, 2123, 2148, 2174, 2199, 2224, + 2249, 2274, 2299, 2324, 2349, 2373, 2398, 2423, + 2448, 2472, 2497, 2521, 2546, 2570, 2594, 2618, + 2643, 2667, 2690, 2714, 2738, 2762, 2785, 2808, + 2832, 2855, 2878, 2901, 2924, 2946, 2969, 2991, + 3013, 3036, 3057, 3079, 3101, 3122, 3144, 3165, + 3186, 3207, 3227, 3248, 3268, 3288, 3308, 3328, + 3347, 3367, 3386, 3405, 3423, 3442, 3460, 3478, + 3496, 3514, 3531, 3548, 3565, 3582, 3599, 3615, + 3631, 3647, 3663, 3678, 3693, 3708, 3722, 3737, + 3751, 3765, 3778, 3792, 3805, 3817, 3830, 3842, + 3854, 3866, 3877, 3888, 3899, 3910, 3920, 3930, + 3940, 3950, 3959, 3968, 3976, 3985, 3993, 4000, + 4008, 4015, 4022, 4028, 4035, 4041, 4046, 4052, + 4057, 4061, 4066, 4070, 4074, 4077, 4081, 4084, + 4086, 4088, 4090, 4092, 4094, 4095, 4095, 4095, + 4095, 4095, 4095, 4095, 4094, 4092, 4090, 4088, + 4086, 4084, 4081, 4077, 4074, 4070, 4066, 4061, + 4057, 4052, 4046, 4041, 4035, 4028, 4022, 4015, + 4008, 4000, 3993, 3985, 3976, 3968, 3959, 3950, + 3940, 3930, 3920, 3910, 3899, 3888, 3877, 3866, + 3854, 3842, 3830, 3817, 3805, 3792, 3778, 3765, + 3751, 3737, 3722, 3708, 3693, 3678, 3663, 3647, + 3631, 3615, 3599, 3582, 3565, 3548, 3531, 3514, + 3496, 3478, 3460, 3442, 3423, 3405, 3386, 3367, + 3347, 3328, 3308, 3288, 3268, 3248, 3227, 3207, + 3186, 3165, 3144, 3122, 3101, 3079, 3057, 3036, + 3013, 2991, 2969, 2946, 2924, 2901, 2878, 2855, + 2832, 2808, 2785, 2762, 2738, 2714, 2690, 2667, + 2643, 2618, 2594, 2570, 2546, 2521, 2497, 2472, + 2448, 2423, 2398, 2373, 2349, 2324, 2299, 2274, + 2249, 2224, 2199, 2174, 2148, 2123, 2098, 2073, + 2048, 2023, 1998, 1973, 1948, 1922, 1897, 1872, + 1847, 1822, 1797, 1772, 1747, 1723, 1698, 1673, + 1648, 1624, 1599, 1575, 1550, 1526, 1502, 1478, + 1453, 1429, 1406, 1382, 1358, 1334, 1311, 1288, + 1264, 1241, 1218, 1195, 1172, 1150, 1127, 1105, + 1083, 1060, 1039, 1017, 995, 974, 952, 931, + 910, 889, 869, 848, 828, 808, 788, 768, + 749, 729, 710, 691, 673, 654, 636, 618, + 600, 582, 565, 548, 531, 514, 497, 481, + 465, 449, 433, 418, 403, 388, 374, 359, + 345, 331, 318, 304, 291, 279, 266, 254, + 242, 230, 219, 208, 197, 186, 176, 166, + 156, 146, 137, 128, 120, 111, 103, 96, + 88, 81, 74, 68, 61, 55, 50, 44, + 39, 35, 30, 26, 22, 19, 15, 12, + 10, 8, 6, 4, 2, 1, 1, 0, + 0, 0, 1, 1, 2, 4, 6, 8, + 10, 12, 15, 19, 22, 26, 30, 35, + 39, 44, 50, 55, 61, 68, 74, 81, + 88, 96, 103, 111, 120, 128, 137, 146, + 156, 166, 176, 186, 197, 208, 219, 230, + 242, 254, 266, 279, 291, 304, 318, 331, + 345, 359, 374, 388, 403, 418, 433, 449, + 465, 481, 497, 514, 531, 548, 565, 582, + 600, 618, 636, 654, 673, 691, 710, 729, + 749, 768, 788, 808, 828, 848, 869, 889, + 910, 931, 952, 974, 995, 1017, 1039, 1060, + 1083, 1105, 1127, 1150, 1172, 1195, 1218, 1241, + 1264, 1288, 1311, 1334, 1358, 1382, 1406, 1429, + 1453, 1478, 1502, 1526, 1550, 1575, 1599, 1624, + 1648, 1673, 1698, 1723, 1747, 1772, 1797, 1822, + 1847, 1872, 1897, 1922, 1948, 1973, 1998, 2023 +}; + +bool DFRobot_MCP4725::check_mcp4725() +{ + uint8_t error; + Wire.beginTransmission(_IIC_addr); + error = Wire.endTransmission(); + if(error == 0){ + return true; + }else{ + return false; + } +} +void DFRobot_MCP4725::init(uint8_t addr, uint16_t vRef) +{ + byte error; + _IIC_addr = addr; + _refVoltage = vRef; + _PowerMode = MCP4725_NORMAL_MODE; + Wire.begin(); + Wire.beginTransmission(_IIC_addr); + + Wire.endTransmission(); + /* + while(error) + { + Wire.beginTransmission(_IIC_addr); + + error = Wire.endTransmission(); + Serial.println("ERROR! Not found I2C device address "); + delay(500); + } + */ +} + +void DFRobot_MCP4725::setMode(uint8_t powerMode) +{ + _PowerMode = powerMode; + outputVoltage(_voltage); +} + +void DFRobot_MCP4725::outputVoltage( uint16_t voltage) +{ + uint16_t data = 0; + _voltage = voltage; + if(_voltage > _refVoltage) + { + Serial.print("ERROR! The input voltage is greater than the maximum voltage!"); + return ; + } + else + { + data = (uint16_t)(((float)_voltage / _refVoltage) * 4095); + + Wire.beginTransmission(_IIC_addr); + + Wire.write(MCP4725_Write_CMD | (_PowerMode << 1)); + + Wire.write(data / 16); + Wire.write((data % 16) << 4); + Wire.endTransmission(); + } +} + +void DFRobot_MCP4725::outputVoltageEEPROM( uint16_t voltage) +{ + uint16_t data = 0; + _voltage = voltage; + if(_voltage > _refVoltage) + { + Serial.print("ERROR! The input voltage is greater than the maximum voltage!"); + return ; + } + else + { + data = (uint16_t)(((float)_voltage / _refVoltage) * 4095); + + Wire.beginTransmission(_IIC_addr); + Wire.write(MCP4725_WriteEEPROM_CMD | (_PowerMode << 1)); + Wire.write(data / 16); + Wire.write((data % 16) << 4); + Wire.endTransmission(); + } +} + +void DFRobot_MCP4725::outputTriangle(uint16_t amp, uint16_t freq, uint16_t offset, uint8_t dutyCycle) +{ + uint64_t starttime; + uint64_t stoptime; + uint64_t looptime; + uint64_t frame; + uint16_t num = 64; + uint16_t up_num; + uint16_t down_num; + uint16_t maxV; + maxV=amp*(4096/(float)_refVoltage); + if(freq > 100){ + num = 16; + }else if(50 <= freq && freq <= 100){ + num = 32; + }else{ + num = 64; + } + frame = 1000000/(freq*num*2); + if(dutyCycle>100){ + dutyCycle = 100; + } + if(dutyCycle<0){ + dutyCycle=0; + } + up_num = (2*num)*((float)dutyCycle/100); + down_num = ((2*num) - up_num); +#ifdef TWBR + uint8_t twbrback = TWBR; + TWBR = ((F_CPU / 400000L) - 16) / 2; // Set I2C frequency to 400kHz +#endif + uint32_t counter; + uint32_t enterV; + + for (counter = 0; counter < (maxV-(maxV/up_num)-1); counter+=(maxV/up_num)) + { + starttime = micros(); + enterV=counter+(offset*(4096/(float)_refVoltage)); + if(enterV > 4095){ + enterV = 4095; + }else if(enterV < 0){ + enterV = 0; + } + Wire.beginTransmission(_IIC_addr); + Wire.write(MCP4725_Write_CMD | (_PowerMode << 1)); + Wire.write(enterV / 16); + Wire.write((enterV % 16) << 4); + Wire.endTransmission(); + stoptime = micros(); + looptime = stoptime-starttime; + while(looptime <= frame){ + stoptime = micros(); + looptime = stoptime-starttime; + } + } + for (counter = maxV-1; counter > (maxV/down_num); counter-=(maxV/down_num)) + { + starttime = micros(); + enterV=counter+(offset*(4096/(float)_refVoltage)); + if(enterV > 4095){ + enterV = 4095; + }else if(enterV < 0){ + enterV = 0; + } + Wire.beginTransmission(_IIC_addr); + Wire.write(MCP4725_Write_CMD | (_PowerMode << 1)); + Wire.write(enterV / 16); + Wire.write((enterV % 16) << 4); + Wire.endTransmission(); + stoptime = micros(); + looptime = stoptime-starttime; + while(looptime <= frame){ + stoptime = micros(); + looptime = stoptime-starttime; + } + } +#ifdef TWBR + TWBR = twbrback; +#endif +} + +void DFRobot_MCP4725::outputSin(uint16_t amp, uint16_t freq, uint16_t offset) +{ + uint64_t starttime; + uint64_t stoptime; + uint64_t looptime; + uint64_t frame; + uint16_t num=512; +#ifdef TWBR + uint8_t twbrback = TWBR; + TWBR = ((F_CPU / 400000L) - 16) / 2; // Set I2C frequency to 400kHz +#endif + int16_t data = 0; + + if(freq < 8){ + num = 512; + }else if( 8 <= freq && freq <= 16){ + num = 256; + }else if(16 < freq && freq < 33){ + num = 128; + }else if(33 <= freq && freq <= 68 ){ + num = 64; + }else{ + num = 32; + } + if(freq > 100){ + freq = 100; + } + frame = 1000000/(freq*num); + for(int i=0;i= 4095){ + data=4095; + } + Wire.beginTransmission(_IIC_addr); + Wire.write(MCP4725_Write_CMD); + Wire.write(data / 16); + Wire.write((data % 16) << 4); + Wire.endTransmission(); + stoptime = micros(); + looptime = stoptime-starttime; + while(looptime <= frame){ + stoptime = micros(); + looptime = stoptime-starttime; + } + } +#ifdef TWBR + TWBR = twbrback; +#endif +} diff --git a/TestBench/DFRobot_MCP4725.h b/TestBench/DFRobot_MCP4725.h new file mode 100644 index 0000000..a468074 --- /dev/null +++ b/TestBench/DFRobot_MCP4725.h @@ -0,0 +1,101 @@ +/*! + * @file DFRobot_MCP4725.h + * @brief Definition and explanation of the MCP4725 function library class + * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) + * @license The MIT License (MIT) + * @author [TangJie]](jie.tang@dfrobot.com) + * @version V1.0.0 + * @date 2018-01-15 + * @url https://github.com/DFRobot/DFRobot_MCP4725 + */ + +#if ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include + +#define MCP4725_Write_CMD 0x40 ///< Write data to the DAC address. +#define MCP4725_WriteEEPROM_CMD 0x60 ///< Write data to the DAC EEPROM address. + +///< The IIC address of MCP4725A0 may be 0x60 or 0x61, depending on the location of the dial code switch on the sensor. +#define MCP4725A0_IIC_Address0 0x60 +#define MCP4725A0_IIC_Address1 0x61 + +#define MCP4725_NORMAL_MODE 0 +#define MCP4725_POWER_DOWN_1KRES 1 +#define MCP4725_POWER_DOWN_100KRES 2 +#define MCP4725_POWER_DOWN_500KRES 3 + +class DFRobot_MCP4725{ +public: + /** + * @fn init + * @brief init MCP4725 device + * @param addr Init the IIC address. + * @param vRefSetting the base voltage of DAC must equal the power supply voltage, and the unit is millivolt. + * @return None + */ + void init(uint8_t addr, uint16_t vRef); + + /** + * @fn setMode + * @brief set power mode + * @param powerMode Set power mode,three are normal mode and power down mode. + * @n The following are three modes of power down. + * @n MCP4725_POWER_DOWN_1KRES 1 kΩ resistor to ground + * @n MCP4725_POWER_DOWN_100KRES 100 kΩ resistor to ground + * @n MCP4725_POWER_DOWN_500KRES 500 kΩ resistor to ground + * @return None + */ + void setMode(uint8_t powerMode); + + /** + * @fn outputVoltage + * @brief Output voltage value range 0-5000mv. + * @param voltage Voltage value, range 0-5000, unit millivolt. + * @return None + */ + void outputVoltage(uint16_t voltage); + + /** + * @fn outputVoltageEEPROM + * @brief Output voltage value range 0-5000mv and write to the EEPROM, + * @n meaning that the DAC will retain the current voltage output + * @n after power-down or reset. + * @param voltage Voltage value, range 0-5000, unit millivolt. + * @return None + */ + void outputVoltageEEPROM(uint16_t voltage); + + /** + * @fn outputSin + * @brief Output a sine wave. + * @param amp amp value, output sine wave amplitude range 0-5000mv + * @param freq freq value, output sine wave frequency + * @param offset offset value, output sine wave DC offset + * @return None + */ + void outputSin(uint16_t amp, uint16_t freq, uint16_t offset); + + /** + * @fn outputTriangle + * @brief Output a sine wave. + * @param amp amp value, output triangular wave amplitude range 0-5000mv + * @param freq freq value, output the triangle wave frequency + * @param offset offset value, output the DC offset of the triangle wave + * @param dutyCycle dutyCycle value, set the rising percentage of the triangle wave as a percentage of the entire cycle. + * @n Value range 0-100 (0 for only the decline of 100, only the rise of paragraph) + * @return None + */ + void outputTriangle(uint16_t amp, uint16_t freq, uint16_t offset, uint8_t dutyCycle); + + private: + bool check_mcp4725(); + uint8_t _IIC_addr; + uint8_t _PowerMode; + uint16_t _refVoltage; + uint16_t _voltage; +}; diff --git a/testbench/MCP4021.cpp b/TestBench/MCP4021.cpp similarity index 100% rename from testbench/MCP4021.cpp rename to TestBench/MCP4021.cpp diff --git a/testbench/MCP4021.h b/TestBench/MCP4021.h similarity index 100% rename from testbench/MCP4021.h rename to TestBench/MCP4021.h diff --git a/TestBench/TestBench.ino b/TestBench/TestBench.ino new file mode 100644 index 0000000..c103572 --- /dev/null +++ b/TestBench/TestBench.ino @@ -0,0 +1,189 @@ + +#include +#include "MCP4021.h" + +// #define STM32 +#ifdef STM32 + #define SERIAL SerialUSB + #define HAL_DAC_MODULE_ENABLED 1 +#else + #define SERIAL Serial +#endif + +const int TESTER_ID = 1; + +#define DAC + +#ifdef STM32 + #ifdef DAC + #warning "Can't have both DAC and STM32 enabled" + #endif +#endif + +#ifdef DAC + #include "DFRobot_MCP4725.h" + #define NUM_DACS 2 + + DFRobot_MCP4725 dacs[NUM_DACS]; + uint8_t dac_power_down[NUM_DACS]; + const uint16_t dac_vref = 4095; +#endif + +#define DIGIPOT_EN +#ifdef DIGIPOT_EN + const uint8_t DIGIPOT_UD_PIN = 7; + const uint8_t DIGIPOT_CS1_PIN = 22; // A4 + const uint8_t DIGIPOT_CS2_PIN = 23; // A5 + + MCP4021 digipot1(DIGIPOT_CS1_PIN, DIGIPOT_UD_PIN, false); // initialize Digipot 1 + MCP4021 digipot2(DIGIPOT_CS2_PIN, DIGIPOT_UD_PIN, false); // initialize Digipot 2 +#endif + +enum GpioCommand { + READ_ADC = 0, + READ_GPIO = 1, + WRITE_DAC = 2, + WRITE_GPIO = 3, + READ_ID = 4, + WRITE_POT = 5, +}; + +int TO_READ[] = { // Parrallel to GpioCommand + 2, // READ_ADC - command, pin + 2, // READ_GPIO - command, pin + 4, // WRITE_DAC - command, pin, value (2 bytes) + 3, // WRITE_GPIO - command, pin, value + 1, // READ_ID - command + 3, // WRITE_POT - command, pin, value +}; + +// 4 = max(TO_READ) +uint8_t data[4] = {-1, -1, -1, -1}; +int data_index = 0; +bool data_ready = false; + + +void setup() { + SERIAL.begin(115200); + +#ifdef DIGIPOT_EN + // Setting up Digipot 1 + digipot1.setup(); + digipot1.begin(); + + // Setting up Digipot 2 + digipot2.setup(); + digipot2.begin(); +#endif +#ifdef DAC + dacs[0].init(0x62, dac_vref); + dacs[1].init(0x63, dac_vref); + dacs[0].setMode(MCP4725_POWER_DOWN_500KRES); + dacs[1].setMode(MCP4725_POWER_DOWN_500KRES); + dac_power_down[0] = 1; + dac_power_down[1] = 1; +#endif +} + +void error(String error_string) { + SERIAL.write(0xFF); + SERIAL.write(0xFF); + SERIAL.println(error_string); +} + + +void loop() { + if (data_ready) { + data_ready = false; + data_index = 0; + + GpioCommand command = (GpioCommand) data[0]; + + switch (command) { + case GpioCommand::READ_ADC: { + int pin = data[1]; + // if (pin <= ANALOG_PIN_COUNT) + if (1) { + int val = analogRead(pin); + SERIAL.write((val >> 8) & 0xFF); + SERIAL.write(val & 0xFF); + } else { + error("ADC PIN COUNT EXCEEDED"); + } + break; + } + case GpioCommand::READ_GPIO: { + int pin = data[1]; + #ifdef DAC + if (pin >= 200 && pin < 200 + NUM_DACS) { + dacs[pin - 200].setMode(MCP4725_POWER_DOWN_500KRES); + dac_power_down[pin - 200] = 1; + SERIAL.write(0x01); + } else + #endif + { + pinMode(pin, INPUT); + int val = digitalRead(pin); + SERIAL.write(val & 0xFF); + } + break; + } + case GpioCommand::WRITE_DAC: { + int pin = data[1]; + int value = (data[2] << 8) | data[3]; + #ifdef DAC + if (pin >= 200 && pin < 200 + NUM_DACS) { + if (dac_power_down[pin-200]) { + dacs[pin-200].setMode(MCP4725_NORMAL_MODE); + dac_power_down[pin - 200] = 0; + } + dacs[pin - 200].outputVoltage(value); + } + #endif + #ifdef STM32 + // 4 and 5 have DAC on f407 + pinMode(pin, OUTPUT); + analogWrite(pin, value & 0xFF); // max val 255 + #endif + + break; + } + case GpioCommand::WRITE_GPIO: { + int pin = data[1]; + int value = data[2]; + pinMode(pin, OUTPUT); + digitalWrite(pin, value); + break; + } + case GpioCommand::READ_ID: { + SERIAL.write(TESTER_ID); + break; + } + case GpioCommand::WRITE_POT: { + int pin = data[1]; + int value = data[2]; + #ifdef DIGIPOT_EN + if (pin == 1) { + digipot1.setTap((uint8_t) value); + } else if (pin == 2) { + digipot2.setTap((uint8_t) value); + } else + #endif + { + error("POT PIN COUNT EXCEEDED"); + } + break; + } + } + } else { + if (SERIAL.available() > 0) { + data[data_index] = SERIAL.read(); + data_index++; + + uint8_t command = data[0]; + if (data_index == TO_READ[command]) { + data_ready = true; + } + } + } +} diff --git a/_old/hil.py b/_old/hil.py new file mode 100644 index 0000000..80deb23 --- /dev/null +++ b/_old/hil.py @@ -0,0 +1,245 @@ +import hil.utils as utils +import os +import signal +import sys +from hil.pin_mapper import PinMapper +from hil.hil_devices.hil_device import HilDevice +from hil.hil_devices.serial_manager import SerialManager +from hil.components.component import Component + +from hil.communication.can_bus import CanBus, BusSignal +from hil.communication.daq_protocol import DaqProtocol +from hil.communication.daq_protocol import DAQPin +from hil.communication.daq_protocol import DAQVariable + +""" HIL TESTER """ + +JSON_CONFIG_SCHEMA_PATH = "" +CONFIG_PATH = "../configurations" + +NET_MAP_PATH = "../net_maps" +PIN_MAP_PATH = "../pin_maps" + +PARAMS_PATH = "../hil_params.json" + + +class HIL(): + def __init__(self): + utils.initGlobals() + self.components: dict[str, Component] = {} + self.dut_connections: dict[str, dict[str, dict[str, tuple[str, str]]]] = {} + self.hil_devices: dict[str, HilDevice] = {} + self.serial_manager: SerialManager = SerialManager() + self.hil_params: dict = utils.load_json_config(PARAMS_PATH, None) + self.can_bus: CanBus = None + utils.hilProt = self + signal.signal(signal.SIGINT, signal_int_handler) + # self.global_failed_checks = [] + # self.global_test_names = [] + # self.global_check_count = 0 # multiple checks within a test + # self.global_test_count = 0 + # self.global_failed_checks = [] + # self.global_test_names = [] + # self.global_check_count = 0 # multiple checks within a test + # self.global_test_count = 0 + + def init_can(self) -> None: + config = self.hil_params + self.daq_config = utils.load_json_config(os.path.join(config['firmware_path'], config['daq_config_path']), os.path.join(config['firmware_path'], config['daq_schema_path'])) + self.can_config = utils.load_json_config(os.path.join(config['firmware_path'], config['can_config_path']), os.path.join(config['firmware_path'], config['can_schema_path'])) + + print(f"PATH: {os.path.join(config['firmware_path'], config['dbc_path'])}") + self.can_bus = CanBus(os.path.join(config['firmware_path'], config['dbc_path']), config['default_ip'], self.can_config) + self.daq_protocol = DaqProtocol(self.can_bus, self.daq_config) + + self.can_bus.connect() + self.can_bus.start() + + def load_pin_map(self, net_map: str, pin_map: str) -> None: + net_map_f = os.path.join(NET_MAP_PATH, net_map) + pin_map_f = os.path.join(PIN_MAP_PATH, pin_map) + + self.pin_map = PinMapper(net_map_f) + self.pin_map.load_mcu_pin_map(pin_map_f) + + def clear_components(self) -> None: + """ Reset HIL""" + for c in self.components.values(): + c.shutdown() + self.components = {} + + def clear_hil_devices(self) -> None: + self.hil_devices = {} + self.serial_manager.close_devices() + + def shutdown(self) -> None: + print(f"{utils.bcolors.OKCYAN}HIL shutdown START{utils.bcolors.ENDC}") + self.clear_components() + self.clear_hil_devices() + self.stop_can() + print(f"{utils.bcolors.OKGREEN}HIL shutdown END{utils.bcolors.OKGREEN}") + + def stop_can(self) -> None: + if not self.can_bus: return + print(f"{utils.bcolors.OKCYAN}HIL stop_can START{utils.bcolors.ENDC}") + + if self.can_bus.connected: + self.can_bus.connected = False + self.can_bus.join() + # while(not self.can_bus.isFinished()): + # # wait for bus receive to finish + # pass + self.can_bus.disconnect_bus() + print(f"{utils.bcolors.OKGREEN}HIL stop_can END{utils.bcolors.ENDC}") + + def load_config(self, config_name: str) -> None: + config = utils.load_json_config(os.path.join(CONFIG_PATH, config_name), None) # TODO: validate w/ schema + + # TODO: support joining configs + + # Load hil_devices + self.load_hil_devices(config['hil_devices']) + + # Setup corresponding components + self.load_connections(config['dut_connections']) + + def load_connections(self, dut_connections: dict) -> None: + self.dut_connections = {} + # Dictionary format: + # [board][connector][pin] = (hil_device, port) + for board_connections in dut_connections: + board_name = board_connections['board'] + if not board_name in self.dut_connections: + self.dut_connections[board_name] = {} + for c in board_connections['harness_connections']: + connector = c['dut']['connector'] + pin = str(c['dut']['pin']) + hil_port = (c['hil']['device'], c['hil']['port']) + if not connector in self.dut_connections[board_name]: + self.dut_connections[board_name][connector] = {} + self.dut_connections[board_name][connector][pin] = hil_port + + def add_component(self, board: str, net: str, mode: str) -> Component: + # If board is a HIL device, net is expected to be port name + # If board is a DUT device, net is expected to be a net name from the board + if board in self.hil_devices: + hil_con = (board, net) + else: + hil_con = self.get_hil_device_connection(board, net) + comp_name = '.'.join([board, net]) + if not comp_name in self.components: + comp = Component(comp_name, hil_con, mode, self) + self.components[comp_name] = comp + else: + utils.log_warning(f"Component {comp_name} already exists") + return self.components[comp_name] + + def load_hil_devices(self, hil_devices: dict) -> None: + self.clear_hil_devices() + self.serial_manager.discover_devices() + for hil_device in hil_devices: + if self.serial_manager.port_exists(hil_device["id"]): + self.hil_devices[hil_device['name']] = HilDevice(hil_device['name'], hil_device['type'], hil_device['id'], self.serial_manager) + else: + self.handle_error(f"Failed to discover HIL device {hil_device['name']} with id {hil_device['id']}") + + def get_hil_device(self, name: str) -> HilDevice: + if name in self.hil_devices: + return self.hil_devices[name] + else: + self.handle_error(f"HIL device {name} not recognized") + + def get_hil_device_connection(self, board: str, net: str) -> tuple[str, str]: + """ Converts dut net to hil port name """ + if not board in self.dut_connections: + self.handle_error(f"No connections to {board} found in configuration.") + board_cons = self.dut_connections[board] + + net_cons = self.pin_map.get_net_connections(board, net) + for c in net_cons: + connector = c[0] + pin = c[1] + if connector in board_cons: + if pin in board_cons[connector]: + return board_cons[connector][pin] + utils.log_warning(f"Unable to find dut connection to net {net} on board {board}") + utils.log_warning(f"The net {net} is available on {board} via ...") + utils.log_warning(net_cons) + self.handle_error(f"Connect dut to {net} on {board}.") + + def din(self, board: str, net: str) -> Component: + return self.add_component(board, net, 'DI') + + def dout(self, board: str, net: str) -> Component: + return self.add_component(board, net, 'DO') + + def ain(self, board: str, net: str) -> Component: + return self.add_component(board, net, 'AI') + + def aout(self, board: str, net: str) -> Component: + return self.add_component(board, net, 'AO') + + def pot(self, board: str, net: str) -> Component: + return self.add_component(board, net, 'POT') + + def pwm(self, board: str, net: str) -> Component: + return self.add_component(board, net, 'PWM') + + def daq_var(self, board: str, var_name: str) -> DAQVariable: + try: + return utils.signals[utils.b_str][board][f"daq_response_{board.upper()}"][var_name] + except KeyError as e: + self.handle_error(f"Unable to locate DAQ variable {var_name} of {board}") + + def can_var(self, board: str, message_name: str, signal_name: str) -> BusSignal: + try: + return utils.signals[utils.b_str][board][message_name][signal_name] + except KeyError: + self.handle_error(f"Unable to locate CAN signal {signal_name} of message {message_name} of board {board}") + + def mcu_pin(self, board: str, net: str) -> DAQPin: + bank, pin = self.pin_map.get_mcu_pin(board, net) + if bank == None: + self.handle_error(f"Failed to get mcu pin for {board} net {net}") + return DAQPin(net, board, bank, pin) + + # def start_test(self, name): + # print(f"{utils.bcolors.OKCYAN}Starting {name}{utils.bcolors.ENDC}") + # self.curr_test = name + # self.curr_test_fail_count = 0 + # self.curr_test_count = 0 + # self.global_test_count = self.global_test_count + 1 + # self.global_test_names.append(name) + + # def check(self, stat, check_name): + # stat_str = "PASS" if stat else "FAIL" + # stat_clr = utils.bcolors.OKGREEN if stat else utils.bcolors.FAIL + # print(f"{self.curr_test + ' - ' + check_name:<50}: {stat_clr+'['+stat_str+']'+utils.bcolors.ENDC:>10}") + # if (not stat): + # self.curr_test_fail_count = self.curr_test_fail_count + 1 + # self.global_failed_checks.append((self.curr_test,check_name)) + # self.curr_test_count = self.curr_test_count + 1 + # self.global_check_count = self.global_check_count + 1 + # return stat + + # def check_within(self, val1, val2, thresh, check_name): + # self.check(abs(val1 - val2) <= thresh, check_name) + + # def end_test(self): + # print(f"{utils.bcolors.OKCYAN}{self.curr_test} failed {self.curr_test_fail_count} out of {self.curr_test_count} checks{utils.bcolors.ENDC}") + + def handle_error(self, msg: str) -> None: + utils.log_error(msg) + self.shutdown() + exit(0) + + +def signal_int_handler(signum, frame) -> None: + utils.log("Received signal interrupt, shutting down") + if (utils.hilProt): + utils.hilProt.shutdown() + sys.exit(0) + +# if __name__ == "__main__": +# hil = HIL() +# hil.load_config("config_test.json") diff --git a/scripts/config_dash.py b/_old/old_scripts/config_dash.py similarity index 100% rename from scripts/config_dash.py rename to _old/old_scripts/config_dash.py diff --git a/_old/old_scripts/rules_constants.py b/_old/old_scripts/rules_constants.py new file mode 100644 index 0000000..425beba --- /dev/null +++ b/_old/old_scripts/rules_constants.py @@ -0,0 +1,16 @@ +# NOTE: each constant in this file shall reference a rule + +# IMD +R_IMD_RESISTANCE_RATIO = 500 # EV.7.6.3 Ohms / Volt +R_IMD_MAX_TRIP_TIME_S = 30.0 # IN.4.4.2 +R_IMD_TRIP_TEST_RESISTANCE_PERCENT = 0.5 # IN.4.4.2 50% of response value + +# BSPD EV.7.7 +R_BSPD_MAX_TRIP_TIME_S = 0.5 # EV.7.7.2 +R_BSPD_POWER_THRESH_W = 5000 # EV.7.7.2 + +# Precharge and Discharge EV.5.6 +R_PCHG_V_BAT_THRESH = 0.90 # EV.5.6.1.a + +# TSAL EV.5.9 +R_TSAL_HV_V = 60.0 # T.9.1.1 diff --git a/_old/old_scripts/test.sh b/_old/old_scripts/test.sh new file mode 100755 index 0000000..9a919dd --- /dev/null +++ b/_old/old_scripts/test.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# If no file passed, run on all files +if [ "$#" -ne 1 ]; then + echo "Running tests on all files..." + pytest --cache-clear --no-header -v +else + echo "Running tests on $1..." + pytest --cache-clear --no-header -v "$1" +fi \ No newline at end of file diff --git a/_old/old_scripts/test_abox.py b/_old/old_scripts/test_abox.py new file mode 100644 index 0000000..042024a --- /dev/null +++ b/_old/old_scripts/test_abox.py @@ -0,0 +1,272 @@ +from os import sys, path +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) +from hil import HIL +import utils +import time +from rules_constants import * +from vehicle_constants import * + + +def test_abox_ams(hil): + # Begin the test + hil.start_test(test_abox_ams.__name__) + + # Outputs + den = hil.dout("a_box", "Discharge Enable") + csafe = hil.dout("a_box", "Charger Safety") + bms_override = hil.daq_var("a_box", "bms_daq_override") + bms_stat = hil.daq_var("a_box", "bms_daq_stat") + + # Inputs + chrg_stat = hil.din("a_box", "BMS Status Charger") + main_stat = hil.din("a_box", "BMS Status PDU") + + bms_override.state = 1 + + for i in range(0, 8): + dchg_set = bool(i & 0x1) + chg_set = bool(i & 0x2) + bms_set = bool(i & 0x4) + exp_chrg = not (chg_set or bms_set) + exp_dchg = not (dchg_set or bms_set) + + den.state = dchg_set + csafe.state = chg_set + bms_stat.state = bms_set + print(f"Combo {i}") + time.sleep(0.1) + hil.check(chrg_stat.state == exp_chrg, f"Chrg stat {exp_chrg}") + hil.check(main_stat.state == exp_dchg, f"Main stat {exp_dchg}") + + bms_override.state = 0 + + hil.end_test() + +def test_isense(hil): + # Begin the test + hil.start_test(test_isense.__name__) + + # Outputs + ch1_raw = hil.aout("a_box", "Isense_Ch1_raw") + + # Inputs + ch1_filt = hil.ain("a_box", "ISense Ch1") + + # Need to test voltage divider transfer function correct + for v in [0.0, DHAB_S124_MIN_OUT_V, DHAB_S124_OFFSET_V, 3.2, DHAB_S124_MAX_OUT_V, 5.0]: + ch1_raw.state = v + time.sleep(1) + exp_out = ABOX_DHAB_CH1_DIV.div(v) + input(f"enter to meas, set to {v}, expected {exp_out}") + meas = ch1_filt.state + print(f"isense expected: {exp_out}V, measured: {meas}V") + hil.check_within(meas, exp_out, 0.05, f"Isense v={v:.3}") + + ch1_raw.hiZ() + time.sleep(0.01) + hil.check_within(ch1_filt.state, 0.0, 0.05, f"Isense float pulled down") + + hil.end_test() + +RLY_ON = 0 +RLY_OFF = 1 +RLY_DLY = 0.01 # Mechanicl relay takes time to transition + +def test_precharge(hil): + # Begin the test + hil.start_test(test_precharge.__name__) + + # Outputs + n_pchg_cmplt = hil.dout("a_box", "NotPrechargeComplete") + sdc = hil.dout("a_box", "SDC") + bat_p = hil.dout("a_box", "Batt+") + + # Inputs + resistor = hil.din("a_box", "NetK1_4") # To precharge resistor + + bat_p.state = RLY_ON + + print("Combo 1") + n_pchg_cmplt.state = 0 + sdc.state = RLY_OFF + time.sleep(RLY_DLY) + hil.check(resistor.state == 0, "Resistor disconnected") + + print("Combo 2") + n_pchg_cmplt.state = 1 + sdc.state = RLY_OFF + time.sleep(RLY_DLY) + hil.check(resistor.state == 0, "Resistor disconnected") + + print("Combo 3") + n_pchg_cmplt.state = 1 + sdc.state = RLY_ON + time.sleep(RLY_DLY) + hil.check(resistor.state == 1, "Resistor connected") + + print("Combo 4") + n_pchg_cmplt.state = 0 + sdc.state = RLY_ON + time.sleep(RLY_DLY) + hil.check(resistor.state == 0, "Resistor disconnected") + + # Duration test + time.sleep(1) + n_pchg_cmplt.state = 1 + sdc.state = RLY_ON + time.sleep(RLY_DLY) + hil.check(resistor.state == 1, "Duration init") + time.sleep(9) + hil.check(resistor.state == 1, "Duration mid") + n_pchg_cmplt.state = 0 + time.sleep(RLY_DLY) + hil.check(resistor.state == 0, "Duration end") + + hil.end_test() + +SUPPLY_VOLTAGE = 24.0 +TIFF_DLY = 0.3 + +def test_tiffomy(hil): + # Begin the test + hil.start_test(test_tiffomy.__name__) + + # Outputs + bat_p = hil.dout("a_box", "Batt+") + + # Inputs + vbat = hil.ain("a_box", "VBatt") + imd_hv = hil.din("a_box", "Batt+_Fused") + + # NOTE: the IMD test confirms that the relay closed + # This is a bit redundant of the tiffomy voltage measurement + + utils.log_warning(f"Assuming supply = {SUPPLY_VOLTAGE} V") + utils.log_warning(f"Do not reverse polarity Vbat, it will kill Arduino ADC") + input("Click enter to acknowledge or ctrl+c to cancel") + + bat_p.state = RLY_OFF + time.sleep(TIFF_DLY) + hil.check_within(vbat.state, 0.0, 0.1, "TIff off") + hil.check(imd_hv.state == 0, "IMD HV off") + + bat_p.state = RLY_ON + time.sleep(TIFF_DLY) + exp = SUPPLY_VOLTAGE + #input("press enter, tiff should be getting volts") + meas = tiff_lv_to_hv(vbat.state) + print(f"Tiff HV reading: {meas} V, expect: {SUPPLY_VOLTAGE} V") + hil.check_within(meas, exp, 2.5, "Tiff on") + hil.check(imd_hv.state == 1, "IMD HV on") + + hil.end_test() + + +def test_tmu(hil): + # Begin the test + hil.start_test(test_tmu.__name__) + + # Outputs + tmu_a_do = hil.dout("a_box", "TMU_1") + tmu_b_do = hil.dout("a_box", "TMU_2") + tmu_c_do = hil.dout("a_box", "TMU_3") + tmu_d_do = hil.dout("a_box", "TMU_4") + + daq_override = hil.daq_var("a_box", "tmu_daq_override") + daq_therm = hil.daq_var("a_box", "tmu_daq_therm") + + # Inputs + mux_a = hil.din("a_box", "MUX_A_NON_ISO") + mux_b = hil.din("a_box", "MUX_B_NON_ISO") + mux_c = hil.din("a_box", "MUX_C_NON_ISO") + mux_d = hil.din("a_box", "MUX_D_NON_ISO") + + tmu_a_ai = hil.daq_var("a_box", "tmu_1") + tmu_b_ai = hil.daq_var("a_box", "tmu_2") + tmu_c_ai = hil.daq_var("a_box", "tmu_3") + tmu_d_ai = hil.daq_var("a_box", "tmu_4") + + daq_therm.state = 0 + daq_override.state = 1 + + # mux line test + for i in range(0,16): + daq_therm.state = i + time.sleep(0.05) + hil.check(mux_a.state == bool(i & 0x1), f"Mux A test {i}") + hil.check(mux_b.state == bool(i & 0x2), f"Mux B test {i}") + hil.check(mux_c.state == bool(i & 0x4), f"Mux C test {i}") + hil.check(mux_d.state == bool(i & 0x8), f"Mux D test {i}") + + daq_override.state = 0 + + TMU_TOLERANCE = 100 + TMU_HIGH_VALUE = 1970 #2148 + + # thermistors + for i in range(0,16): + tmu_a_do.state = bool(i & 0x1) + tmu_b_do.state = bool(i & 0x2) + tmu_c_do.state = bool(i & 0x4) + tmu_d_do.state = bool(i & 0x8) + time.sleep(1.0) + a = int(tmu_a_ai.state) + b = int(tmu_b_ai.state) + c = int(tmu_c_ai.state) + d = int(tmu_d_ai.state) + print(f"Readings at therm={i}: {a}, {b}, {c}, {d}") + hil.check_within(a, TMU_HIGH_VALUE if (i & 0x1) else 0, TMU_TOLERANCE, f"TMU 1 test {i}") + hil.check_within(b, TMU_HIGH_VALUE if (i & 0x2) else 0, TMU_TOLERANCE, f"TMU 2 test {i}") + hil.check_within(c, TMU_HIGH_VALUE if (i & 0x4) else 0, TMU_TOLERANCE, f"TMU 3 test {i}") + hil.check_within(d, TMU_HIGH_VALUE if (i & 0x8) else 0, TMU_TOLERANCE, f"TMU 4 test {i}") + + + hil.end_test() + +def test_imd(hil): + hil.start_test(test_imd.__name__) + + # Outputs + imd_out = hil.dout('a_box', 'IMD_Status') + + # Inputs + imd_in = hil.din('a_box', 'IMD_STATUS_LV_COMP') + imd_mcu = hil.mcu_pin('a_box', 'IMD_STATUS_LV_COMP') + + + imd_out.state = RLY_OFF + time.sleep(RLY_DLY) + + hil.check(imd_in.state == 0, 'IMD LV OFF') + hil.check(imd_mcu.state == 0, 'IMD MCU OFF') + + imd_out.state = RLY_ON + time.sleep(RLY_DLY) + + hil.check(imd_in.state == 1, 'IMD LV ON') + hil.check(imd_mcu.state == 1, 'IMD MCU ON') + + imd_out.state = RLY_OFF + time.sleep(RLY_DLY) + + hil.check(imd_in.state == 0, 'IMD LV BACK OFF') + hil.check(imd_mcu.state == 0, 'IMD MCU BACK OFF') + + hil.end_test() + + +if __name__ == "__main__": + hil = HIL() + hil.load_config("config_abox_bench.json") + hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + hil.init_can() + + test_abox_ams(hil) + test_isense(hil) + test_precharge(hil) + test_tiffomy(hil) + test_tmu(hil) + test_imd(hil) + + hil.shutdown() diff --git a/_old/old_scripts/test_charger.py b/_old/old_scripts/test_charger.py new file mode 100644 index 0000000..87a6fa4 --- /dev/null +++ b/_old/old_scripts/test_charger.py @@ -0,0 +1,147 @@ +from os import sys, path +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) +from hil import HIL +import hil.utils as utils +import time +from rules_constants import * +from vehicle_constants import * + +AMS_STAT_OKAY = 1 +AMS_STAT_TRIP = 0 +AMS_CTRL_OKAY = 1 +AMS_CTRL_TRIP = 0 + +def reset_ams(ams_stat): + ams_stat.state = AMS_STAT_OKAY + +IMD_STAT_OKAY = 1 +IMD_STAT_TRIP = 0 +def reset_imd(imd_stat): + imd_stat.state = IMD_STAT_OKAY + +power = None + +CYCLE_POWER_OFF_DELAY = 2.0 +CYCLE_POWER_ON_DELAY = 3.0 + +def cycle_power(): + power.state = 1 + time.sleep(CYCLE_POWER_OFF_DELAY) + power.state = 0 + time.sleep(CYCLE_POWER_ON_DELAY) + + +IMD_RC_MIN_TRIP_TIME_S = IMD_STARTUP_TIME_S +IMD_RC_MAX_TRIP_TIME_S = R_IMD_MAX_TRIP_TIME_S - IMD_MEASURE_TIME_S +IMD_CTRL_OKAY = 1 +IMD_CTRL_TRIP = 0 + +def test_imd(hil): + # Begin the test + hil.start_test(test_imd.__name__) + + # Outputs + imd_stat = hil.dout("Charger", "IMD_STATUS") + + # Outputs to set SDC status to okay + ams_stat = hil.dout("Charger", "BMS_STATUS") + + # Inputs + imd_ctrl = hil.din("Main_Module", "SDC_FINAL") # assuming AMS closed + + # Set other SDC nodes to okay + reset_ams(ams_stat) + + # IMD Fault + reset_imd(imd_stat) + cycle_power() + hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + time.sleep(1) + imd_stat.state = IMD_STAT_TRIP + t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) + print(f"Target trip time: [{IMD_RC_MIN_TRIP_TIME_S}, {IMD_RC_MAX_TRIP_TIME_S}]") + hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + imd_stat.state = IMD_STAT_OKAY + time.sleep(IMD_RC_MAX_TRIP_TIME_S * 1.1) + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + + # IMD Fault on Power On + reset_imd(imd_stat) + imd_stat.state = IMD_STAT_TRIP + cycle_power() + time.sleep(IMD_RC_MAX_TRIP_TIME_S) + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + + # IMD Floating + reset_imd(imd_stat) + imd_stat.hiZ() + cycle_power() + t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) + hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + + hil.end_test() + +def test_ams(hil): + # Begin the test + hil.start_test(test_ams.__name__) + + # Outputs + ams_stat = hil.dout("Charger", "BMS_STATUS") + + + # Outputs to set SDC status to okay + imd_stat = hil.dout("Charger", "IMD_STATUS") + + # Inputs + ams_ctrl = hil.din("Main_Module", "SDC_FINAL") # assuming AMS closed + + # Set other SDC nodes to okay + reset_imd(imd_stat) + + # AMS Fault + reset_ams(ams_stat) + cycle_power() + hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + time.sleep(1) + ams_stat.state = AMS_STAT_TRIP + t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) + hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + ams_stat.state = AMS_STAT_OKAY + time.sleep(AMS_MAX_TRIP_DELAY_S * 1.1) + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + + # AMS Fault on Power On + reset_ams(ams_stat) + ams_stat.state = AMS_STAT_TRIP + cycle_power() + time.sleep(AMS_MAX_TRIP_DELAY_S) + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + + # AMS Floating + reset_ams(ams_stat) + ams_stat.hiZ() + cycle_power() + t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) + hil.check(0 <= t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + + hil.end_test() + + +if __name__ == "__main__": + hil = HIL() + + hil.load_config("config_charger.json") + hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + #hil.init_can() + + power = hil.dout("RearTester", "RLY1") + + test_imd(hil) + test_ams(hil) + + hil.shutdown() diff --git a/_old/old_scripts/test_collector.py b/_old/old_scripts/test_collector.py new file mode 100644 index 0000000..b1ee509 --- /dev/null +++ b/_old/old_scripts/test_collector.py @@ -0,0 +1,58 @@ +from os import sys, path +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) +from hil import HIL +import utils +import time + +def test_collector(hil): + # Begin the test + hil.start_test(test_collector.__name__) + + # Outputs + m1 = hil.dout("Collector", "MUX_A") + m2 = hil.dout("Collector", "MUX_B") + m3 = hil.dout("Collector", "MUX_C") + m4 = hil.dout("Collector", "MUX_D") + + # Inputs + to = hil.ain("Collector", "TEMP_OUT") + + tolerance_v = 0.1 # volts + current_res = 9100.0 # ohms + pullup_res = 4700.0 # ohms + test_voltage = 3.3 # volts + pullup_voltage = 5 # volts + num_therm = 10 + + test_voltage = (pullup_voltage / (current_res + pullup_res)) * current_res + + utils.log_warning(test_voltage) + + for thermistor in range(num_therm): + print(f"Place test input on thermistor {thermistor}. Press Enter when ready") + input("") + + for i in range(num_therm): + m1.state = i & 0x1 + m2.state = i & 0x2 + m3.state = i & 0x4 + m4.state = i & 0x8 + time.sleep(0.01) + + if (i == thermistor): + hil.check_within(to.state, test_voltage, tolerance_v, f"Input on therm {thermistor}, selecting {i}") + else: + hil.check_within(to.state, pullup_voltage, tolerance_v, f"Input on therm {thermistor}, selecting {i}") + print(to.state) + + # End the test + hil.end_test() + +if __name__ == "__main__": + hil = HIL() + hil.load_config("config_collector_bench.json") + hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + test_collector(hil) + + hil.shutdown() diff --git a/_old/old_scripts/test_main_base.py b/_old/old_scripts/test_main_base.py new file mode 100644 index 0000000..133eb27 --- /dev/null +++ b/_old/old_scripts/test_main_base.py @@ -0,0 +1,463 @@ +from os import sys, path +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) +from hil import HIL +import utils +import time +from rules_constants import * +from vehicle_constants import * + +AMS_STAT_OKAY = 1 +AMS_STAT_TRIP = 0 +AMS_CTRL_OKAY = 1 +AMS_CTRL_TRIP = 0 + +def reset_ams(ams_stat): + ams_stat.state = AMS_STAT_OKAY + +def set_bspd_current(ch1, current): + ch1.state = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(current)) + +def reset_bspd(fail, stat, ch1): + fail.state = 0 + stat.state = 0 + set_bspd_current(ch1, 0.0) + +IMD_STAT_OKAY = 1 +IMD_STAT_TRIP = 0 +def reset_imd(imd_stat): + imd_stat.state = IMD_STAT_OKAY + +def reset_pchg(v_bat, v_mc): + print(f"Setting v_bat to {tiff_hv_to_lv(ACCUM_NOM_V)}") + v_bat.state = tiff_hv_to_lv(ACCUM_NOM_V) + print(f"Setting v_mc to {tiff_hv_to_lv(0.0)}") + v_mc.state = tiff_hv_to_lv(0.0) + +def reset_tsal(v_mc): + v_mc.state = tiff_hv_to_lv(0.0) + +power = None + +CYCLE_POWER_ON_DELAY = 0.1 + +def cycle_power(): + power.state = 1 + time.sleep(0.75) + power.state = 0 + time.sleep(CYCLE_POWER_ON_DELAY) + +def test_precharge(hil): + # Begin the test + hil.start_test(test_precharge.__name__) + + # Outputs + v_bat = hil.aout("Main_Module", "VBatt") + v_mc = hil.aout("Main_Module", "Voltage MC Transducer") + + # Inputs + pchg_cmplt = hil.mcu_pin("Main_Module", "PrechargeComplete_Prot") + not_pchg_cmplt_delayed = hil.din("Main_Module", "NotPrechargeCompleteSchmitt") + # v_bat_mcu = hil.daq_var("Main_Module", "Varname") # TODO + # v_mc_mcu = hil.daq_var("Main_Module", "Varname") # TODO + # pchg_mux = hil.daq_var("Main_Module", "Varname") # TODO + + # Initial State + reset_pchg(v_bat, v_mc) + time.sleep(2.5) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on startup") + hil.check(not_pchg_cmplt_delayed.state == 1, "Not precharge complete delayed high on startup") + + # Check delay + v_mc.state = tiff_hv_to_lv(ACCUM_NOM_V) + t = utils.measure_trip_time(not_pchg_cmplt_delayed, PCHG_COMPLETE_DELAY_S*3, is_falling=True) + hil.check(not_pchg_cmplt_delayed.state == 0, "Precharge complete delayed") + hil.check_within(t, PCHG_COMPLETE_DELAY_S, 0.25, f"Precharge complete delay of {t:.3}s close to expected {PCHG_COMPLETE_DELAY_S}s") + + # Find threshold at nominal pack voltage + for v in [ACCUM_MIN_V, ACCUM_NOM_V, ACCUM_MAX_V]: + reset_pchg(v_bat, v_mc) + print(f"Testing precharge threshold at V_bat = {v}") + v_bat.state = tiff_hv_to_lv(v) + v_mc.state = tiff_hv_to_lv(v*0.8) + time.sleep(0.01) + hil.check(pchg_cmplt.state == 0, "Precharge Complete Low at Initial State") + + start = tiff_hv_to_lv(v*0.8) + stop = tiff_hv_to_lv(v) + step = tiff_hv_to_lv(1) + thresh = utils.measure_trip_thresh(v_mc, start, stop, step, 0.01, + pchg_cmplt, is_falling=0) + thresh_hv = tiff_lv_to_hv(thresh) + print(f"Precharge triggered at {thresh_hv / v * 100:.4}% ({thresh_hv:.5}V) of vbat={v}.") + hil.check_within(thresh_hv / v, R_PCHG_V_BAT_THRESH, 0.03, f"Precharge threshold of {R_PCHG_V_BAT_THRESH*100}% at vbat = {v}V") + v_mc.state = tiff_hv_to_lv(v) + time.sleep(0.25) + hil.check(pchg_cmplt.state == 1, f"Precharge completed at vbat = {v}V") + + + # Floating conditions (check never precharge complete) + reset_pchg(v_bat, v_mc) + v_bat.hiZ() + v_mc.state = tiff_hv_to_lv(0) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc 0V") + v_mc.state = tiff_hv_to_lv(ACCUM_MAX_V) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc max V") + + reset_pchg(v_bat, v_mc) + v_mc.hiZ() + v_bat.state = tiff_hv_to_lv(ACCUM_MIN_V) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat min, v_mc float") + v_bat.state = tiff_hv_to_lv(ACCUM_MAX_V) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat max, v_mc float") + + reset_pchg(v_bat, v_mc) + v_bat.hiZ() + v_mc.hiZ() + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc float") + + # TODO: software precharge validity checks (make precharge take forever) + hil.end_test() + +def test_bspd(hil): + # Begin the test + hil.start_test(test_bspd.__name__) + + # Outputs + brk_fail = hil.dout("Main_Module", "Brake Fail") + brk_stat = hil.dout("Main_Module", "Brake Status") + c_sense = hil.aout("Main_Module", "Current Sense C1") + + # Outputs to set SDC status to okay + ams_stat = hil.dout("Main_Module", "BMS-Status-Main") + imd_stat = hil.dout("Main_Module", "IMD_Status") + + # Inputs + bspd_ctrl = hil.din("Main_Module", "SDC3") # Assuming IMD and AMS closed + + # Set other SDC nodes to okay + reset_ams(ams_stat) + reset_imd(imd_stat) + + # Brake Fail + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + brk_fail.state = 1 + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + brk_fail.state = 0 + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + + # Brake Fail on Power On + reset_bspd(brk_fail, brk_stat, c_sense) + # Manual power cycle, setting brk_fail on before + # Will cause power to feed back into system + power.state = 1 + time.sleep(2) + brk_fail.state = 1 + power.state = 0 + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + + # Current no brake + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + time.sleep(2) # TODO: I am not sure why this fails, but oh well + set_bspd_current(c_sense, 75) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + # time.sleep(100) + + hil.check(bspd_ctrl.state == 1, "Current no brake") + + # Current Sense Short to Ground + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.state = 0.0 + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + set_bspd_current(c_sense, 0.0) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + + # Current Sense Short to 5V + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.state = ABOX_DHAB_CH1_DIV.div(5.0) + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + set_bspd_current(c_sense, 0.0) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + + # Braking + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + brk_stat.state = 1 + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Brake no current") + + # Lowest current required to trip at + min_trip_current = R_BSPD_POWER_THRESH_W / ACCUM_MAX_V + set_bspd_current(c_sense, min_trip_current) + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + + # Measure braking with current threshold + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + brk_stat.state = 1 + + hil.check(bspd_ctrl.state == 1, "Power On") + start = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(0.0)) + stop = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(DHAB_S124_CH1_MAX_A)) + step = 0.1 + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + thresh_amps = dhab_ch1_v_to_a(ABOX_DHAB_CH1_DIV.reverse(thresh)) + print(f"Current while braking threshold: {thresh}V = {thresh_amps}A") + hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + + # Determine the current sense short to gnd threshold + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + set_bspd_current(c_sense, DHAB_S124_CH1_MIN_A) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Min output current okay") + start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MIN_OUT_V) + stop = 0.0 + step = -0.1 + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + print(f"Short to ground threshold: {thresh}V") + hil.check(stop < (thresh) < start, "Current short to ground threshold") + + # Determine the current sense short to 5V threshold + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + time.sleep(2) + + set_bspd_current(c_sense, DHAB_S124_CH1_MAX_A) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Max output current okay") + start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MAX_OUT_V) + stop = ABOX_DHAB_CH1_DIV.div(5.0) + step = 0.01 + + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + print(f"Short to 5V threshold: {thresh}V") + hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + print(stop) + print(start) + hil.check(start < (thresh) <= stop, "Current short to 5V threshold") + + # Floating current + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.hiZ() + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Floating current") + + # Floating brake_fail + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + brk_fail.hiZ() + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Floating brake fail") + + # Floating brake_stat + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power() + hil.check(bspd_ctrl.state == 1, "Power On") + brk_stat.hiZ() + set_bspd_current(c_sense, min_trip_current) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Floating brake status") + + # End the test + hil.end_test() + + +IMD_RC_MIN_TRIP_TIME_S = IMD_STARTUP_TIME_S +IMD_RC_MAX_TRIP_TIME_S = R_IMD_MAX_TRIP_TIME_S - IMD_MEASURE_TIME_S +IMD_CTRL_OKAY = 1 +IMD_CTRL_TRIP = 0 + +def test_imd(hil): + # Begin the test + hil.start_test(test_imd.__name__) + + # Outputs + imd_stat = hil.dout("Main_Module", "IMD_Status") + + # Outputs to set SDC status to okay + ams_stat = hil.dout("Main_Module", "BMS-Status-Main") + brk_fail = hil.dout("Main_Module", "Brake Fail") + brk_stat = hil.dout("Main_Module", "Brake Status") + c_sense = hil.aout("Main_Module", "Current Sense C1") + + # Inputs + imd_ctrl = hil.din("Main_Module", "SDC3") # assuming AMS and BSPD closed + + # Set other SDC nodes to okay + reset_ams(ams_stat) + reset_bspd(brk_fail, brk_stat, c_sense) + + # IMD Fault + reset_imd(imd_stat) + cycle_power() + hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + time.sleep(1) + imd_stat.state = IMD_STAT_TRIP + t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) + print(f"Target trip time: [{IMD_RC_MIN_TRIP_TIME_S}, {IMD_RC_MAX_TRIP_TIME_S}]") + hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + imd_stat.state = IMD_STAT_OKAY + time.sleep(IMD_RC_MAX_TRIP_TIME_S * 1.1) + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + + # IMD Fault on Power On + reset_imd(imd_stat) + imd_stat.state = IMD_STAT_TRIP + cycle_power() + time.sleep(IMD_RC_MAX_TRIP_TIME_S) + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + + # IMD Floating + reset_imd(imd_stat) + imd_stat.hiZ() + cycle_power() + t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) + hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + + hil.end_test() + +def test_ams(hil): + # Begin the test + hil.start_test(test_ams.__name__) + + # Outputs + ams_stat = hil.dout("Main_Module", "BMS-Status-Main") + + # Outputs to set SDC status to okay + imd_stat = hil.dout("Main_Module", "IMD_Status") + brk_fail = hil.dout("Main_Module", "Brake Fail") + brk_stat = hil.dout("Main_Module", "Brake Status") + c_sense = hil.aout("Main_Module", "Current Sense C1") + + # Inputs + ams_ctrl = hil.din("Main_Module", "SDC3") # assumes IMD and BSPD closed + + # Set other SDC nodes to okay + reset_imd(imd_stat) + reset_bspd(brk_fail, brk_stat, c_sense) + + # AMS Fault + reset_ams(ams_stat) + cycle_power() + hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + time.sleep(1) + ams_stat.state = AMS_STAT_TRIP + t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) + hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + ams_stat.state = AMS_STAT_OKAY + time.sleep(AMS_MAX_TRIP_DELAY_S * 1.1) + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + + # AMS Fault on Power On + reset_ams(ams_stat) + ams_stat.state = AMS_STAT_TRIP + cycle_power() + time.sleep(AMS_MAX_TRIP_DELAY_S) + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + + # AMS Floating + reset_ams(ams_stat) + ams_stat.hiZ() + cycle_power() + t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) + hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + + hil.end_test() + +def test_tsal(hil): + + # Begin the test + hil.start_test(test_tsal.__name__) + + # Outputs + v_mc = hil.aout("Main_Module", "Voltage MC Transducer") + + # Inputs + tsal = hil.din("Main_Module", "TSAL+") + lval = hil.din("Main_Module", "LVAL+") + + # Initial State + reset_tsal(v_mc) + time.sleep(0.2) + # No need to power cycle + + hil.check(lval.state == 1, "LVAL on at v_mc = 0") + hil.check(tsal.state == 0, "TSAL off at v_mc = 0") + + time.sleep(5) + hil.check(lval.state == 1, "LVAL stays on") + + v_mc.state = tiff_hv_to_lv(ACCUM_MIN_V) + time.sleep(0.1) + + hil.check(lval.state == 0, f"LVAL off at {ACCUM_MIN_V:.4} V") + hil.check(tsal.state == 1, f"TSAL on at {ACCUM_MIN_V:.4} V") + + reset_tsal(v_mc) + time.sleep(0.2) + hil.check(lval.state == 1, f"LVAL turns back on") + + start = tiff_hv_to_lv(0.0) + stop = tiff_hv_to_lv(R_TSAL_HV_V * 1.5) + step = tiff_hv_to_lv(1) + thresh = utils.measure_trip_thresh(v_mc, start, stop, step, + 0.01, + tsal, is_falling=False) + thresh = tiff_lv_to_hv(thresh) + print(f"TSAL on at {thresh:.4} V") + hil.check_within(thresh, R_TSAL_HV_V, 4, f"TSAL trips at {R_TSAL_HV_V:.4} +-4") + hil.check(lval.state == 0, f"LVAL off V") + hil.check(tsal.state == 1, f"TSAL on V") + + hil.end_test() + + +if __name__ == "__main__": + hil = HIL() + + hil.load_config("config_main_base_bench.json") + hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + hil.init_can() + + power = hil.dout("Arduino2", "RLY1") + + test_precharge(hil) + test_bspd(hil) + test_imd(hil) + test_ams(hil) + test_tsal(hil) + + hil.shutdown() diff --git a/_old/old_scripts/test_main_sdc.py b/_old/old_scripts/test_main_sdc.py new file mode 100644 index 0000000..c4ad102 --- /dev/null +++ b/_old/old_scripts/test_main_sdc.py @@ -0,0 +1,302 @@ +from os import sys, path +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) +from hil import HIL +import utils +import time +from rules_constants import * +from vehicle_constants import * + +# SETUP for CSENSE +# Current Sensor (DHAB) -> ABOX V Divider -> MAIN_SDC + +DAC_GAIN = 1 + 4.7 / 4.7 - 0.05 + +def cycle_power(pow): + pow.state = 0 + time.sleep(0.75) + pow.state = 1 + +def set_bspd_current(ch1, current): + v = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(current)) + ch1.state = v / DAC_GAIN + +def reset_bspd(fail, stat, ch1): + fail.state = 0 + stat.state = 0 + set_bspd_current(ch1, 0.0) + +def test_bspd(hil): + # Begin the test + hil.start_test(test_bspd.__name__) + + # Outputs + brk_fail = hil.dout("MainSDC", "Brake Fail") + brk_stat = hil.dout("MainSDC", "Brake Status") + c_sense = hil.aout("MainSDC", "Current Sense C1") + pow = hil.dout("MainSDC", "5V_Crit") + + # Inputs + bspd_ctrl = hil.din("MainSDC", "BSPD_Control") + + # Brake Fail + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + brk_fail.state = 1 + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + brk_fail.state = 0 + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + + # Brake Fail on Power On + reset_bspd(brk_fail, brk_stat, c_sense) + brk_fail.state = 1 + cycle_power(pow) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + + # Current no brake + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + time.sleep(2) # TODO: I am not sure why this fails, but oh well + set_bspd_current(c_sense, 75) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + # time.sleep(100) + + hil.check(bspd_ctrl.state == 1, "Current no brake") + + # Current Sense Short to Ground + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.state = 0.0 + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + set_bspd_current(c_sense, 0.0) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + + # Current Sense Short to 5V + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.state = ABOX_DHAB_CH1_DIV.div(5.0) / DAC_GAIN + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + set_bspd_current(c_sense, 0.0) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + + # Braking + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + brk_stat.state = 1 + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Brake no current") + + # Lowest current required to trip at + min_trip_current = R_BSPD_POWER_THRESH_W / ACCUM_MAX_V + set_bspd_current(c_sense, min_trip_current) + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + + # Measure braking with current threshold + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + brk_stat.state = 1 + + hil.check(bspd_ctrl.state == 1, "Power On") + start = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(0.0)) / DAC_GAIN + stop = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(DHAB_S124_CH1_MAX_A)) / DAC_GAIN + step = 0.1 / DAC_GAIN + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + thresh *= DAC_GAIN + thresh_amps = dhab_ch1_v_to_a(ABOX_DHAB_CH1_DIV.reverse(thresh)) + print(f"Current while braking threshold: {thresh}V = {thresh_amps}A") + hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + + # Determine the current sense short to gnd threshold + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + set_bspd_current(c_sense, DHAB_S124_CH1_MIN_A) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Min output current okay") + start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MIN_OUT_V) / DAC_GAIN + stop = 0.0 + step = -0.1 / DAC_GAIN + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + thresh *= DAC_GAIN + print(f"Short to ground threshold: {thresh}V") + hil.check(stop < (thresh / DAC_GAIN) < start, "Current short to ground threshold") + + # Determine the current sense short to 5V threshold + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + time.sleep(2) + + set_bspd_current(c_sense, DHAB_S124_CH1_MAX_A) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Max output current okay") + start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MAX_OUT_V) / DAC_GAIN + stop = ABOX_DHAB_CH1_DIV.div(5.0) / DAC_GAIN + step = 0.01 / DAC_GAIN + + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + thresh *= DAC_GAIN + print(f"Short to 5V threshold: {thresh}V") + hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + print(stop * DAC_GAIN) + print(start * DAC_GAIN) + hil.check(start < (thresh / DAC_GAIN) <= stop, "Current short to 5V threshold") + + # Floating current + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.hiZ() + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Floating current") + + # Floating brake_fail + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + brk_fail.hiZ() + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Floating brake fail") + + # Floating brake_stat + reset_bspd(brk_fail, brk_stat, c_sense) + cycle_power(pow) + hil.check(bspd_ctrl.state == 1, "Power On") + brk_stat.hiZ() + set_bspd_current(c_sense, min_trip_current) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Floating brake status") + + # End the test + hil.end_test() + + +IMD_RC_MIN_TRIP_TIME_S = IMD_STARTUP_TIME_S +IMD_RC_MAX_TRIP_TIME_S = R_IMD_MAX_TRIP_TIME_S - IMD_MEASURE_TIME_S +IMD_STAT_OKAY = 1 +IMD_STAT_TRIP = 0 +IMD_CTRL_OKAY = 1 +IMD_CTRL_TRIP = 0 + +def reset_imd(imd_stat): + imd_stat.state = IMD_STAT_OKAY + +def test_imd(hil): + # Begin the test + hil.start_test(test_imd.__name__) + + # Outputs + imd_stat = hil.dout("MainSDC", "IMD_Status") + pow = hil.dout("MainSDC", "5V_Crit") + + # Inputs + imd_ctrl = hil.din("MainSDC", "IMD_Control") + + # IMD Fault + reset_imd(imd_stat) + cycle_power(pow) + hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + time.sleep(1) + imd_stat.state = IMD_STAT_TRIP + t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) + hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + imd_stat.state = IMD_STAT_OKAY + time.sleep(IMD_RC_MAX_TRIP_TIME_S * 1.1) + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + + # IMD Fault on Power On + reset_imd(imd_stat) + imd_stat.state = IMD_STAT_TRIP + cycle_power(pow) + time.sleep(IMD_RC_MAX_TRIP_TIME_S) + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + + # IMD Floating + reset_imd(imd_stat) + imd_stat.hiZ() + cycle_power(pow) + t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) + hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + + hil.end_test() + +AMS_STAT_OKAY = 1 +AMS_STAT_TRIP = 0 +AMS_CTRL_OKAY = 1 +AMS_CTRL_TRIP = 0 + +def reset_ams(ams_stat): + ams_stat.state = AMS_STAT_OKAY + +def test_ams(hil): + # Begin the test + hil.start_test(test_ams.__name__) + + # Outputs + ams_stat = hil.dout("MainSDC", "BMS-Status-Main") + pow = hil.dout("MainSDC", "5V_Crit") + + # Inputs + ams_ctrl = hil.din("MainSDC", "BMS_Control") + + # AMS Fault + reset_ams(ams_stat) + cycle_power(pow) + hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + time.sleep(1) + ams_stat.state = AMS_STAT_TRIP + t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) + hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + ams_stat.state = AMS_STAT_OKAY + time.sleep(AMS_MAX_TRIP_DELAY_S * 1.1) + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + + # AMS Fault on Power On + reset_ams(ams_stat) + ams_stat.state = AMS_STAT_TRIP + cycle_power(pow) + time.sleep(AMS_MAX_TRIP_DELAY_S) + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + + # AMS Floating + reset_ams(ams_stat) + ams_stat.hiZ() + cycle_power(pow) + t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) + hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + + hil.end_test() + + +if __name__ == "__main__": + hil = HIL() + hil.load_config("config_main_sdc_bench.json") + hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + test_bspd(hil) + test_imd(hil) + test_ams(hil) + + hil.shutdown() diff --git a/_old/old_scripts/test_system.py b/_old/old_scripts/test_system.py new file mode 100644 index 0000000..30ffc5a --- /dev/null +++ b/_old/old_scripts/test_system.py @@ -0,0 +1,714 @@ +from os import sys, path +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) +from hil import HIL +import utils +import time +from rules_constants import * +from vehicle_constants import * + +AMS_STAT_OKAY = 1 +AMS_STAT_TRIP = 0 +AMS_CTRL_OKAY = 1 +AMS_CTRL_TRIP = 0 + +def reset_ams(ams_stat): + ams_stat.state = AMS_STAT_OKAY + +def set_bspd_current(ch1, current): + c = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(current)) + #print(f"bspd current: {current} voltage: {c}") + ch1.state = c + +def reset_bspd(brk1, brk2, ch1): + brk1.state = BRK_1_REST_V + brk2.state = BRK_2_REST_V + set_bspd_current(ch1, 0.0) + +IMD_STAT_OKAY = 1 +IMD_STAT_TRIP = 0 +def reset_imd(imd_stat): + imd_stat.state = IMD_STAT_OKAY + +def reset_pchg(v_bat, v_mc): + print(f"Setting v_bat to {tiff_hv_to_lv(ACCUM_NOM_V)}") + v_bat.state = tiff_hv_to_lv(ACCUM_NOM_V) + print(f"Setting v_mc to {tiff_hv_to_lv(0.0)}") + v_mc.state = tiff_hv_to_lv(0.0) + +def reset_tsal(v_mc): + v_mc.state = tiff_hv_to_lv(0.0) + +power = None + +CYCLE_POWER_OFF_DELAY = 2.0 +CYCLE_POWER_ON_DELAY = 3.0 + +def cycle_power(): + power.state = 1 + time.sleep(CYCLE_POWER_OFF_DELAY) + power.state = 0 + time.sleep(CYCLE_POWER_ON_DELAY) + +def test_precharge(hil): + # Begin the test + hil.start_test(test_precharge.__name__) + + # Outputs + v_bat = hil.aout("Main_Module", "VBatt") + v_mc = hil.aout("Main_Module", "Voltage MC Transducer") + + # Inputs + pchg_cmplt = hil.mcu_pin("Main_Module", "PrechargeComplete_Prot") + not_pchg_cmplt_delayed = hil.din("Main_Module", "NotPrechargeCompleteSchmitt") + # v_bat_mcu = hil.daq_var("Main_Module", "Varname") # TODO + # v_mc_mcu = hil.daq_var("Main_Module", "Varname") # TODO + # pchg_mux = hil.daq_var("Main_Module", "Varname") # TODO + + # Outputs to set SDC status to okay + imd_stat = hil.dout("Main_Module", "IMD_Status") + brk1 = hil.aout("Dashboard", "BRK1_RAW") + brk2 = hil.aout("Dashboard", "BRK2_RAW") + c_sense = hil.aout("Main_Module", "Current Sense C1") + ams_stat = hil.dout("Main_Module", "BMS-Status-Main") + reset_imd(imd_stat) + reset_bspd(brk1, brk2, c_sense) + reset_ams(ams_stat) + + cycle_power() + + # Initial State + reset_pchg(v_bat, v_mc) + time.sleep(2.5) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on startup") + hil.check(not_pchg_cmplt_delayed.state == 1, "Not precharge complete delayed high on startup") + # Check delay + v_mc.state = tiff_hv_to_lv(ACCUM_NOM_V) + t = utils.measure_trip_time(not_pchg_cmplt_delayed, PCHG_COMPLETE_DELAY_S*3, is_falling=True) + hil.check(not_pchg_cmplt_delayed.state == 0, "Precharge complete delayed") + hil.check_within(t, PCHG_COMPLETE_DELAY_S, 0.25, f"Precharge complete delay of {t:.3}s close to expected {PCHG_COMPLETE_DELAY_S}s") + + # Find threshold at nominal pack voltage + for v in [ACCUM_MIN_V, ACCUM_NOM_V, ACCUM_MAX_V]: + reset_pchg(v_bat, v_mc) + print(f"Testing precharge threshold at V_bat = {v}") + v_bat.state = tiff_hv_to_lv(v) + v_mc.state = tiff_hv_to_lv(v*0.8) + #time.sleep(0.01) + time.sleep(0.5) + hil.check(pchg_cmplt.state == 0, "Precharge Complete Low at Initial State") + + start = tiff_hv_to_lv(v*0.8) + stop = tiff_hv_to_lv(v) + step = tiff_hv_to_lv(1) + thresh = utils.measure_trip_thresh(v_mc, start, stop, step, 0.1, + pchg_cmplt, is_falling=0) + thresh_hv = tiff_lv_to_hv(thresh) + print(f"Precharge triggered at {thresh_hv / v * 100:.4}% ({thresh_hv:.5}V) of vbat={v}.") + hil.check_within(thresh_hv / v, R_PCHG_V_BAT_THRESH, 0.03, f"Precharge threshold of {R_PCHG_V_BAT_THRESH*100}% at vbat = {v}V") + v_mc.state = tiff_hv_to_lv(v) + #time.sleep(0.25) + time.sleep(8) + hil.check(pchg_cmplt.state == 1, f"Precharge completed at vbat = {v}V") + + + # Floating conditions (check never precharge complete) + reset_pchg(v_bat, v_mc) + v_bat.hiZ() + v_mc.state = tiff_hv_to_lv(0) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc 0V") + v_mc.state = tiff_hv_to_lv(ACCUM_MAX_V) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc max V") + + reset_pchg(v_bat, v_mc) + v_mc.hiZ() + v_bat.state = tiff_hv_to_lv(ACCUM_MIN_V) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat min, v_mc float") + v_bat.state = tiff_hv_to_lv(ACCUM_MAX_V) + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat max, v_mc float") + + reset_pchg(v_bat, v_mc) + v_bat.hiZ() + v_mc.hiZ() + hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc float") + + # TODO: software precharge validity checks (make precharge take forever) + hil.end_test() + +BRK_SWEEP_DELAY = 0.1 +BSPD_DASH_ON_TIME = 0 +def test_bspd(hil): + # Begin the test + hil.start_test(test_bspd.__name__) + + # Outputs + brk1 = hil.aout("Dashboard", "BRK1_RAW") + brk2 = hil.aout("Dashboard", "BRK2_RAW") + c_sense = hil.aout("Main_Module", "Current Sense C1") + + # Outputs to set SDC status to okay + ams_stat = hil.dout("Main_Module", "BMS-Status-Main") + imd_stat = hil.dout("Main_Module", "IMD_Status") + + # Inputs + bspd_ctrl = hil.din("Main_Module", "SDC15") # assuming AMS and BSPD closed + + brk_fail_tap = hil.mcu_pin("Dashboard", "BRK_FAIL_TAP") + brk_stat_tap = hil.mcu_pin("Dashboard", "BRK_STAT_TAP") + + # Set other SDC nodes to okay + reset_ams(ams_stat) + reset_imd(imd_stat) + # BOTS assumed to be good + + # Brake threshold check + brk1.state = BRK_1_REST_V + brk2.state = BRK_2_REST_V + hil.check(brk_stat_tap.state == 0, "Brake stat starts low") + brk1.state = BRK_1_THRESH_V + time.sleep(0.1) + hil.check(brk_stat_tap.state == 1, "Brake stat goes high at brk 1 thresh") + brk1.state = BRK_1_REST_V + hil.check(brk_stat_tap.state == 0, "Brake stat starts low") + brk2.state = BRK_2_THRESH_V + time.sleep(0.1) + hil.check(brk_stat_tap.state == 1, "Brake stat goes high at brk 2 thresh") + brk1.state = BRK_1_THRESH_V + hil.check(brk_stat_tap.state == 1, "Brake stat stays high for both brakes") + + # Brake threshold scan + brk1.state = BRK_MIN_OUT_V + brk2.state = BRK_MIN_OUT_V + time.sleep(0.1) + hil.check(brk_stat_tap.state == 0, "Brake Stat Starts Low Brk 1") + + start = BRK_MIN_OUT_V + stop = BRK_MAX_OUT_V + step = 0.1 + + thresh = utils.measure_trip_thresh(brk1, start, stop, step, + BRK_SWEEP_DELAY, + brk_stat_tap, is_falling=False) + print(f"Brake 1 braking threshold: {thresh}") + hil.check_within(thresh, BRK_1_THRESH_V, 0.2, "Brake 1 trip voltage") + hil.check(brk_stat_tap.state == 1, "Brake Stat Tripped for Brk 1") + + brk1.state = BRK_MIN_OUT_V + brk2.state = BRK_MIN_OUT_V + hil.check(brk_stat_tap.state == 0, "Brake Stat Starts Low Brk 2") + thresh = utils.measure_trip_thresh(brk2, start, stop, step, + BRK_SWEEP_DELAY, + brk_stat_tap, is_falling=False) + print(f"Brake 2 braking threshold: {thresh}") + hil.check_within(thresh, BRK_2_THRESH_V, 0.2, "Brake 2 trip voltage") + hil.check(brk_stat_tap.state == 1, "Brake Stat Tripped for Brk 2") + + # Brake Fail scan + brk1.state = BRK_1_REST_V + brk2.state = BRK_2_REST_V + time.sleep(0.1) + hil.check(brk_fail_tap.state == 0, "Brake Fail Check 1 Starts 0") + brk1.state = 0.0 # Force 0 + time.sleep(0.1) + hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Short GND") + brk1.state = BRK_1_REST_V + time.sleep(0.1) + hil.check(brk_fail_tap.state == 0, "Brake Fail Check 2 Starts 0") + brk2.state = 0.0 # Force 0 + time.sleep(0.1) + hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Short GND") + brk2.state = BRK_2_REST_V + time.sleep(0.1) + hil.check(brk_fail_tap.state == 0, "Brake Fail Check 3 Starts 0") + brk1.state = 5.0 # Short VCC + time.sleep(0.1) + hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Short VCC") + brk1.state = BRK_1_REST_V + time.sleep(0.1) + hil.check(brk_fail_tap.state == 0, "Brake Fail Check 4 Starts 0") + brk2.state = 5.0 # Short VCC + time.sleep(0.1) + hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Short VCC") + brk2.state = BRK_2_REST_V + time.sleep(0.1) + hil.check(brk_fail_tap.state == 0, "Brake Fail Check 5 Starts 0") + brk1.hiZ() + time.sleep(0.1) + hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Hi-Z") + brk1.state = BRK_1_REST_V + time.sleep(0.1) + hil.check(brk_fail_tap.state == 0, "Brake Fail Check 6 Starts 0") + brk2.hiZ() + time.sleep(0.1) + hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Hi-Z") + brk2.state = BRK_2_REST_V + + # Brake Fail + reset_bspd(brk1, brk2, c_sense) + cycle_power() + time.sleep(BSPD_DASH_ON_TIME) + hil.check(bspd_ctrl.state == 1, "Power On") + brk1.state = 0.0 + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + hil.check(brk_fail_tap.state == 1, "Brake Fail went high") + brk1.state = BRK_1_REST_V + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(brk_fail_tap.state == 0, "Brake Fail returned low") + hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + + # Brake Fail on Power On + reset_bspd(brk1, brk2, c_sense) + # Manual power cycle, setting brk_fail on before + # Will cause power to feed back into system + power.state = 1 + time.sleep(2) + brk1.state = 0.0 + power.state = 0 + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + time.sleep(BSPD_DASH_ON_TIME) # NOTE: test can't check for the trip time + hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + + # Current no brake + reset_bspd(brk1, brk2, c_sense) + power.state = 1 + time.sleep(3) + power.state = 0 + time.sleep(1) + #hil.check(bspd_ctrl.state == 1, "Power On") + time.sleep(2) # TODO: I am not sure why this fails, but oh well + set_bspd_current(c_sense, 75) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + # time.sleep(100) + time.sleep(3) + hil.check(bspd_ctrl.state == 1, "Current no brake") + + # Current Sense Short to Ground + reset_bspd(brk1, brk2, c_sense) + cycle_power() + time.sleep(BSPD_DASH_ON_TIME) + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.state = 0.0 + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + set_bspd_current(c_sense, 0.0) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + + # Current Sense Short to 5V + reset_bspd(brk1, brk2, c_sense) + cycle_power() + time.sleep(BSPD_DASH_ON_TIME*1.2) + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.state = ABOX_DHAB_CH1_DIV.div(5.0) + t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + set_bspd_current(c_sense, 0.0) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + + # Braking + reset_bspd(brk1, brk2, c_sense) + cycle_power() + time.sleep(BSPD_DASH_ON_TIME) + hil.check(bspd_ctrl.state == 1, "Power On") + brk1.state = BRK_1_THRESH_V + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Brake no current") + hil.check(brk_stat_tap.state == 1, "Brake stat went high") + + # Lowest current required to trip at + min_trip_current = R_BSPD_POWER_THRESH_W / ACCUM_MAX_V + set_bspd_current(c_sense, min_trip_current) + t = utils.measure_trip_time(bspd_ctrl, 10.0, is_falling=True) + hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + + # Measure braking with current threshold + reset_bspd(brk1, brk2, c_sense) + cycle_power() + time.sleep(BSPD_DASH_ON_TIME) + brk1.state = BRK_1_THRESH_V + + hil.check(bspd_ctrl.state == 1, "Power On") + start = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(0.0)) + stop = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(DHAB_S124_CH1_MAX_A)) + step = 0.1 + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + thresh_amps = dhab_ch1_v_to_a(ABOX_DHAB_CH1_DIV.reverse(thresh)) + print(f"Current while braking threshold: {thresh}V = {thresh_amps}A") + hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + + # Determine the current sense short to gnd threshold + reset_bspd(brk1, brk2, c_sense) + cycle_power() + time.sleep(BSPD_DASH_ON_TIME) + hil.check(bspd_ctrl.state == 1, "Power On") + set_bspd_current(c_sense, DHAB_S124_CH1_MIN_A) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Min output current okay") + start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MIN_OUT_V) + stop = 0.0 + step = -0.1 + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + print(f"Short to ground threshold: {thresh}V") + hil.check(stop < (thresh) < start, "Current short to ground threshold") + + # Determine the current sense short to 5V threshold + reset_bspd(brk1, brk2, c_sense) + cycle_power() + time.sleep(BSPD_DASH_ON_TIME) + hil.check(bspd_ctrl.state == 1, "Power On") + time.sleep(2) + + set_bspd_current(c_sense, DHAB_S124_CH1_MAX_A) + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Max output current okay") + + start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MAX_OUT_V) + stop = ABOX_DHAB_CH1_DIV.div(5.0) + step = 0.01 + + c_sense.state = start + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 1, "Max output voltage okay") + input("enter to continue") + + thresh = utils.measure_trip_thresh(c_sense, start, stop, step, + R_BSPD_MAX_TRIP_TIME_S, + bspd_ctrl, is_falling=True) + print(f"Short to 5V threshold: {thresh}V") + hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + print(stop) + print(start) + hil.check(start < (thresh) <= stop, "Current short to 5V threshold") + + # Floating current + reset_bspd(brk1, brk2, c_sense) + cycle_power() + time.sleep(BSPD_DASH_ON_TIME) + hil.check(bspd_ctrl.state == 1, "Power On") + c_sense.hiZ() + time.sleep(R_BSPD_MAX_TRIP_TIME_S) + hil.check(bspd_ctrl.state == 0, "Floating current") + + # Floating brake_fail + # Can't test this at system level! + + # Floating brake_stat + # Can't test this at system level! + + # End the test + hil.end_test() + + +IMD_RC_MIN_TRIP_TIME_S = IMD_STARTUP_TIME_S +IMD_RC_MAX_TRIP_TIME_S = R_IMD_MAX_TRIP_TIME_S - IMD_MEASURE_TIME_S +IMD_CTRL_OKAY = 1 +IMD_CTRL_TRIP = 0 + +def test_imd(hil): + # Begin the test + hil.start_test(test_imd.__name__) + + # Outputs + imd_stat = hil.dout("Main_Module", "IMD_Status") + + # Outputs to set SDC status to okay + ams_stat = hil.dout("Main_Module", "BMS-Status-Main") + brk1 = hil.aout("Dashboard", "BRK1_RAW") + brk2 = hil.aout("Dashboard", "BRK2_RAW") + c_sense = hil.aout("Main_Module", "Current Sense C1") + + # Inputs + imd_ctrl = hil.din("Main_Module", "SDC15") # assuming AMS and BSPD closed + + # Set other SDC nodes to okay + reset_ams(ams_stat) + reset_bspd(brk1, brk2, c_sense) + + # IMD Fault + reset_imd(imd_stat) + cycle_power() + hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + time.sleep(1) + imd_stat.state = IMD_STAT_TRIP + t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) + print(f"Target trip time: [{IMD_RC_MIN_TRIP_TIME_S}, {IMD_RC_MAX_TRIP_TIME_S}]") + hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + imd_stat.state = IMD_STAT_OKAY + time.sleep(IMD_RC_MAX_TRIP_TIME_S * 1.1) + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + + # IMD Fault on Power On + reset_imd(imd_stat) + imd_stat.state = IMD_STAT_TRIP + cycle_power() + time.sleep(IMD_RC_MAX_TRIP_TIME_S) + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + + # IMD Floating + reset_imd(imd_stat) + imd_stat.hiZ() + cycle_power() + t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) + hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + + hil.end_test() + +def test_ams(hil): + # Begin the test + hil.start_test(test_ams.__name__) + + # Outputs + ams_stat = hil.dout("Main_Module", "BMS-Status-Main") + + # Outputs to set SDC status to okay + imd_stat = hil.dout("Main_Module", "IMD_Status") + brk1 = hil.aout("Dashboard", "BRK1_RAW") + brk2 = hil.aout("Dashboard", "BRK2_RAW") + c_sense = hil.aout("Main_Module", "Current Sense C1") + + # Inputs + ams_ctrl = hil.din("Main_Module", "SDC15") # assumes IMD and BSPD closed + + # Set other SDC nodes to okay + reset_imd(imd_stat) + reset_bspd(brk1, brk2, c_sense) + + # AMS Fault + reset_ams(ams_stat) + cycle_power() + hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + time.sleep(1) + ams_stat.state = AMS_STAT_TRIP + t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) + hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + ams_stat.state = AMS_STAT_OKAY + time.sleep(AMS_MAX_TRIP_DELAY_S * 1.1) + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + + # AMS Fault on Power On + reset_ams(ams_stat) + ams_stat.state = AMS_STAT_TRIP + cycle_power() + time.sleep(AMS_MAX_TRIP_DELAY_S) + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + + # AMS Floating + reset_ams(ams_stat) + ams_stat.hiZ() + cycle_power() + t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) + hil.check(0 <= t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + + hil.end_test() + +def tsal_is_red(): + while 1: + i = input("Is TSAL Green (g) or Red (r): ") + utils.clear_term_line() + i = i.upper() + if (i == 'G' or i == 'R'): + return i == 'R' + print("You may only enter G or R!") + +def test_tsal(hil): + + # Begin the test + hil.start_test(test_tsal.__name__) + + # Outputs + v_mc = hil.aout("Main_Module", "Voltage MC Transducer") + + # Inputs + # User :D + print(f"{utils.bcolors.OKCYAN}Press 'G' for green and 'R' for red.{utils.bcolors.ENDC}") + + # Initial State + reset_tsal(v_mc) + time.sleep(0.2) + # No need to power cycle + + hil.check(tsal_is_red() == False, "LVAL on at v_mc = 0") + #hil.check(tsal.state == 0, "TSAL off at v_mc = 0") + + time.sleep(5) + hil.check(tsal_is_red() == False, "LVAL stays on") + + v_mc.state = tiff_hv_to_lv(ACCUM_MIN_V) + time.sleep(0.1) + + #hil.check(lval.state == 0, f"LVAL off at {ACCUM_MIN_V:.4} V") + hil.check(tsal_is_red() == True, f"TSAL on at {ACCUM_MIN_V:.4} V") + + reset_tsal(v_mc) + time.sleep(0.2) + hil.check(tsal_is_red() == False, f"LVAL turns back on") + + start = tiff_hv_to_lv(50) + stop = tiff_hv_to_lv(R_TSAL_HV_V * 1.5) + step = tiff_hv_to_lv(1) + + gain = 1000 + thresh = start + _start = int(start * gain) + _stop = int(stop * gain) + _step = int(step * gain) + v_mc.state = start + tripped = False + print(f"Start: {_start} Stop: {_stop} Step: {_step} Gain: {gain}") + for v in range(_start, _stop+_step, _step): + v_mc.state = v / gain + time.sleep(0.01) + if (tsal_is_red()): + thresh = v / gain + tripped = True + break + if (not tripped): + utils.log_warning(f"TSAL did not trip at stop of {stop}.") + thresh = stop + hil.check(tripped, "TSAL tripped") + + thresh = tiff_lv_to_hv(thresh) + print(f"TSAL on at {thresh:.4} V") + hil.check_within(thresh, R_TSAL_HV_V, 4, f"TSAL trips at {R_TSAL_HV_V:.4} +-4") + + hil.end_test() + +def test_sdc(hil): + ''' Check that every node in the sdc trips ''' + # Begin the test + hil.start_test(test_sdc.__name__) + + # Outputs + + # Inputs + + hil.check(0, "TODO") + + hil.end_test() + +def is_buzzer_on(): + while 1: + i = input("Is Buzzer On (y) or No (n): ") + utils.clear_term_line() + i = i.upper() + if (i == 'Y' or i == 'N'): + return i == 'Y' + print("You may only enter Y or N!") + +def test_buzzer(hil): + # Begin the test + hil.start_test(test_buzzer.__name__) + + # Outputs + buzzer_ctrl = hil.daq_var("Main_Module", "daq_buzzer") + + # Inputs + buzzer_stat = hil.mcu_pin("Main_Module", "Buzzer_Prot") + + buzzer_ctrl.state = 0 + time.sleep(0.02) + hil.check(buzzer_stat.state == 0, "Buzzer Off") + + buzzer_ctrl.state = 1 + print(buzzer_ctrl.state) + time.sleep(0.02) + hil.check(buzzer_stat.state == 1, "Buzzer On") + hil.check(is_buzzer_on() == True, "Buzzer Making Noise") + + buzzer_ctrl.state = 0 + time.sleep(0.02) + hil.check(buzzer_stat.state == 0, "Buzzer back Off") + hil.check(is_buzzer_on() == False, "Buzzer Not Making Noise") + + hil.end_test() + +def is_brake_light_on(): + while 1: + i = input("Is Brake Light On (y) or No (n): ") + utils.clear_term_line() + i = i.upper() + if (i == 'Y' or i == 'N'): + return i == 'Y' + print("You may only enter Y or N!") + +def test_brake_light(hil): + # Begin the test + hil.start_test(test_brake_light.__name__) + + # Outputs + brk_ctrl = hil.daq_var("Main_Module", "daq_brake") + + # Inputs + brk_stat = hil.mcu_pin("Main_Module", "Brake_Light_CTRL_Prot") + + brk_ctrl.state = 0 + time.sleep(0.02) + hil.check(brk_ctrl.state == 0, "Brake Off") + + brk_ctrl.state = 1 + print(brk_ctrl.state) + time.sleep(0.02) + hil.check(brk_ctrl.state == 1, "Brake Light On") + hil.check(is_brake_light_on() == True, "Brake Light is On") + + brk_ctrl.state = 0 + time.sleep(0.02) + hil.check(brk_ctrl.state == 0, "Brake Light back Off") + hil.check(is_brake_light_on() == False, "Brake Light is Off") + + + # Can copy lot from bspd + # Read the brake control mcu pin + # Finally have user verify light actually turned on + + hil.end_test() + +def test_light_tsal_buz(hil): + hil.start_test(test_light_tsal_buz.__name__) + + # Outputs + brk_ctrl = hil.daq_var("Main_Module", "daq_brake") + buzzer_ctrl = hil.daq_var("Main_Module", "daq_buzzer") + + brk_ctrl.state = 1 + buzzer_ctrl.state = 1 + input("Press enter to end the test") + brk_ctrl.state = 0 + buzzer_ctrl.state = 0 + + hil.end_test() + + +if __name__ == "__main__": + hil = HIL() + + hil.load_config("config_system_hil_attached.json") + hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + hil.init_can() + + power = hil.dout("RearTester", "RLY1") + + # Drive Critical Tests + # test_precharge(hil) + test_bspd(hil) + # test_imd(hil) # note: tsal needs to be tripped + # test_ams(hil) + # test_tsal(hil) + # test_sdc(hil) + # test_buzzer(hil) + # test_brake_light(hil) + # test_light_tsal_buz(hil) + + # Peripheral Sensor Tests + + hil.shutdown() diff --git a/_old/old_scripts/test_test.py b/_old/old_scripts/test_test.py new file mode 100644 index 0000000..08af75a --- /dev/null +++ b/_old/old_scripts/test_test.py @@ -0,0 +1,172 @@ +from os import sys, path +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) +from hil import HIL +import utils +import time +import can +from rules_constants import * +from vehicle_constants import * + +def test_bspd(hil): + # Begin the test + hil.start_test(test_bspd.__name__) + + # Inputs + d2 = hil.din("Test_HIL", "AI2") + d3 = hil.din("Test_HIL", "AI3") + d4 = hil.din("Test_HIL", "AI4") + # d5 = hil.din("Test_HIL", "DI5") + # d6 = hil.din("Test_HIL", "DI6") + # d7 = hil.din("Test_HIL", "DI7") + r1 = hil.dout("Test_HIL", "RLY1") + r2 = hil.dout("Test_HIL", "RLY2") + r3 = hil.dout("Test_HIL", "RLY3") + r4 = hil.dout("Test_HIL", "RLY4") + + a1 = hil.dout("Test_HIL", "AI1") + + a1.state = 1 + + + r1.state = 1 + r2.state = 1 + r3.state = 1 + r4.state = 1 + + r1.state = 0 + input("") + r2.state = 0 + input("") + r3.state = 0 + input("") + r4.state = 0 + input("") + + for _ in range(100): + + #print(f"{d2.state}, {d3.state}, {d4.state}") + for i in range(4): + r1.state = not ((i % 4) == 0) + r2.state = not ((i % 4) == 1) + r3.state = not ((i % 4) == 2) + r4.state = not ((i % 4) == 3) + time.sleep(1) + time.sleep(2) + + hil.end_test() + +def test_dac(hil): + hil.start_test(test_dac.__name__) + + dac1 = hil.aout("Test_HIL", "DAC1") + dac2 = hil.aout("Test_HIL", "DAC2") + + dac1.state = 2.5 + dac2.state = 5.0 + input("5, 2") + dac1.hiZ() + dac2.hiZ() + input("hi-z") + dac1.state = 0.25 + dac2.state = 0.25 + input(".25") + + hil.end_test() + +def test_pot(hil): + + pot1 = hil.pot("Test_HIL", "POT1") + pot2 = hil.pot("Test_HIL", "POT2") + + print("initial") + input(" - ") + print("0.5, 1") + pot1.state = 0.5 + pot2.state = 0.5 + input(" - ") + + print("1, 0.5") + pot1.state = 1.0 + pot2.state = 1.0 + input(" - ") + + print("0, 0") + pot1.state = 0.0 + pot2.state = 0.0 + input(" - ") + + for i in range(1000): + pot1.state = 0.25 + pot2.state = 0.25 + time.sleep(0.01) + pot1.state = 0.75 + pot2.state = 0.75 + time.sleep(0.01) + + pot1.state = 0.5 + pot2.state = 0.5 + input("-------") + +def test_mcu_pin(hil): + hil.start_test(test_mcu_pin.__name__) + + brk_stat_tap = hil.mcu_pin("Dashboard", "BRK_STAT_TAP") + + delta_avg = 0 + delta_cnt = 0 + for i in range(100): + t_start = time.time() + #time.sleep(0.01) + print(brk_stat_tap.state) + t_start = time.time() - t_start + delta_avg += t_start + delta_cnt = delta_cnt + 1 + + print(f"Average: {delta_avg/delta_cnt}") + + hil.end_test() + + +def test_daq(hil): + hil.start_test(test_daq.__name__) + + counter = 0 + start_time = time.time() + + LOOP_TIME_S = 0.001 + + # while(1): + # time.sleep(3) + + print("Sending") + + #while (time.time() - start_time < 15*60): + while (counter < 4000): + last_tx = time.perf_counter() + #msg = can.Message(arbitration_id=0x14000072, data=counter.to_bytes(4, 'little')) + #msg = can.Message(arbitration_id=0x80080c4, data=counter.to_bytes(4, 'little')) + msg = can.Message(arbitration_id=0x400193e, data=counter.to_bytes(8, 'little')) + #print(msg) + hil.can_bus.sendMsg(msg) + counter = counter + 1 + delta = LOOP_TIME_S - (time.perf_counter() - last_tx) + if (delta < 0): delta = 0 + time.sleep(delta) + + print("Done") + print(f"Last count sent: {counter - 1}") + + +if __name__ == "__main__": + hil = HIL() + #hil.load_config("config_testing.json") + hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + hil.init_can() + #test_bspd(hil) + #test_dac(hil) + # test_pot(hil) + # test_mcu_pin(hil) + test_daq(hil) + + hil.shutdown() diff --git a/_old/old_scripts/vehicle_constants.py b/_old/old_scripts/vehicle_constants.py new file mode 100644 index 0000000..35a4050 --- /dev/null +++ b/_old/old_scripts/vehicle_constants.py @@ -0,0 +1,75 @@ +from os import sys, path +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) +import utils + +# NOTE: each value in this file should be a physical or electrical property of the vehicle + +# Accumulator Constants +ACCUM_MAX_V = 317.3 +ACCUM_MIN_V = 190.0 +ACCUM_NOM_V = 273.6 +ACCUM_FUSE_A = 140.0 + +ABOX_DHAB_CH1_DIV = utils.VoltageDivider(1000, 2000) + +# IMD Constants +IMD_MEASURE_TIME_S = 20.0 +IMD_STARTUP_TIME_S = 2.0 + +# AMS Constants +AMS_MAX_TRIP_DELAY_S = 3.0 + +# Precharge Constants +PCHG_COMPLETE_DELAY_S = 0.5 + +# Tiffomy Constants +TIFF_LV_MAX = 5.0 +TIFF_LV_MIN = -5.0 +TIFF_SCALE = 100.0 +def tiff_hv_to_lv(hv_voltage): + return min(max(hv_voltage / TIFF_SCALE, TIFF_LV_MIN), TIFF_LV_MAX) +def tiff_lv_to_hv(lv_voltage): + return lv_voltage * TIFF_SCALE + +# DHAB S124 Current Sensor +DHAB_S124_MAX_OUT_V = 4.8 +DHAB_S124_MIN_OUT_V = 0.2 +DHAB_S124_OFFSET_V = 2.5 +DHAB_S124_CH1_SENSITIVITY = 26.7 / 1000.0 # V / A +DHAB_S124_CH2_SENSITIVITY = 4.9 / 1000.0 # V / A +DHAB_S124_CH1_MAX_A = 75.0 +DHAB_S124_CH1_MIN_A = -75.0 +DHAB_S124_CH2_MAX_A = 500.0 +DHAB_S124_CH2_MIN_A = -500.0 + +def dhab_ch1_v_to_a(signal_v): + return (signal_v - DHAB_S124_OFFSET_V) / DHAB_S124_CH1_SENSITIVITY + +def dhab_ch2_v_to_a(signal_v): + return (signal_v - DHAB_S124_OFFSET_V) / DHAB_S124_CH2_SENSITIVITY + +def dhab_ch1_a_to_v(amps): + amps = min(max(amps, DHAB_S124_CH1_MIN_A), DHAB_S124_CH1_MAX_A) + return (amps * DHAB_S124_CH1_SENSITIVITY) + DHAB_S124_OFFSET_V + +def dhab_ch2_a_to_v(amps): + amps = min(max(amps, DHAB_S124_CH2_MIN_A), DHAB_S124_CH2_MAX_A) + return (amps * DHAB_S124_CH2_SENSITIVITY) + DHAB_S124_OFFSET_V + +def dhab_v_valid(signal_v): + return (DHAB_S124_MIN_OUT_V <= signal_v <= DHAB_S124_MAX_OUT_V) + +# Brake Pressure Transducer +BRK_MAX_OUT_V = 4.8 +BRK_MIN_OUT_V = 0.2 +BRK_1_REST_V = 0.5 # Resting line voltage of brake 1 +BRK_2_REST_V = 0.5 # Resting line voltage of brake 2 +BRK_1_DIV = utils.VoltageDivider(5600, 10000) +BRK_2_DIV = utils.VoltageDivider(5600, 10000) +BRK_1_THRESH_V = 0.68 # Threshold that is considered braking for brake 1 +BRK_2_THRESH_V = 0.68 # Threshold that is considered braking for brake 2 + +# Throttle +THTL_MAX_P = 0.9 # Maximum pedal press percent +THTL_MIN_P = 0.1 # Minimum pedal press percent +THTL_THRESH = 0.2 # Throttle pressed percent diff --git a/hil/communication/can_bus.py b/hil/communication/can_bus.py index f16b6fc..fefa1f7 100644 --- a/hil/communication/can_bus.py +++ b/hil/communication/can_bus.py @@ -1,3 +1,6 @@ +from __future__ import annotations +from collections.abc import Callable + from datetime import datetime import can import can.interfaces.gs_usb @@ -5,57 +8,58 @@ import socket import usb import cantools -from communication.client import TCPBus, UDPBus -import utils +from hil.communication.client import TCPBus, UDPBus +import hil.utils as utils import time import threading import numpy as np -import math CAN_READ_TIMEOUT_S = 1.0 +# ---------------------------------------------------------------------------- # class CanBus(threading.Thread): """ Handles sending and receiving can bus messages, tracks all degined signals (BusSignal) """ - def __init__(self, dbc_path, default_ip, can_config: dict): + def __init__(self, dbc_path: str, default_ip: str, can_config: dict): super(CanBus, self).__init__() - self.db = cantools.db.load_file(dbc_path) + self.db: cantools.database.can.database.Database = cantools.db.load_file(dbc_path) utils.log(f"CAN version: {can.__version__}") utils.log(f"gs_usb version: {gs_usb.__version__}") - self.connected = False - self.bus = None - self.start_time_bus = -1 - self.start_date_time_str = "" - self.tcp = False - self.tcpbus = None + self.connected: bool = False + self.bus: can.ThreadSafeBus | UDPBus = None + self.start_time_bus: float = -1 + self.start_date_time_str: str = "" + self.tcp: bool = False + self.tcpbus: TCPBus = None - self.handle_daq_msg = None + self.handle_daq_msg: Callable[[can.Message], None] = None # Bus Load Estimation - self.total_bits = 0 - self.last_estimate_time = 0 + self.total_bits: int = 0 + self.last_estimate_time: float = 0 # Load Bus Signals - self.can_config = can_config + self.can_config: dict = can_config self.updateSignals(self.can_config) - self.is_importing = False + self.is_importing: bool = False #self.port = 8080 - self.port = 5005 - self.ip = default_ip #self.ip = "10.42.0.1" - self.password = None - self.is_wireless = False + self.port: int = 5005 + self.ip: str = default_ip + + self.password: str | None = None + self.is_wireless: bool = False - def connect(self): + def connect(self) -> None: """ Connects to the bus """ utils.log("Trying usb") # Attempt usb connection first @@ -117,7 +121,7 @@ def connect(self): # self.connect_sig.emit(self.connected) self.connectError() - def connect_tcp(self): + def connect_tcp(self) -> None: # Usb failed, trying tcp utils.log("Trying tcp") self.connected_disp = 1 @@ -178,9 +182,7 @@ def connect_tcp(self): self.connected_disp = 0 # self.write_sig.emit(self.connected_disp) - - - def disconnect_bus(self): + def disconnect_bus(self) -> None: self.connected = False # self.connect_sig.emit(self.connected) if self.tcpbus: @@ -191,7 +193,7 @@ def disconnect_bus(self): del(self.bus) self.bus = None - def disconnect_tcp(self): + def disconnect_tcp(self) -> None: self.connected_disp = 0 # self.write_sig.emit(self.connected_disp) if self.tcpbus: @@ -200,7 +202,7 @@ def disconnect_tcp(self): self.tcpbus = None - def reconnect(self): + def reconnect(self) -> None: """ destroy usb connection, attempt to reconnect """ self.connected = False # while(not self.isFinished()): @@ -216,14 +218,14 @@ def reconnect(self): self.start_date_time_str = datetime.now().strftime("%m-%d-%Y %H:%M:%S") self.start() - def sendLogCmd(self, option : bool): + def sendLogCmd(self, option: bool) -> None: """Send the start logging function""" if option == True: self.tcpbus.start_logging() else: self.tcpbus.stop_logging() - def sendFormatMsg(self, msg_name, msg_data: dict): + def sendFormatMsg(self, msg_name: str, msg_data: dict) -> None: """ Sends a message using a dictionary of its data """ dbc_msg = self.db.get_message_by_name(msg_name) data = dbc_msg.encode(msg_data) @@ -233,7 +235,7 @@ def sendFormatMsg(self, msg_name, msg_data: dict): if self.tcp: self.tcpbus.send(msg) - def sendMsg(self, msg: can.Message): + def sendMsg(self, msg: can.Message) -> None: """ Sends a can message over the bus """ if self.connected: if not self.is_wireless: @@ -247,7 +249,7 @@ def sendMsg(self, msg: can.Message): else: utils.log_error("Tried to send msg without connection") - def onMessageReceived(self, msg: can.Message): + def onMessageReceived(self, msg: can.Message) -> None: """ Emits new message signal and updates the corresponding signals """ if self.start_time_bus == -1: self.start_time_bus = msg.timestamp @@ -286,7 +288,7 @@ def onMessageReceived(self, msg: can.Message): msg_bit_length_max = 64 + msg.dlc * 8 + 18 self.total_bits += msg_bit_length_max - def connectError(self): + def connectError(self) -> None: """ Creates message box prompting to try to reconnect """ # self.ip = ConnectionErrorDialog.connectionError(self.ip) utils.log_error("Ip wrong") @@ -294,7 +296,7 @@ def connectError(self): # self.connect_tcp() - def updateSignals(self, can_config: dict): + def updateSignals(self, can_config: dict) -> None: """ Creates dictionary of BusSignals of all signals in can_config """ utils.signals.clear() for bus in can_config['busses']: @@ -308,7 +310,7 @@ def updateSignals(self, can_config: dict): [msg['msg_name']][signal['sig_name']]\ = BusSignal.fromCANMsg(signal, msg, node, bus) - def run(self): + def run(self) -> None: """ Thread loop to receive can messages """ self.last_estimate_time = time.time() loop_count = 0 @@ -341,8 +343,10 @@ def run(self): skips = 0 #self.connect_sig.emit(self.connected and self.bus.is_connected) # if (self.connected and self.is_wireless): self.connect_sig.emit(self.bus and self.bus.is_connected) +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # class BusSignal(): """ Signal that can be subscribed (connected) to for updates """ @@ -353,68 +357,85 @@ class BusSignal(): # NOTE: don't need lock for now as long as only one writer # However, when timestamp and data are read, it is possible an older timestamp is read for newer data - def __init__(self, bus_name, node_name, msg_name, sig_name, dtype, store_dtype=None, unit="", msg_desc="", sig_desc="", msg_period=0): - self.bus_name = bus_name - self.node_name = node_name - self.message_name = msg_name - self.signal_name = sig_name - self.name = '.'.join([self.bus_name, self.node_name, self.message_name, self.signal_name]) - - self.unit = unit - self.msg_desc = msg_desc - self.sig_desc = sig_desc + def __init__( + self, + bus_name: str, + node_name: str, + msg_name: str, + sig_name: str, + dtype: np.dtype, + store_dtype: np.dtype | None = None, + unit: str = "", + msg_desc: str = "", + sig_desc: str = "", + msg_period = 0 + ): + self.bus_name: str = bus_name + self.node_name: str = node_name + self.message_name: str = msg_name + self.signal_name: str = sig_name + self.name: str = '.'.join([self.bus_name, self.node_name, self.message_name, self.signal_name]) + + self.unit: str = unit + self.msg_desc: str = msg_desc + self.sig_desc: str = sig_desc self.msg_period = msg_period - self.send_dtype = dtype - if not store_dtype: self.store_dtype = self.send_dtype - else: self.store_dtype = store_dtype - self.data = 0 - self.time = 0 - self.stale_timestamp = time.time() - def fromCANMsg(sig, msg, node, bus): + self.send_dtype: np.dtype = dtype + if not store_dtype: + self.store_dtype: np.dtype = self.send_dtype + else: + self.store_dtype: np.dtype = store_dtype + + self.data: int = 0 + self.time: float = 0 + self.stale_timestamp: float = time.time() + + @classmethod + def fromCANMsg(cls, sig: dict, msg: dict, node: dict, bus: dict) -> BusSignal: send_dtype = utils.data_types[sig['type']] # If there is scaling going on, don't store as an integer on accident if ('scale' in sig and sig['scale'] != 1) or ('offset' in sig and sig['offset'] != 0): parse_dtype = utils.data_types['float'] else: parse_dtype = send_dtype - return BusSignal(bus['bus_name'], node['node_name'], msg['msg_name'], sig['sig_name'], + return cls(bus['bus_name'], node['node_name'], msg['msg_name'], sig['sig_name'], send_dtype, store_dtype=parse_dtype, unit=(sig['unit'] if 'unit' in sig else ""), msg_desc=(msg['msg_desc'] if 'msg_desc' in msg else ""), sig_desc=(sig['sig_desc'] if 'sig_desc' in sig else ""), msg_period=msg['msg_period']) - def update(self, val, timestamp): + def update(self, val: int, timestamp: float) -> None: """ update the value of the signal """ self.data = val self.time = timestamp self.stale_timestamp = time.time() - def clear(self): + def clear(self) -> None: """ clears stored signal values """ self.data = 0 self.time = 0 @property - def curr_val(self): + def curr_val(self) -> int: """ last value recorded """ return self.data @property - def last_update_time(self): + def last_update_time(self) -> float: """ timestamp of last value recorded """ return self.time @property - def is_stale(self): + def is_stale(self) -> bool: """ based on last receive time """ if self.msg_period == 0: return False else: return ((time.time() - self.stale_timestamp) * 1000) > self.msg_period * 1.5 @property - def state(self): + def state(self) -> int: start_t = time.time() while (self.is_stale): if (time.time() >= start_t + CAN_READ_TIMEOUT_S): diff --git a/hil/communication/client.py b/hil/communication/client.py index ba643d5..b317f80 100644 --- a/hil/communication/client.py +++ b/hil/communication/client.py @@ -5,13 +5,15 @@ from threading import Thread import can from time import sleep -import utils +import hil.utils as utils import datetime # modification of: # https://github.com/teebr/socketsocketcan # switched raspi to be the server instead of a client + +# ---------------------------------------------------------------------------- # class TCPBus(can.BusABC): RECV_FRAME_SZ = 29 @@ -19,30 +21,30 @@ class TCPBus(can.BusABC): CAN_RTR_FLAG = 0x40000000 CAN_ERR_FLAG = 0x20000000 - def __init__(self, ip, port,can_filters=None,**kwargs): - super().__init__("whatever",can_filters) - self.port = port - self._is_connected = False - self.recv_buffer = Queue() - self.send_buffer = Queue() - self._shutdown_flag = False#Queue() + def __init__(self, ip: str, port: int, can_filters: can.typechecking.CanFilters | None = None,**kwargs): + super().__init__("whatever", can_filters) + self.port: int = port + self._is_connected: bool = False + self.recv_buffer: Queue = Queue() # Queue[can.Message] + self.send_buffer: Queue = Queue() # Queue[can.Message] + self._shutdown_flag: bool = False#Queue() print(f"IP: {ip}, port: {port}") #open socket and wait for connection to establish. socket.setdefaulttimeout(3) # seconds utils.log("attempting to connect to tcp") - self._conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + self._conn: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) utils.log("Connecting...") self._conn.connect((ip, port)) utils.log("connected") - self._is_connected = True + self._is_connected: bool = True self._conn.settimeout(0.5) #blocking makes exiting an infinite loop hard self.start_threads() - def start_threads(self): + def start_threads(self) -> bool: # self._conn.sendall(password.encode()) # data_raw = self._conn.recv(1) # data = int.from_bytes(data_raw,"little") @@ -61,7 +63,7 @@ def start_threads(self): self.send_time_sync() return True - def send_time_sync(self): + def send_time_sync(self) -> None: # Sends the current time to update RTC self.send_buffer.put(4) t = datetime.datetime.now() @@ -69,14 +71,14 @@ def send_time_sync(self): t.day, t.month, (t.year-2000), 0, 0]) self.send_buffer.put(v) - def _recv_internal(self,timeout=None): + def _recv_internal(self,timeout: float | None = None) -> tuple[can.Message | None, bool]: #TODO: filtering try: return (self.recv_buffer.get(timeout=timeout), True) except queue.Empty: return None, True - def send(self,msg): + def send(self, msg: can.Message) -> None: if msg.is_extended_id: msg.arbitration_id |= self.CAN_EFF_FLAG if msg.is_remote_frame: @@ -86,21 +88,21 @@ def send(self,msg): self.send_buffer.put(0) self.send_buffer.put(msg) - def start_logging(self): + def start_logging(self) -> None: self.send_buffer.put(1) self.send_buffer.put(0xFFFFFFFF) - def stop_logging(self): + def stop_logging(self) -> None: self.send_buffer.put(3) self.send_buffer.put(0xFFFFFFFF) - def _stop_threads(self): + def _stop_threads(self) -> None: #self._shutdown_flag.put(True) self._shutdown_flag = True self._is_connected = False utils.log_warning("Bus Client Shutdown (TCP)") - def shutdown(self, handshake): + def shutdown(self, handshake: int) -> None: """gracefully close TCP connection and exit threads""" #handshake: 0: Currently in handshake mode, 1: In regular send mode if handshake == 0: @@ -116,15 +118,15 @@ def shutdown(self, handshake): sleep(0.005) self._conn.close() #shutdown might be faster but can be ugly and raise an exception - def close(self): + def close(self) -> None: self._conn.close() @property - def is_connected(self): + def is_connected(self) -> bool: """check that a TCP connection is active""" return self._is_connected - def _msg_to_bytes(self,msg): + def _msg_to_bytes(self, msg: can.Message) -> bytes: """convert Message object to bytes to be put on TCP socket""" # print(msg) arb_id = msg.arbitration_id.to_bytes(4,"little") #TODO: masks @@ -133,7 +135,7 @@ def _msg_to_bytes(self,msg): # print(arb_id + dlc + data) return arb_id+dlc+data - def _bytes_to_message(self,b): + def _bytes_to_message(self, b: bytes) -> can.Message: """convert raw TCP bytes to can.Message object""" #ts = int.from_bytes(b[:4],"little") + int.from_bytes(b[4:8],"little")/1e6 ts = int.from_bytes(b[:8],"little") + int.from_bytes(b[8:16],"little")/1e6 @@ -160,7 +162,7 @@ def _bytes_to_message(self,b): data=b[21:21+dlc] ) - def _poll_socket(self): + def _poll_socket(self) -> None: """background thread to check for new CAN messages on the TCP socket""" part_formed_message = bytearray() # TCP transfer might off part way through sending a message #with self._conn as conn: @@ -204,7 +206,7 @@ def _poll_socket(self): break # utils.log("Exited poll socket") - def _poll_send(self): + def _poll_send(self) -> None: """background thread to send messages when they are put in the queue""" #with self._conn as s: s = self._conn @@ -234,6 +236,10 @@ def _poll_send(self): except QueueEmpty: pass #NBD, just means nothing to send. # utils.log("Exited poll send") +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # class UDPBus(can.BusABC): #RECV_FRAME_SZ = 29 RECV_FRAME_SZ = 18 @@ -241,28 +247,28 @@ class UDPBus(can.BusABC): CAN_RTR_FLAG = 0x40000000 CAN_ERR_FLAG = 0x20000000 - def __init__(self, ip, port,can_filters=None,**kwargs): + def __init__(self, ip: str, port: int, can_filters: can.typechecking.CanFilters | None = None, **kwargs): super().__init__("whatever",can_filters) - self.port = port - self._is_connected = False - self.recv_buffer = Queue() - self._shutdown_flag = False#Queue() + self.port: int = port + self._is_connected: bool = False + self.recv_buffer: Queue = Queue() + self._shutdown_flag: bool = False#Queue() #open socket and wait for connection to establish. socket.setdefaulttimeout(3) # seconds utils.log("attempting to connect to udp") - self._conn = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + self._conn: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self._conn.bind(("", 5005)) utils.log("Listening to UDP Port") - self._is_connected = True + self._is_connected: bool = True self._conn.settimeout(0.5) #blocking makes exiting an infinite loop hard #now we're connected, kick off other threads. - self._udp_listener = Thread(target=self._poll_udp_socket) + self._udp_listener: Thread = Thread(target=self._poll_udp_socket) self._udp_listener.start() - def _bytes_to_message(self,b): + def _bytes_to_message(self, b: bytes) -> can.Message: """convert raw TCP bytes to can.Message object""" #ts = int.from_bytes(b[:4],"little") + int.from_bytes(b[4:8],"little")/ e6 # ts = int.from_bytes(b[:8],"little") + int.from_bytes(b[8:16],"little")/1e6 @@ -294,7 +300,8 @@ def _bytes_to_message(self,b): #data=b[21:21+dlc] data=b[10:10+dlc] ) - def send(self,msg): + + def send(self, msg: can.Message) -> None: if msg.is_extended_id: msg.arbitration_id |= self.CAN_EFF_FLAG if msg.is_remote_frame: @@ -303,14 +310,14 @@ def send(self,msg): msg.arbitration_id |= self.CAN_ERR_FLAG self.send_buffer.put(msg) - def _recv_internal(self,timeout=None): + def _recv_internal(self, timeout: float | None = None) -> tuple[can.Message | None, bool]: #TODO: filtering try: return (self.recv_buffer.get(timeout=timeout), True) except queue.Empty: return None, True - def _poll_udp_socket(self): + def _poll_udp_socket(self) -> None: """background thread to check for new CAN messages on the UDP socket""" part_formed_message = bytearray() # UDP transfer might off part way through sending a message conn = self._conn @@ -352,13 +359,13 @@ def _poll_udp_socket(self): self._stop_threads() break - def _stop_threads(self): + def _stop_threads(self) -> None: #self._shutdown_flag.put(True) self._shutdown_flag = True self._is_connected = False utils.log_warning("Bus Client Shutdown (UDP)") - def shutdown(self): + def shutdown(self) -> None: """gracefully close UDP connection and exit threads""" if self._is_connected: self._stop_threads() @@ -366,3 +373,4 @@ def shutdown(self): while self._udp_listener.is_alive(): sleep(0.005) self._conn.close() #shutdown might be faster but can be ugly and raise an exception +# ---------------------------------------------------------------------------- # \ No newline at end of file diff --git a/hil/communication/daq_protocol.py b/hil/communication/daq_protocol.py index bfb4fb2..6d0aceb 100644 --- a/hil/communication/daq_protocol.py +++ b/hil/communication/daq_protocol.py @@ -1,6 +1,7 @@ -from communication.can_bus import BusSignal, CanBus +from __future__ import annotations +from hil.communication.can_bus import BusSignal, CanBus # from PyQt5 import QtCore -import utils +import hil.utils as utils import can import math import numpy as np @@ -46,27 +47,55 @@ """ +# ---------------------------------------------------------------------------- # class DAQVariable(BusSignal): """ DAQ variable that can be subscribed (connected) to for receiving updates""" - - - def __init__(self, bus_name, node_name, msg_name, sig_name, id, read_only, bit_length, - dtype, store_dtype=None, unit="", msg_desc="", sig_desc="", msg_period=0, file_name=None, file_lbl=None, scale=1, offset=0): - super(DAQVariable, self).__init__(bus_name, node_name, msg_name, sig_name, dtype, store_dtype=store_dtype, - unit=unit, msg_desc=msg_desc, sig_desc=sig_desc, msg_period=msg_period) - self.id = id - self.read_only = read_only - self.bit_length = bit_length - self.file = file_name - self.file_lbl = file_lbl - self.pub_period_ms = 0 - - self.scale = scale - self.offset = offset - - self.is_dirty = False - - def fromDAQVar(id, var, node, bus): + def __init__(self, + bus_name: str, + node_name: str, + msg_name: str, + sig_name: str, + id: int, + read_only: bool, + bit_length: int, + dtype: np.dtype, + store_dtype: np.dtype | None = None, + unit: str = "", + msg_desc: str = "", + sig_desc: str = "", + msg_period: int = 0, + file_name: str | None = None, + file_lbl: str | None = None, + scale: int = 1, + offset: int = 0 + ): + super(DAQVariable, self).__init__( + bus_name, + node_name, + msg_name, + sig_name, + dtype, + store_dtype=store_dtype, + unit=unit, + msg_desc=msg_desc, + sig_desc=sig_desc, + msg_period=msg_period + ) + self.id: int = id + self.read_only: bool = read_only + self.bit_length: int = bit_length + self.file: str = file_name + self.file_lbl: str = file_lbl + + self.pub_period_ms: int = 0 + + self.scale: int = scale + self.offset: int = offset + + self.is_dirty: bool = False + + @classmethod + def fromDAQVar(cls, id: int, var: dict, node: dict, bus: dict) -> DAQVariable: send_dtype = utils.data_types[var['type']] # If there is scaling going on, don't store as an integer on accident if ('scale' in var and var['scale'] != 1) or ('offset' in var and var['offset'] != 0): @@ -80,7 +109,7 @@ def fromDAQVar(id, var, node, bus): utils.log_error(f"Invalid bit length defined for DAQ variable {var['var_name']}") bit_length = var['length'] - return DAQVariable(bus['bus_name'], node['node_name'], f"daq_response_{node['node_name'].upper()}", var['var_name'], + return cls(bus['bus_name'], node['node_name'], f"daq_response_{node['node_name'].upper()}", var['var_name'], id, var['read_only'], bit_length, send_dtype, store_dtype=parse_dtype, unit=(var['unit'] if 'unit' in var else ""), @@ -89,7 +118,8 @@ def fromDAQVar(id, var, node, bus): scale=(var['scale'] if 'scale' in var else 1), offset=(var['offset'] if 'offset' in var else 0)) - def fromDAQFileVar(id, var, file_name, file_lbl, node, bus): + @classmethod + def fromDAQFileVar(cls, id: int, var: dict, file_name: str, file_lbl: str, node: dict, bus: dict) -> DAQVariable: send_dtype = utils.data_types[var['type']] # If there is scaling going on, don't store as an integer on accident if ('scale' in var and var['scale'] != 1) or ('offset' in var and var['offset'] != 0): @@ -99,7 +129,7 @@ def fromDAQFileVar(id, var, file_name, file_lbl, node, bus): # Calculate bit length bit_length = utils.data_type_length[var['type']] - return DAQVariable(bus['bus_name'], node['node_name'], f"daq_response_{node['node_name'].upper()}", var['var_name'], + return cls(bus['bus_name'], node['node_name'], f"daq_response_{node['node_name'].upper()}", var['var_name'], id, False, bit_length, send_dtype, store_dtype=parse_dtype, unit=(var['unit'] if 'unit' in var else ""), @@ -108,15 +138,15 @@ def fromDAQFileVar(id, var, file_name, file_lbl, node, bus): scale=(var['scale'] if 'scale' in var else 1), offset=(var['offset'] if 'offset' in var else 0)) - def update(self, bytes, timestamp): + def update(self, bytes: int, timestamp: float) -> None: val = np.frombuffer(bytes.to_bytes((self.bit_length + 7)//8, 'little'), dtype=self.send_dtype, count=1) val = val * self.scale + self.offset - return super().update(val, timestamp) + super().update(val, timestamp) - def reverseScale(self, val): + def reverseScale(self, val: float) -> float: return (val - self.offset) / self.scale - def valueSendable(self, val): + def valueSendable(self, val: float) -> bool: # TODO: check max and min from json config val = self.reverseScale(val) if 'uint' in str(self.send_dtype): @@ -129,27 +159,27 @@ def valueSendable(self, val): return False return True - def reverseToBytes(self, val): + def reverseToBytes(self, val: float) -> bytes | bool: if not self.valueSendable(val): return False # Value will not fit in the given dtype return (np.array([self.reverseScale(val)], dtype=self.send_dtype).tobytes()) - def getSendValue(self, val): + def getSendValue(self, val: float) -> float | bool: if not self.valueSendable(val): return False # Value will not fit in the given dtype # Convert to send a = np.array([self.reverseScale(val)], dtype=self.send_dtype)[0] # Convert back return a * self.scale + self.offset - def isDirty(self): + def isDirty(self) -> bool: if self.file_lbl == None: return False return self.is_dirty - def updateDirty(self, dirty): + def updateDirty(self, dirty: bool) -> None: if self.file_lbl == None: return self.is_dirty = dirty @property - def state(self): + def state(self) -> int: """ Read the value in blocking manner """ old_t = self.last_update_time utils.daqProt.readVar(self) @@ -162,7 +192,7 @@ def state(self): return self.curr_val @state.setter - def state(self, s): + def state(self, s: int) -> None: """ Writes the value in blocking manner """ if (self.read_only): utils.log_error(f"Can't write to read-only DAQ variable {self.signal_name} of {self.node_name}") @@ -172,8 +202,10 @@ def state(self, s): a = self.state if (abs(s - a) > 0.0001): utils.log_warning(f"Write failed for DAQ var {self.signal_name} of {self.node_name}") +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # class DaqProtocol(): """ Implements CAN daq protocol for modifying and live tracking of variables """ @@ -181,23 +213,22 @@ class DaqProtocol(): def __init__(self, bus: CanBus, daq_config: dict): super(DaqProtocol, self).__init__() - self.can_bus = bus - # self.can_bus.new_msg_sig.connect(self.handleDaqMsg) + self.can_bus: CanBus = bus self.can_bus.handle_daq_msg = self.handleDaqMsg self.updateVarDict(daq_config) # eeprom saving (prevent a load while save taking place) - self.last_save_request_id = 0 - self.save_in_progress = False + self.last_save_request_id: int = 0 + self.save_in_progress: bool = False utils.daqProt = self - self.curr_pin = 0 - self.curr_bank = 0 - self.curr_pin_val = 0 - self.pin_read_in_progress = False + self.curr_pin: int = 0 + self.curr_bank: int = 0 + self.curr_pin_val: int = 0 + self.pin_read_in_progress: bool = False - def readPin(self, node, bank, pin): + def readPin(self, node: str, bank: int, pin: int) -> None: """ Requests to read a GPIO pin, expects a reply """ dbc_msg = self.can_bus.db.get_message_by_name(f"daq_command_{node.upper()}") self.curr_bank = bank & DAQ_BANK_MASK @@ -209,7 +240,7 @@ def readPin(self, node, bank, pin): is_extended_id=True, data=data)) - def readVar(self, var: DAQVariable): + def readVar(self, var: DAQVariable) -> None: """ Requests to read a variable, expects a reply """ dbc_msg = self.can_bus.db.get_message_by_name(f"daq_command_{var.node_name.upper()}") data = [((var.id & DAQ_ID_MASK) << DAQ_CMD_LENGTH) | DAQ_CMD_READ] @@ -217,7 +248,7 @@ def readVar(self, var: DAQVariable): is_extended_id=True, data=data)) - def writeVar(self, var: DAQVariable, new_val): + def writeVar(self, var: DAQVariable, new_val: float) -> None: """ Writes to a variable """ dbc_msg = self.can_bus.db.get_message_by_name(f"daq_command_{var.node_name.upper()}") data = [((var.id & DAQ_ID_MASK) << DAQ_CMD_LENGTH) | DAQ_CMD_WRITE] @@ -231,7 +262,7 @@ def writeVar(self, var: DAQVariable, new_val): is_extended_id=True, data=data)) - def saveFile(self, var: DAQVariable): + def saveFile(self, var: DAQVariable) -> None: """ Saves variable state in eeprom, expects save complete reply """ if var.file_lbl == None: utils.log_error(f"Invalid save var operation for {var.signal_name}") @@ -251,7 +282,7 @@ def saveFile(self, var: DAQVariable): # self.last_save_request_id = var.id # self.save_in_progress_sig.emit(True) - def loadFile(self, var: DAQVariable): + def loadFile(self, var: DAQVariable) -> None: """ Loads a variable from eeprom, cannot be performed during save operation """ if var.file_lbl == None: utils.log_error(f"Invalid load var operation for {var.signal_name}") @@ -271,7 +302,7 @@ def loadFile(self, var: DAQVariable): data=data)) self.setFileClean(var) - def pubVar(self, var: DAQVariable, period_ms): + def pubVar(self, var: DAQVariable, period_ms: int) -> None: """ Requests to start publishing a variable at a specified period """ var.pub_period_ms = period_ms dbc_msg = self.can_bus.db.get_message_by_name(f"daq_command_{var.node_name.upper()}") @@ -281,7 +312,7 @@ def pubVar(self, var: DAQVariable, period_ms): is_extended_id=True, data=data)) - def forceFault(self, id, state): + def forceFault(self, id: int, state: int) -> None: print(f"Id: {id}, State: {state}") fault_msg = self.can_bus.db.get_message_by_name(f"set_fault") data = fault_msg.encode({"id": id, "value": state}) @@ -289,7 +320,7 @@ def forceFault(self, id, state): is_extended_id=True, data=data)) - def create_ids(self, fault_config): + def create_ids(self, fault_config: dict) -> dict: num = 0 idx = 0 for node in fault_config['modules']: @@ -313,7 +344,7 @@ def create_ids(self, fault_config): print("An error occured configuring a node.") return fault_config - def unforceFault(self, id): + def unforceFault(self, id: int) -> None: print(f"Id: {id}. Returning control!") fault_msg = self.can_bus.db.get_message_by_name(f"return_fault_control") data = fault_msg.encode({"id": id}) @@ -321,7 +352,7 @@ def unforceFault(self, id): is_extended_id=True, data=data)) - def pubVarStop(self, var: DAQVariable): + def pubVarStop(self, var: DAQVariable) -> None: """ Requests to stop publishing a variable """ var.pub_period_ms = 0 dbc_msg = self.can_bus.db.get_message_by_name(f"daq_command_{var.node_name.upper()}") @@ -330,7 +361,7 @@ def pubVarStop(self, var: DAQVariable): is_extended_id=True, data=data)) - def setFileClean(self, var_in_file: DAQVariable): + def setFileClean(self, var_in_file: DAQVariable) -> None: """ Sets all variables in a file to clean (usually after flushing) """ if (var_in_file.file_lbl == None): return node_d = utils.signals[var_in_file.bus_name][var_in_file.node_name] @@ -340,7 +371,7 @@ def setFileClean(self, var_in_file: DAQVariable): vars[file_var].updateDirty(False) - def setFileClean(self, var_in_file: DAQVariable): + def setFileClean(self, var_in_file: DAQVariable) -> None: """ Sets all variables in a file to clean (usually after flushing) """ if (var_in_file.file_lbl == None): return node_d = utils.signals[var_in_file.bus_name][var_in_file.node_name] @@ -350,7 +381,7 @@ def setFileClean(self, var_in_file: DAQVariable): vars[file_var].updateDirty(False) - def handleDaqMsg(self, msg: can.Message): + def handleDaqMsg(self, msg: can.Message) -> None: """ Interprets and runs commands from DAQ message """ # Return if not a DAQ message if (msg.arbitration_id >> 6) & 0xFFFFF != 0xFFFFF: return @@ -412,7 +443,7 @@ def handleDaqMsg(self, msg: can.Message): else: utils.log_warning(f"Got unexpected pin read response {node_name} bank {bank} pin {pin}") - def updateVarDict(self, daq_config: dict): + def updateVarDict(self, daq_config: dict) -> None: """ Creates dictionary of variable objects from daq configuration""" for bus in daq_config['busses']: # create bus keys @@ -440,19 +471,20 @@ def updateVarDict(self, daq_config: dict): utils.signals[bus['bus_name']][node['node_name']][f"daq_response_{node['node_name'].upper()}"][var['var_name']] = DAQVariable.fromDAQFileVar( id_counter, var, file['name'], file['eeprom_lbl'], node, bus) id_counter += 1 +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # class DAQPin(): - - def __init__(self, pin_name, board, bank, pin): - self.name = pin_name - self.board = board - self.bank = bank - self.pin = pin - self.t_last = time.time() + def __init__(self, pin_name: str, board: str, bank: int, pin: int): + self.name: str = pin_name + self.board: str = board + self.bank: int = bank + self.pin: int = pin + self.t_last: float = time.time() @property - def state(self): + def state(self) -> int: self.t_last = time.time() t_start = time.time() utils.daqProt.readPin(self.board, self.bank, self.pin) diff --git a/hil/components/component.py b/hil/components/component.py index cfa632a..b38d8fa 100644 --- a/hil/components/component.py +++ b/hil/components/component.py @@ -1,4 +1,9 @@ -import utils +from collections.abc import Callable +from typing import TYPE_CHECKING + +import hil.utils as utils +if TYPE_CHECKING: + from hil.hil import HIL class Component(): """ @@ -7,15 +12,15 @@ class Component(): When in measurement or emulation, a source must be specified. """ - def __init__(self, name, hil_con, mode, hil): - self.name = name + def __init__(self, name: str, hil_con: tuple[str, str], mode: str, hil: 'HIL'): + self.name: str = name self._state = 0 - self.inv_meas = False - self.inv_emul = False - self.read_func = None - self.write_func = None - self.hiZ_func = None + self.inv_meas: bool = False + self.inv_emul: bool = False + self.read_func: Callable[[], int] = None + self.write_func: Callable[[int], None] = None + self.hiZ_func: Callable[[], None] = None # TODO: allow both measure and emulation source @@ -62,12 +67,12 @@ def __init__(self, name, hil_con, mode, hil): if (hil_port_num >= 0): print(f"Creating new component '{self.name}' of type {mode} on {hil_con}") if (mode == "DI"): - if self.inv_meas: - self.read_func = lambda : not dev.read_gpio(hil_port_num) - else: - self.read_func = lambda : dev.read_gpio(hil_port_num) + if self.inv_meas: + self.read_func = lambda : not dev.read_gpio(hil_port_num) + else: + self.read_func = lambda : dev.read_gpio(hil_port_num) elif (mode == "AI"): - self.read_func = lambda : dev.read_analog(hil_port_num) + self.read_func = lambda : dev.read_analog(hil_port_num) elif (mode == "DO"): if self.inv_emul: self.write_func = lambda s: dev.write_gpio(hil_port_num, not s) @@ -89,7 +94,7 @@ def __init__(self, name, hil_con, mode, hil): self.hil = hil @property - def state(self): + def state(self) -> int: if self.read_func: self._state = self.read_func() elif self.write_func == None: @@ -97,7 +102,7 @@ def state(self): return self._state @state.setter - def state(self, s): + def state(self, s: int) -> None: if self.read_func == None: self._state = s if self.write_func: @@ -105,13 +110,13 @@ def state(self, s): else: utils.log_warning(f"Wrote to {self.name}, but no emulation source was found") - def hiZ(self): + def hiZ(self) -> None: if (self.hiZ_func): self.hiZ_func() else: utils.log_warning(f"hiZ is not supported for {self.name}") - def shutdown(self): + def shutdown(self) -> None: if (self.hiZ_func): self.hiZ_func() elif self.write_func: diff --git a/hil/hil.py b/hil/hil.py index 3d9a0c3..cc0b735 100644 --- a/hil/hil.py +++ b/hil/hil.py @@ -1,16 +1,17 @@ -import utils +from types import FrameType +import hil.utils as utils import os import signal import sys -import time -from pin_mapper import PinMapper -from hil_devices.hil_device import HilDevice -from hil_devices.serial_manager import SerialManager -from components.component import Component +from hil.pin_mapper import PinMapper +from hil.hil_devices.hil_device import HilDevice +from hil.hil_devices.serial_manager import SerialManager +from hil.components.component import Component -from communication.can_bus import CanBus -from communication.daq_protocol import DaqProtocol -from communication.daq_protocol import DAQPin +from hil.communication.can_bus import CanBus, BusSignal +from hil.communication.daq_protocol import DaqProtocol +from hil.communication.daq_protocol import DAQPin +from hil.communication.daq_protocol import DAQVariable """ HIL TESTER """ @@ -20,27 +21,22 @@ NET_MAP_PATH = "..\\net_maps" PIN_MAP_PATH = "..\\pin_maps" -PARAMS_PATH = "..\hil_params.json" +PARAMS_PATH = "..\\hil_params.json" class HIL(): - def __init__(self): utils.initGlobals() - self.components = {} - self.dut_connections = {} - self.hil_devices = {} - self.serial_manager = SerialManager() - self.hil_params = utils.load_json_config(PARAMS_PATH, None) - self.can_bus = None + self.components: dict[str, Component] = {} + self.dut_connections: dict[str, dict[str, dict[str, tuple[str, str]]]] = {} + self.hil_devices: dict[str, HilDevice] = {} + self.serial_manager: SerialManager = SerialManager() + self.hil_params: dict = utils.load_json_config(PARAMS_PATH, None) + self.can_bus: CanBus = None utils.hilProt = self signal.signal(signal.SIGINT, signal_int_handler) - self.global_failed_checks = [] - self.global_test_names = [] - self.global_check_count = 0 # multiple checks within a test - self.global_test_count = 0 - def init_can(self): + def init_can(self) -> None: config = self.hil_params self.daq_config = utils.load_json_config(os.path.join(config['firmware_path'], config['daq_config_path']), os.path.join(config['firmware_path'], config['daq_schema_path'])) self.can_config = utils.load_json_config(os.path.join(config['firmware_path'], config['can_config_path']), os.path.join(config['firmware_path'], config['can_schema_path'])) @@ -51,48 +47,31 @@ def init_can(self): self.can_bus.connect() self.can_bus.start() - def load_pin_map(self, net_map, pin_map): + def load_pin_map(self, net_map: str, pin_map: str) -> None: net_map_f = os.path.join(NET_MAP_PATH, net_map) pin_map_f = os.path.join(PIN_MAP_PATH, pin_map) self.pin_map = PinMapper(net_map_f) self.pin_map.load_mcu_pin_map(pin_map_f) - def clear_components(self): + def clear_components(self) -> None: """ Reset HIL""" for c in self.components.values(): c.shutdown() self.components = {} - def clear_hil_devices(self): + def clear_hil_devices(self) -> None: self.hil_devices = {} self.serial_manager.close_devices() - def shutdown(self): + def shutdown(self) -> None: self.clear_components() self.clear_hil_devices() self.stop_can() - print(f"{utils.bcolors.OKCYAN}{utils.bcolors.UNDERLINE}") - utils.log("TEST SUMMARY") - print(f"{utils.bcolors.ENDC}") - utils.log(f"{self.global_test_count} tests with {self.global_check_count} checks performed:") - utils.log(', '.join(self.global_test_names)) - num_fail = len(self.global_failed_checks) - num_pass = self.global_check_count - num_fail - if (self.global_check_count == 0): - return - utils.log(f"{num_pass}/{self.global_check_count} ({num_pass/self.global_check_count*100:.5}%) of checks passing") - if (num_fail > 0): - utils.log(f"{utils.bcolors.FAIL}{utils.bcolors.BOLD}Failing {num_fail} checks:{utils.bcolors.ENDC}") - for c in self.global_failed_checks: - utils.log(f"{c[0]} - {c[1]}") - else: - utils.log(f"{utils.bcolors.OKGREEN}{utils.bcolors.BOLD}ALL CHECKS PASSING{utils.bcolors.ENDC}") - - - def stop_can(self): + def stop_can(self) -> None: if not self.can_bus: return + if self.can_bus.connected: self.can_bus.connected = False self.can_bus.join() @@ -101,7 +80,7 @@ def stop_can(self): # pass self.can_bus.disconnect_bus() - def load_config(self, config_name): + def load_config(self, config_name: str) -> None: config = utils.load_json_config(os.path.join(CONFIG_PATH, config_name), None) # TODO: validate w/ schema # TODO: support joining configs @@ -112,7 +91,7 @@ def load_config(self, config_name): # Setup corresponding components self.load_connections(config['dut_connections']) - def load_connections(self, dut_connections): + def load_connections(self, dut_connections: dict) -> None: self.dut_connections = {} # Dictionary format: # [board][connector][pin] = (hil_device, port) @@ -128,7 +107,7 @@ def load_connections(self, dut_connections): self.dut_connections[board_name][connector] = {} self.dut_connections[board_name][connector][pin] = hil_port - def add_component(self, board, net, mode): + def add_component(self, board: str, net: str, mode: str) -> Component: # If board is a HIL device, net is expected to be port name # If board is a DUT device, net is expected to be a net name from the board if board in self.hil_devices: @@ -143,7 +122,7 @@ def add_component(self, board, net, mode): utils.log_warning(f"Component {comp_name} already exists") return self.components[comp_name] - def load_hil_devices(self, hil_devices): + def load_hil_devices(self, hil_devices: dict) -> None: self.clear_hil_devices() self.serial_manager.discover_devices() for hil_device in hil_devices: @@ -152,13 +131,13 @@ def load_hil_devices(self, hil_devices): else: self.handle_error(f"Failed to discover HIL device {hil_device['name']} with id {hil_device['id']}") - def get_hil_device(self, name): + def get_hil_device(self, name: str) -> HilDevice: if name in self.hil_devices: return self.hil_devices[name] else: self.handle_error(f"HIL device {name} not recognized") - def get_hil_device_connection(self, board, net): + def get_hil_device_connection(self, board: str, net: str) -> tuple[str, str]: """ Converts dut net to hil port name """ if not board in self.dut_connections: self.handle_error(f"No connections to {board} found in configuration.") @@ -176,75 +155,53 @@ def get_hil_device_connection(self, board, net): utils.log_warning(net_cons) self.handle_error(f"Connect dut to {net} on {board}.") - def din(self, board, net): + def din(self, board: str, net: str) -> Component: return self.add_component(board, net, 'DI') - - def dout(self, board, net): - return self.add_component(board, net, 'DO') - def ain(self, board, net): + def dout(self, board: str, net: str) -> Component: + return self.add_component(board, net, 'DO') + + def ain(self, board: str, net: str) -> Component: return self.add_component(board, net, 'AI') - def aout(self, board, net): + def aout(self, board: str, net: str) -> Component: return self.add_component(board, net, 'AO') - def pot(self, board, net): + def pot(self, board: str, net: str) -> Component: return self.add_component(board, net, 'POT') - - def daq_var(self, board, var_name): + + def daq_var(self, board: str, var_name: str) -> DAQVariable: try: return utils.signals[utils.b_str][board][f"daq_response_{board.upper()}"][var_name] - except KeyError as e: + except KeyError: self.handle_error(f"Unable to locate DAQ variable {var_name} of {board}") - def can_var(self, board, message_name, signal_name): + def can_var(self, board: str, message_name: str, signal_name: str) -> BusSignal: try: return utils.signals[utils.b_str][board][message_name][signal_name] except KeyError: self.handle_error(f"Unable to locate CAN signal {signal_name} of message {message_name} of board {board}") - def mcu_pin(self, board, net): + def mcu_pin(self, board: str, net: str) -> DAQPin: bank, pin = self.pin_map.get_mcu_pin(board, net) if bank == None: self.handle_error(f"Failed to get mcu pin for {board} net {net}") return DAQPin(net, board, bank, pin) - def start_test(self, name): - print(f"{utils.bcolors.OKCYAN}Starting {name}{utils.bcolors.ENDC}") - self.curr_test = name - self.curr_test_fail_count = 0 - self.curr_test_count = 0 - self.global_test_count = self.global_test_count + 1 - self.global_test_names.append(name) - - def check(self, stat, check_name): - stat_str = "PASS" if stat else "FAIL" - stat_clr = utils.bcolors.OKGREEN if stat else utils.bcolors.FAIL - print(f"{self.curr_test + ' - ' + check_name:<50}: {stat_clr+'['+stat_str+']'+utils.bcolors.ENDC:>10}") - if (not stat): - self.curr_test_fail_count = self.curr_test_fail_count + 1 - self.global_failed_checks.append((self.curr_test,check_name)) - self.curr_test_count = self.curr_test_count + 1 - self.global_check_count = self.global_check_count + 1 - return stat - - def check_within(self, val1, val2, thresh, check_name): - self.check(abs(val1 - val2) <= thresh, check_name) - - def end_test(self): - print(f"{utils.bcolors.OKCYAN}{self.curr_test} failed {self.curr_test_fail_count} out of {self.curr_test_count} checks{utils.bcolors.ENDC}") - - def handle_error(self, msg): + def handle_error(self, msg: str) -> None: utils.log_error(msg) self.shutdown() exit(0) -def signal_int_handler(signum, frame): + +def signal_int_handler(signum: int, frame: FrameType) -> None: utils.log("Received signal interrupt, shutting down") - if (utils.hilProt): + if utils.hilProt: utils.hilProt.shutdown() sys.exit(0) -if __name__ == "__main__": - hil = HIL() - hil.load_config("config_test.json") + +# Old testing code. When run directly (python hil.py), this code will run. +# if __name__ == "__main__": +# hil = HIL() +# hil.load_config("config_test.json") diff --git a/hil/hil_devices/hil_device.py b/hil/hil_devices/hil_device.py index 92e7645..3cfc6cf 100644 --- a/hil/hil_devices/hil_device.py +++ b/hil/hil_devices/hil_device.py @@ -1,28 +1,26 @@ import os -import time -import serial -import serial.tools.list_ports -import utils +from hil.hil_devices.serial_manager import SerialManager -HIL_CMD_MASK = 0xFF -HIL_CMD_READ_ADC = 0 -HIL_CMD_READ_GPIO = 1 -HIL_CMD_WRITE_DAC = 2 -HIL_CMD_WRITE_GPIO = 3 -HIL_CMD_READ_ID = 4 -HIL_CMD_WRITE_POT = 5 +import hil.utils as utils -HIL_ID_MASK = 0xFF +HIL_CMD_READ_ADC = 0 # command, pin +HIL_CMD_READ_GPIO = 1 # command, pin +HIL_CMD_WRITE_DAC = 2 # command, pin, value (2 bytes) +HIL_CMD_WRITE_GPIO = 3 # command, pin, value +HIL_CMD_READ_ID = 4 # command +HIL_CMD_WRITE_POT = 5 # command, pin, value -HIL_DEVICES_PATH = "..\hil\hil_devices" +SERIAL_MASK = 0xFF # 2^8 - 1 +SERIAL_BITS = 8 # char -class HilDevice(): +HIL_DEVICES_PATH = "../hil/hil_devices" - def __init__(self, name, type, id, serial_manager): - self.name = name - self.type = type - self.id = id - self.sm = serial_manager +class HilDevice(): + def __init__(self, name: str, type: str, id: int, serial_manager: SerialManager): + self.name: str = name + self.type: str = type + self.id: int = id + self.sm: SerialManager = serial_manager self.config = utils.load_json_config(os.path.join(HIL_DEVICES_PATH, f"hil_device_{self.type}.json"), None) # TODO: validate w/ schema @@ -61,7 +59,7 @@ def __init__(self, name, type, id, serial_manager): if "pot_config" in self.config: self.pot_max = pow(2, self.config['pot_config']['bit_resolution']) - 1 - def get_port_number(self, port_name, mode): + def get_port_number(self, port_name: str, mode: str) -> int: for p in self.config['ports']: if port_name == p['name']: if mode in p['capabilities']: @@ -75,18 +73,19 @@ def get_port_number(self, port_name, mode): utils.log_error(f"Port {port_name} not found for hil device {self.name}") return -1 - def write_gpio(self, pin, value): - data = [(HIL_CMD_WRITE_GPIO & HIL_CMD_MASK), (pin & HIL_ID_MASK), value] + def write_gpio(self, pin: int, value: int) -> None: + data = [(HIL_CMD_WRITE_GPIO & SERIAL_MASK), (pin & SERIAL_MASK), value] self.sm.send_data(self.id, data) - def write_dac(self, pin, value): - value = min(self.dac_max, max(0, int(value * self.volts_to_dac))) - data = [(HIL_CMD_WRITE_DAC & HIL_CMD_MASK), (pin & HIL_ID_MASK), value] - # print(f"write pin {pin} to {value}") + def write_dac(self, pin: int, voltage: float) -> None: + value = int(voltage * self.volts_to_dac) + char_1 = (value >> SERIAL_BITS) & SERIAL_MASK + char_2 = value & SERIAL_MASK + data = [(HIL_CMD_WRITE_DAC & SERIAL_MASK), (pin & SERIAL_MASK), char_1, char_2] self.sm.send_data(self.id, data) - def read_gpio(self, pin): - data = [(HIL_CMD_READ_GPIO & HIL_CMD_MASK), (pin & HIL_ID_MASK), 0] + def read_gpio(self, pin: int) -> int: + data = [(HIL_CMD_READ_GPIO & SERIAL_MASK), (pin & SERIAL_MASK)] self.sm.send_data(self.id, data) d = self.sm.read_data(self.id, 1) if len(d) == 1: @@ -94,8 +93,8 @@ def read_gpio(self, pin): if (d <= 1): return d utils.log_error(f"Failed to read gpio pin {pin} on {self.name}") - def read_analog(self, pin): - data = [(HIL_CMD_READ_ADC & HIL_CMD_MASK), (pin & HIL_ID_MASK), 0] + def read_analog(self, pin: int) -> float: + data = [(HIL_CMD_READ_ADC & SERIAL_MASK), (pin & SERIAL_MASK)] self.sm.send_data(self.id, data) d = self.sm.read_data(self.id, 2) if len(d) == 2: @@ -104,8 +103,7 @@ def read_analog(self, pin): utils.log_error(f"Failed to read adc pin {pin} on {self.name}") return 0 - def write_pot(self, pin, value): + def write_pot(self, pin: int, value: float) -> None: value = min(self.pot_max, max(0, int(value * self.pot_max))) - data = [(HIL_CMD_WRITE_POT & HIL_CMD_MASK), (pin & HIL_ID_MASK), value] - #print(f"sending {value} to pin {pin}") - self.sm.send_data(self.id, data) + data = [(HIL_CMD_WRITE_POT & SERIAL_MASK), (pin & SERIAL_MASK), value] + self.sm.send_data(self.id, data) \ No newline at end of file diff --git a/hil/hil_devices/hil_device_arduino_micro.json b/hil/hil_devices/hil_device_arduino_micro.json index 5e85d1d..eb66a6d 100644 --- a/hil/hil_devices/hil_device_arduino_micro.json +++ b/hil/hil_devices/hil_device_arduino_micro.json @@ -26,6 +26,6 @@ {"port":21, "name":"A3", "capabilities":["DI", "AI", "DO"], "notes":"analog / digital input 5V"}, {"port":22, "name":"A4", "capabilities":["DI", "AI", "DO"], "notes":"analog / digital input 5V"}, {"port":23, "name":"A5", "capabilities":["DI", "AI", "DO"], "notes":"analog / digital input 5V"} - ], - "adc_config":{"bit_resolution":10, "reference_v":5.0} + ], + "adc_config":{"bit_resolution":10, "reference_v":5.0} } \ No newline at end of file diff --git a/hil/hil_devices/serial_manager.py b/hil/hil_devices/serial_manager.py index 0f0dc54..4d47005 100644 --- a/hil/hil_devices/serial_manager.py +++ b/hil/hil_devices/serial_manager.py @@ -6,10 +6,10 @@ class SerialManager(): """ Manages hil device discovery and communication """ def __init__(self): - self.devices = {} + self.devices: dict[int, serial.Serial] = {} - def discover_devices(self): - #print([a[1] for a in serial.tools.list_ports.comports()]) + def discover_devices(self) -> None: + # print([a[0] for a in serial.tools.list_ports.comports()]) ports = [a[0] for a in serial.tools.list_ports.comports() if ("Arduino" in a[1] or "USB Serial Device" in a[1])] self.devices = {} print('Arduinos found on ports ' + str(ports)) @@ -26,8 +26,8 @@ def discover_devices(self): ard.setDTR(True) # Uno takes a while startup, have to treat it nicely for _ in range(5): - # Get Tester id - ard.write(b'\x04\x00\x00') + # 4 = HIL_CMD_READ_ID + ard.write(b'\x04') i = ard.read(1) if (len(i) == 1): break @@ -39,15 +39,15 @@ def discover_devices(self): ard.close() print('Tester ids: ' + str(list(self.devices.keys()))) - def port_exists(self, id): + def port_exists(self, id: int) -> bool: return id in self.devices - def send_data(self, id, data): + def send_data(self, id: int, data: list[int]) -> None: self.devices[id].write(data) - def read_data(self, id, length): + def read_data(self, id: int, length: int) -> bytes: return self.devices[id].read(length) - def close_devices(self): + def close_devices(self) -> None: for d in self.devices.values(): d.close() diff --git a/hil/pin_mapper.py b/hil/pin_mapper.py index 8b61edd..0212453 100644 --- a/hil/pin_mapper.py +++ b/hil/pin_mapper.py @@ -2,7 +2,7 @@ import os #from hil_devices.hil_device import HilDevice import csv -import utils +import hil.utils as utils """ PIN MAPPER """ @@ -10,18 +10,22 @@ class PinMapper(): - def __init__(self, net_map): + def __init__(self, net_map: str): #utils.initGlobals() + + # [board name][net name] = [(component, designator, connector name), ...] + self.net_map: dict[str, dict[str, list[tuple[str, str, str]]]] = {} + self.net_map_fname: str = "" + + # [designator] = (bank, pin) + self.mcu_pin_map: dict[int, tuple[int, int]] = {} + self.mcu_pin_name_fname: str = "" + self.load_net_map(net_map) - self.mcu_pin_map = {} - def load_net_map(self, fname): + def load_net_map(self, fname: str) -> None: self.net_map_fname = fname - self.net_map = {} - # CSV format: - # Board,Net,Component,Designator,Connector Name,, - # Create dictionary as follows - # [board name][net name] = [(component, designator, connector name), ...] + # CSV format: Board,Net,Component,Designator,Connector Name,, with open(self.net_map_fname, mode='r') as f: csv_file = csv.DictReader(f) for row in csv_file: @@ -35,13 +39,10 @@ def load_net_map(self, fname): net_map_board[net] = [] self.net_map[board][net].append(items) - def load_mcu_pin_map(self, fname): + def load_mcu_pin_map(self, fname: str) -> None: self.mcu_pin_name_fname = fname self.mcu_pin_map = {} - # CSV format: - # Designator,Pin Name,Type - # Create dictionary as follows - # [designator] = (bank, pin) + # CSV format: Designator,Pin Name,Type with open(self.mcu_pin_name_fname, mode='r') as f: csv_file = csv.DictReader(f) for row in csv_file: @@ -52,7 +53,7 @@ def load_mcu_pin_map(self, fname): pin = int(row['Pin Name'][2:]) self.mcu_pin_map[designator] = (bank, pin) - def get_mcu_pin(self, board, net): + def get_mcu_pin(self, board: str, net: str) -> tuple[int, int]: """ Returns first MCU pin found that is connected """ connections = self.get_net_connections(board, net) for connection in connections: @@ -65,7 +66,7 @@ def get_mcu_pin(self, board, net): utils.log_error(f"Net {net} on board {board} is not connected to MCU.") return (None, None) - def get_net_connections(self, board, net): + def get_net_connections(self, board: str, net: str) -> list[tuple[str, str, str]]: """ [(component, designator, connector name), ...] """ if not board in self.net_map: utils.log_error(f"Unrecogniazed board {board}.") diff --git a/hil/utils.py b/hil/utils.py index be46c8f..16af398 100644 --- a/hil/utils.py +++ b/hil/utils.py @@ -1,42 +1,93 @@ -import json -from jsonschema import validate -from jsonschema.exceptions import ValidationError +from typing import TYPE_CHECKING + import sys import time import numpy as np +import json +from jsonschema import validate +from jsonschema.exceptions import ValidationError + +from hil.components.component import Component +from hil.communication.daq_protocol import DaqProtocol + +if TYPE_CHECKING: + from hil.hil import HIL + + +signals: dict = {} +b_str: str = "" +data_types: dict[str, np.dtype] = {} +data_type_length: dict[str, int] = {} +debug_mode: bool = True +daqProt: DaqProtocol = None +hilProt: 'HIL' = None + + def initGlobals(): global signals signals = {} - global plot_x_range_sec - plot_x_range_sec = 10 - global events - events = [] + # Structure of signals based on daq_config and can_config + # signals = { + # 'bus_name': { # busses; ex: "Main", "Test" + # 'node_name': { # busses->nodes; ex: "Main_Module", "Dashboard" + # 'msg_name': { # busses->nodes->tx; ex: "main_hb", "coolant_temps" + # 'sig_name': BusSignal # busses->nodes->tx->signals; ex: "car_state", "battery_in_temp" + # }, + # "daq_response_{node['node_name'].upper()}": { # busses->nodes; ex: "daq_response_MAIN_MODULE", "daq_response_DASHBOARD" + # 'var_name': DAQVariable # busses->nodes->variables | busses->nodes->files->contents; ex: "cal_steer_angle", "sdc_main_status", "blue_on", "odometer" + # }, + # "files": { + # 'name': { # busses->nodes->files; ex: "config" + # "contents": [ + # 'var_name' # busses->nodes->files->contents; ex: "blue_on", "odometer" + # ] + # } + # } + # } + # } + # } + + global b_str b_str = "Main" + global data_types data_types = { - 'uint8_t':np.dtype(' None: print(f"{bcolors.FAIL}ERROR: {phrase}{bcolors.ENDC}") -def log_warning(phrase): +def log_warning(phrase: str) -> None: log(f"{bcolors.WARNING}WARNING: {phrase}{bcolors.ENDC}") -def log_success(phrase): +def log_success(phrase: str) -> None: log(f"{bcolors.OKGREEN}{phrase}{bcolors.ENDC}") -def log(phrase): +def log(phrase: str) -> None: global debug_mode if debug_mode: print(phrase) -def load_json_config(config_path, schema_path=None): +def load_json_config(config_path: str, schema_path: str = None) -> dict: """ loads config from json and validates with schema """ config = json.load(open(config_path)) if (schema_path == None): return config # Bypass schema check @@ -78,20 +129,20 @@ def load_json_config(config_path, schema_path=None): return config -def clearDictItems(dictionary:dict): - """ recursively calls clear on items in multidimensional dict""" - for key, value in dictionary.items(): +def clearDictItems(dictionary: dict) -> None: + """Recursively calls clear on items in multidimensional dict""" + for value in dictionary.values(): if type(value) is dict: clearDictItems(value) else: value.clear() -def clear_term_line(): +def clear_term_line() -> None: sys.stdout.write('\033[F\033[K') #sys.stdout.flush() # Credit: https://stackoverflow.com/questions/1133857/how-accurate-is-pythons-time-sleep/76554895#76554895 -def high_precision_sleep(duration): +def high_precision_sleep(duration: float) -> None: start_time = time.perf_counter() while True: elapsed_time = time.perf_counter() - start_time @@ -102,21 +153,22 @@ def high_precision_sleep(duration): time.sleep(max(remaining_time/2, 0.0001)) # Sleep for the remaining time or minimum sleep interval else: pass -class VoltageDivider(): - def __init__(self, r1, r2): + +class VoltageDivider(): + def __init__(self, r1: float, r2: float): self.r1 = float(r1) self.r2 = float(r2) self.ratio = (self.r2 / (self.r1 + self.r2)) - def div(self, input): + def div(self, input: float) -> float: return input * self.ratio - def reverse(self, output): + def reverse(self, output: float) -> float: return output / self.ratio -def measure_trip_time(trip_sig, timeout, is_falling=False): +def measure_trip_time(trip_sig: Component, timeout: float, is_falling: bool = False) -> float: t_start = time.time() while(trip_sig.state == is_falling): time.sleep(0.015) @@ -128,7 +180,15 @@ def measure_trip_time(trip_sig, timeout, is_falling=False): return t_delt -def measure_trip_thresh(thresh_sig, start, stop, step, period_s, trip_sig, is_falling=False): +def measure_trip_thresh( + thresh_sig: Component, + start: float, + stop: float, + step: float, + period_s: float, + trip_sig: Component, + is_falling: bool = False +) -> float: gain = 1000 thresh = start _start = int(start * gain) diff --git a/hil_params.json b/hil_params.json index b9562e7..142f056 100644 --- a/hil_params.json +++ b/hil_params.json @@ -1,5 +1,5 @@ { - "firmware_path": "C:/users/lukeo/Documents/firmware", + "firmware_path": "/home/millankumar/Documents/PER/firmware", "daq_config_path": "common/daq/daq_config.json", "daq_schema_path": "common/daq/daq_schema.json", "dbc_path": "common/daq/per_dbc.dbc", diff --git a/scripts/test_abox.py b/scripts/test_abox.py index 042024a..99e9269 100644 --- a/scripts/test_abox.py +++ b/scripts/test_abox.py @@ -1,15 +1,39 @@ -from os import sys, path -sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) -from hil import HIL -import utils +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +from hil.hil import HIL +import hil.utils as utils import time from rules_constants import * from vehicle_constants import * +import pytest_check as check +import pytest + + + +# ---------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def hil(): + hil_instance = HIL() + + hil_instance.load_config("config_abox_bench.json") + hil_instance.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + hil_instance.init_can() + + yield hil_instance + + hil_instance.shutdown() +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # def test_abox_ams(hil): # Begin the test - hil.start_test(test_abox_ams.__name__) + # hil.start_test(test_abox_ams.__name__) # Outputs den = hil.dout("a_box", "Discharge Enable") @@ -35,16 +59,20 @@ def test_abox_ams(hil): bms_stat.state = bms_set print(f"Combo {i}") time.sleep(0.1) - hil.check(chrg_stat.state == exp_chrg, f"Chrg stat {exp_chrg}") - hil.check(main_stat.state == exp_dchg, f"Main stat {exp_dchg}") + # hil.check(chrg_stat.state == exp_chrg, f"Chrg stat {exp_chrg}") + # hil.check(main_stat.state == exp_dchg, f"Main stat {exp_dchg}") + check.equal(chrg_stat.state, exp_chrg, f"Chrg stat {exp_chrg}") + check.equal(main_stat.state, exp_dchg, f"Main stat {exp_dchg}") bms_override.state = 0 - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # def test_isense(hil): # Begin the test - hil.start_test(test_isense.__name__) + # hil.start_test(test_isense.__name__) # Outputs ch1_raw = hil.aout("a_box", "Isense_Ch1_raw") @@ -60,21 +88,26 @@ def test_isense(hil): input(f"enter to meas, set to {v}, expected {exp_out}") meas = ch1_filt.state print(f"isense expected: {exp_out}V, measured: {meas}V") - hil.check_within(meas, exp_out, 0.05, f"Isense v={v:.3}") + # hil.check_within(meas, exp_out, 0.05, f"Isense v={v:.3}") + check.almost_equal(meas, exp_out, abs=0.05, rel=0.0, msg=f"Isense v={v:.3}") ch1_raw.hiZ() time.sleep(0.01) - hil.check_within(ch1_filt.state, 0.0, 0.05, f"Isense float pulled down") + # hil.check_within(ch1_filt.state, 0.0, 0.05, f"Isense float pulled down") + check.almost_equal(ch1_filt.state, 0.0, abs=0.05, rel=0.0, msg="Isense float pulled down") + + # hil.end_test() +# ---------------------------------------------------------------------------- # - hil.end_test() +# ---------------------------------------------------------------------------- # RLY_ON = 0 RLY_OFF = 1 RLY_DLY = 0.01 # Mechanicl relay takes time to transition def test_precharge(hil): # Begin the test - hil.start_test(test_precharge.__name__) + # hil.start_test(test_precharge.__name__) # Outputs n_pchg_cmplt = hil.dout("a_box", "NotPrechargeComplete") @@ -90,46 +123,58 @@ def test_precharge(hil): n_pchg_cmplt.state = 0 sdc.state = RLY_OFF time.sleep(RLY_DLY) - hil.check(resistor.state == 0, "Resistor disconnected") + # hil.check(resistor.state == 0, "Resistor disconnected") + check.equal(resistor.state, 0, "Combo 1, resistor disconnected") print("Combo 2") n_pchg_cmplt.state = 1 sdc.state = RLY_OFF time.sleep(RLY_DLY) - hil.check(resistor.state == 0, "Resistor disconnected") + # hil.check(resistor.state == 0, "Resistor disconnected") + check.equal(resistor.state, 0, "Combo 2, resistor disconnected") print("Combo 3") n_pchg_cmplt.state = 1 sdc.state = RLY_ON time.sleep(RLY_DLY) - hil.check(resistor.state == 1, "Resistor connected") + # hil.check(resistor.state == 1, "Resistor connected") + check.equal(resistor.state, 1, "Combo 3, resistor connected") print("Combo 4") n_pchg_cmplt.state = 0 sdc.state = RLY_ON time.sleep(RLY_DLY) - hil.check(resistor.state == 0, "Resistor disconnected") + # hil.check(resistor.state == 0, "Resistor disconnected") + check.equal(resistor.state, 0, "Combo 4, resistor disconnected") # Duration test time.sleep(1) n_pchg_cmplt.state = 1 sdc.state = RLY_ON time.sleep(RLY_DLY) - hil.check(resistor.state == 1, "Duration init") + # hil.check(resistor.state == 1, "Duration init") + check.equal(resistor.state, 1, "Duration init") + time.sleep(9) - hil.check(resistor.state == 1, "Duration mid") + # hil.check(resistor.state == 1, "Duration mid") + check.equal(resistor.state, 1, "Duration mid") + n_pchg_cmplt.state = 0 time.sleep(RLY_DLY) - hil.check(resistor.state == 0, "Duration end") + # hil.check(resistor.state == 0, "Duration end") + check.equal(resistor.state, 0, "Duration end") + + # hil.end_test() +# ---------------------------------------------------------------------------- # - hil.end_test() +# ---------------------------------------------------------------------------- # SUPPLY_VOLTAGE = 24.0 TIFF_DLY = 0.3 def test_tiffomy(hil): # Begin the test - hil.start_test(test_tiffomy.__name__) + # hil.start_test(test_tiffomy.__name__) # Outputs bat_p = hil.dout("a_box", "Batt+") @@ -147,8 +192,10 @@ def test_tiffomy(hil): bat_p.state = RLY_OFF time.sleep(TIFF_DLY) - hil.check_within(vbat.state, 0.0, 0.1, "TIff off") - hil.check(imd_hv.state == 0, "IMD HV off") + # hil.check_within(vbat.state, 0.0, 0.1, "TIff off") + # hil.check(imd_hv.state == 0, "IMD HV off") + check.almost_equal(vbat.state, 0.0, abs=0.1, rel=0.0, msg="TIff off") + check.equal(imd_hv.state, 0, "IMD HV off") bat_p.state = RLY_ON time.sleep(TIFF_DLY) @@ -156,15 +203,18 @@ def test_tiffomy(hil): #input("press enter, tiff should be getting volts") meas = tiff_lv_to_hv(vbat.state) print(f"Tiff HV reading: {meas} V, expect: {SUPPLY_VOLTAGE} V") - hil.check_within(meas, exp, 2.5, "Tiff on") - hil.check(imd_hv.state == 1, "IMD HV on") - - hil.end_test() + # hil.check_within(meas, exp, 2.5, "Tiff on") + # hil.check(imd_hv.state == 1, "IMD HV on") + check.almost_equal(meas, exp, abs=2.5, rel=0.0, msg="Tiff on") + check.equal(imd_hv.state, 1, "IMD HV on") + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # def test_tmu(hil): # Begin the test - hil.start_test(test_tmu.__name__) + # hil.start_test(test_tmu.__name__) # Outputs tmu_a_do = hil.dout("a_box", "TMU_1") @@ -193,10 +243,14 @@ def test_tmu(hil): for i in range(0,16): daq_therm.state = i time.sleep(0.05) - hil.check(mux_a.state == bool(i & 0x1), f"Mux A test {i}") - hil.check(mux_b.state == bool(i & 0x2), f"Mux B test {i}") - hil.check(mux_c.state == bool(i & 0x4), f"Mux C test {i}") - hil.check(mux_d.state == bool(i & 0x8), f"Mux D test {i}") + # hil.check(mux_a.state == bool(i & 0x1), f"Mux A test {i}") + # hil.check(mux_b.state == bool(i & 0x2), f"Mux B test {i}") + # hil.check(mux_c.state == bool(i & 0x4), f"Mux C test {i}") + # hil.check(mux_d.state == bool(i & 0x8), f"Mux D test {i}") + check.equal(mux_a.state, bool(i & 0x1), f"Mux A test {i}") + check.equal(mux_b.state, bool(i & 0x2), f"Mux B test {i}") + check.equal(mux_c.state, bool(i & 0x4), f"Mux C test {i}") + check.equal(mux_d.state, bool(i & 0x8), f"Mux D test {i}") daq_override.state = 0 @@ -215,16 +269,21 @@ def test_tmu(hil): c = int(tmu_c_ai.state) d = int(tmu_d_ai.state) print(f"Readings at therm={i}: {a}, {b}, {c}, {d}") - hil.check_within(a, TMU_HIGH_VALUE if (i & 0x1) else 0, TMU_TOLERANCE, f"TMU 1 test {i}") - hil.check_within(b, TMU_HIGH_VALUE if (i & 0x2) else 0, TMU_TOLERANCE, f"TMU 2 test {i}") - hil.check_within(c, TMU_HIGH_VALUE if (i & 0x4) else 0, TMU_TOLERANCE, f"TMU 3 test {i}") - hil.check_within(d, TMU_HIGH_VALUE if (i & 0x8) else 0, TMU_TOLERANCE, f"TMU 4 test {i}") - - - hil.end_test() - + # hil.check_within(a, TMU_HIGH_VALUE if (i & 0x1) else 0, TMU_TOLERANCE, f"TMU 1 test {i}") + # hil.check_within(b, TMU_HIGH_VALUE if (i & 0x2) else 0, TMU_TOLERANCE, f"TMU 2 test {i}") + # hil.check_within(c, TMU_HIGH_VALUE if (i & 0x4) else 0, TMU_TOLERANCE, f"TMU 3 test {i}") + # hil.check_within(d, TMU_HIGH_VALUE if (i & 0x8) else 0, TMU_TOLERANCE, f"TMU 4 test {i}") + check.almost_equal(a, TMU_HIGH_VALUE if (i & 0x1) else 0, abs=TMU_TOLERANCE, rel=0.0, msg=f"TMU 1 test {i}") + check.almost_equal(b, TMU_HIGH_VALUE if (i & 0x2) else 0, abs=TMU_TOLERANCE, rel=0.0, msg=f"TMU 2 test {i}") + check.almost_equal(c, TMU_HIGH_VALUE if (i & 0x4) else 0, abs=TMU_TOLERANCE, rel=0.0, msg=f"TMU 3 test {i}") + check.almost_equal(d, TMU_HIGH_VALUE if (i & 0x8) else 0, abs=TMU_TOLERANCE, rel=0.0, msg=f"TMU 4 test {i}") + + # hil.end_test() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # def test_imd(hil): - hil.start_test(test_imd.__name__) + # hil.start_test(test_imd.__name__) # Outputs imd_out = hil.dout('a_box', 'IMD_Status') @@ -237,36 +296,26 @@ def test_imd(hil): imd_out.state = RLY_OFF time.sleep(RLY_DLY) - hil.check(imd_in.state == 0, 'IMD LV OFF') - hil.check(imd_mcu.state == 0, 'IMD MCU OFF') + # hil.check(imd_in.state == 0, 'IMD LV OFF') + # hil.check(imd_mcu.state == 0, 'IMD MCU OFF') + check.equal(imd_in.state, 0, 'IMD LV OFF') + check.equal(imd_mcu.state, 0, 'IMD MCU OFF') imd_out.state = RLY_ON time.sleep(RLY_DLY) - hil.check(imd_in.state == 1, 'IMD LV ON') - hil.check(imd_mcu.state == 1, 'IMD MCU ON') + # hil.check(imd_in.state == 1, 'IMD LV ON') + # hil.check(imd_mcu.state == 1, 'IMD MCU ON') + check.equal(imd_in.state, 1, 'IMD LV ON') + check.equal(imd_mcu.state, 1, 'IMD MCU ON') imd_out.state = RLY_OFF time.sleep(RLY_DLY) - hil.check(imd_in.state == 0, 'IMD LV BACK OFF') - hil.check(imd_mcu.state == 0, 'IMD MCU BACK OFF') - - hil.end_test() - - -if __name__ == "__main__": - hil = HIL() - hil.load_config("config_abox_bench.json") - hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") - - hil.init_can() - - test_abox_ams(hil) - test_isense(hil) - test_precharge(hil) - test_tiffomy(hil) - test_tmu(hil) - test_imd(hil) + # hil.check(imd_in.state == 0, 'IMD LV BACK OFF') + # hil.check(imd_mcu.state == 0, 'IMD MCU BACK OFF') + check.equal(imd_in.state, 0, 'IMD LV BACK OFF') + check.equal(imd_mcu.state, 0, 'IMD MCU BACK OFF') - hil.shutdown() + # hil.end_test() +# ---------------------------------------------------------------------------- # \ No newline at end of file diff --git a/scripts/test_charger.py b/scripts/test_charger.py index 61ea023..4b7138c 100644 --- a/scripts/test_charger.py +++ b/scripts/test_charger.py @@ -1,11 +1,18 @@ -from os import sys, path -sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) -from hil import HIL -import utils +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +from hil.hil import HIL +import hil.utils as utils import time from rules_constants import * from vehicle_constants import * +import pytest_check as check +import pytest + + +# ---------------------------------------------------------------------------- # AMS_STAT_OKAY = 1 AMS_STAT_TRIP = 0 AMS_CTRL_OKAY = 1 @@ -35,10 +42,33 @@ def cycle_power(): IMD_RC_MAX_TRIP_TIME_S = R_IMD_MAX_TRIP_TIME_S - IMD_MEASURE_TIME_S IMD_CTRL_OKAY = 1 IMD_CTRL_TRIP = 0 +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def hil(): + global power + + hil_instance = HIL() + + hil_instance.load_config("config_charger.json") + hil_instance.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + # hil_instance.init_can() + + power = hil_instance.dout("RearTester", "RLY1") + + yield hil_instance + + hil_instance.shutdown() +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # def test_imd(hil): # Begin the test - hil.start_test(test_imd.__name__) + # hil.start_test(test_imd.__name__) # Outputs imd_stat = hil.dout("Charger", "IMD_STATUS") @@ -55,34 +85,47 @@ def test_imd(hil): # IMD Fault reset_imd(imd_stat) cycle_power() - hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + # hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + check.equal(imd_ctrl.state, IMD_CTRL_OKAY, "Power On") + time.sleep(1) imd_stat.state = IMD_STAT_TRIP t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) print(f"Target trip time: [{IMD_RC_MIN_TRIP_TIME_S}, {IMD_RC_MAX_TRIP_TIME_S}]") - hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + # hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + check.between(t, IMD_RC_MIN_TRIP_TIME_S, IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Trip") + imd_stat.state = IMD_STAT_OKAY time.sleep(IMD_RC_MAX_TRIP_TIME_S * 1.1) - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Fault Stays Latched") + # IMD Fault on Power On reset_imd(imd_stat) imd_stat.state = IMD_STAT_TRIP cycle_power() time.sleep(IMD_RC_MAX_TRIP_TIME_S) - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Fault Power On") + # IMD Floating reset_imd(imd_stat) imd_stat.hiZ() cycle_power() t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) - hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + # hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + check.less(t, R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Floating Trip") - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # def test_ams(hil): # Begin the test hil.start_test(test_ams.__name__) @@ -103,45 +146,41 @@ def test_ams(hil): # AMS Fault reset_ams(ams_stat) cycle_power() - hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + # hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + check.equal(ams_ctrl.state, AMS_CTRL_OKAY, "Power On") + time.sleep(1) ams_stat.state = AMS_STAT_TRIP t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) - hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + # hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + check.between(t, 0, AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Trip") + ams_stat.state = AMS_STAT_OKAY time.sleep(AMS_MAX_TRIP_DELAY_S * 1.1) - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Fault Stays Latched") + # AMS Fault on Power On reset_ams(ams_stat) ams_stat.state = AMS_STAT_TRIP cycle_power() time.sleep(AMS_MAX_TRIP_DELAY_S) - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Fault Power On") + # AMS Floating reset_ams(ams_stat) ams_stat.hiZ() cycle_power() t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) - hil.check(0 <= t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + # hil.check(0 <= t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + check.between(t, 0, AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time", ge=True) + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Floating Trip") - hil.end_test() - - -if __name__ == "__main__": - hil = HIL() - - hil.load_config("config_charger.json") - hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") - - #hil.init_can() - - power = hil.dout("RearTester", "RLY1") - - test_imd(hil) - test_ams(hil) - - hil.shutdown() + # hil.end_test() +# ---------------------------------------------------------------------------- # \ No newline at end of file diff --git a/scripts/test_collector.py b/scripts/test_collector.py index b1ee509..3065b1c 100644 --- a/scripts/test_collector.py +++ b/scripts/test_collector.py @@ -1,12 +1,35 @@ -from os import sys, path -sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) -from hil import HIL -import utils +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +from hil.hil import HIL +import hil.utils as utils import time +import pytest_check as check +import pytest + + +# ---------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def hil(): + hil_instance = HIL() + + hil_instance.load_config("config_collector_bench.json") + hil_instance.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + # hil_instance.init_can() + + yield hil_instance + + hil_instance.shutdown() +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # def test_collector(hil): # Begin the test - hil.start_test(test_collector.__name__) + # hil.start_test(test_collector.__name__) # Outputs m1 = hil.dout("Collector", "MUX_A") @@ -29,30 +52,38 @@ def test_collector(hil): utils.log_warning(test_voltage) for thermistor in range(num_therm): - print(f"Place test input on thermistor {thermistor}. Press Enter when ready") - input("") + print(f"\nPlace test input on thermistor {thermistor}.") + + # TODO: find some way to wait for user input + # input("Press Enter when ready...") for i in range(num_therm): + # MUX (multiplexer) = choose which output to return from the thermistor based on the input + # Like a giant switch statement (0 -> return thermistor 0, 1 -> return thermistor 1, etc.) + # Encode the current thermistor into binary where each bit corresponds to each pin being high or low m1.state = i & 0x1 m2.state = i & 0x2 m3.state = i & 0x4 m4.state = i & 0x8 time.sleep(0.01) - if (i == thermistor): - hil.check_within(to.state, test_voltage, tolerance_v, f"Input on therm {thermistor}, selecting {i}") + to_state = to.state + if i == thermistor: + expected_voltage = test_voltage else: - hil.check_within(to.state, pullup_voltage, tolerance_v, f"Input on therm {thermistor}, selecting {i}") - print(to.state) + expected_voltage = pullup_voltage + within = abs(to_state - expected_voltage) < tolerance_v - # End the test - hil.end_test() + print(f"({thermistor=}, {i=}) {to_state=} ?= {expected_voltage=} -> {within=}") + check.almost_equal(to_state, expected_voltage, abs=tolerance_v, rel=0.0, msg=f"Input on therm {thermistor}, selecting {i}") -if __name__ == "__main__": - hil = HIL() - hil.load_config("config_collector_bench.json") - hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + # if i == thermistor: + # # hil.check_within(to.state, test_voltage, tolerance_v, f"Input on therm {thermistor}, selecting {i}") + # # check.almost_equal(to.state, test_voltage, abs=tolerance_v, rel=0.0, msg=f"Input on therm {thermistor}, selecting {i}") + # else: + # # hil.check_within(to.state, pullup_voltage, tolerance_v, f"Input on therm {thermistor}, selecting {i}") + # # check.almost_equal(to.state, pullup_voltage, abs=tolerance_v, rel=0.0, msg=f"Input on therm {thermistor}, selecting {i}") - test_collector(hil) - - hil.shutdown() + # End the test + # hil.end_test() +# ---------------------------------------------------------------------------- # diff --git a/scripts/test_dash.py b/scripts/test_dash.py new file mode 100644 index 0000000..57501f2 --- /dev/null +++ b/scripts/test_dash.py @@ -0,0 +1,181 @@ +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +from hil.hil import HIL +import hil.utils as utils +import time +from rules_constants import * +from vehicle_constants import * + +import pytest_check as check +import pytest + + +# ---------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def hil(): + hil_instance = HIL() + + hil_instance.load_config("config_system_hil_attached.json") + hil_instance.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + hil_instance.init_can() + + yield hil_instance + + hil_instance.shutdown() +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # +BRK_SWEEP_DELAY = 0.1 + +def test_bspd(hil): + # Begin the test + # hil.start_test(test_bspd.__name__) + + # Outputs + brk1 = hil.aout("Dashboard", "BRK1_RAW") + brk2 = hil.aout("Dashboard", "BRK2_RAW") + + # Inputs + brk_fail_tap = hil.mcu_pin("Dashboard", "BRK_FAIL_TAP") + brk_stat_tap = hil.mcu_pin("Dashboard", "BRK_STAT_TAP") + + # Brake threshold check + brk1.state = BRK_1_REST_V + brk2.state = BRK_2_REST_V + # hil.check(brk_stat_tap.state == 0, "Brake stat starts low") + check.equal(brk_stat_tap.state, 0, "Brake stat starts low") + + brk1.state = BRK_1_THRESH_V + time.sleep(0.1) + # hil.check(brk_stat_tap.state == 1, "Brake stat goes high at brk 1 thresh") + check.equal(brk_stat_tap.state, 1, "Brake stat goes high at brk 1 thresh") + + brk1.state = BRK_1_REST_V + # hil.check(brk_stat_tap.state == 0, "Brake stat starts low") + check.equal(brk_stat_tap.state, 0, "Brake stat starts low") + + brk2.state = BRK_2_THRESH_V + time.sleep(0.1) + # hil.check(brk_stat_tap.state == 1, "Brake stat goes high at brk 2 thresh") + check.equal(brk_stat_tap.state, 1, "Brake stat goes high at brk 2 thresh") + + brk1.state = BRK_1_THRESH_V + # hil.check(brk_stat_tap.state == 1, "Brake stat stays high for both brakes") + check.equal(brk_stat_tap.state, 1, "Brake stat stays high for both brakes") + + + # Brake threshold scan + brk1.state = BRK_MIN_OUT_V + brk2.state = BRK_MIN_OUT_V + time.sleep(0.1) + # hil.check(brk_stat_tap.state == 0, "Brake Stat Starts Low Brk 1") + check.equal(brk_stat_tap.state, 0, "Brake Stat Starts Low Brk 1") + + start = BRK_MIN_OUT_V + stop = BRK_MAX_OUT_V + step = 0.1 + + thresh = utils.measure_trip_thresh(brk1, start, stop, step, + BRK_SWEEP_DELAY, + brk_stat_tap, is_falling=False) + print(f"Brake 1 braking threshold: {thresh}") + # hil.check_within(thresh, BRK_1_THRESH_V, 0.2, "Brake 1 trip voltage") + # hil.check(brk_stat_tap.state == 1, "Brake Stat Tripped for Brk 1") + check.almost_equal( + thresh, BRK_1_THRESH_V, + abs=0.2, rel=0.0, + msg="Brake 1 trip voltage" + ) + check.equal(brk_stat_tap.state, 1, "Brake Stat Tripped for Brk 1") + + brk1.state = BRK_MIN_OUT_V + brk2.state = BRK_MIN_OUT_V + hil.check(brk_stat_tap.state == 0, "Brake Stat Starts Low Brk 2") + thresh = utils.measure_trip_thresh(brk2, start, stop, step, + BRK_SWEEP_DELAY, + brk_stat_tap, is_falling=False) + print(f"Brake 2 braking threshold: {thresh}") + # hil.check_within(thresh, BRK_2_THRESH_V, 0.2, "Brake 2 trip voltage") + # hil.check(brk_stat_tap.state == 1, "Brake Stat Tripped for Brk 2") + check.almost_equal( + thresh, BRK_2_THRESH_V, + abs=0.2, rel=0.0, + msg="Brake 2 trip voltage" + ) + check.equal(brk_stat_tap.state, 1, "Brake Stat Tripped for Brk 2") + + # Brake Fail scan + brk1.state = BRK_1_REST_V + brk2.state = BRK_2_REST_V + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 1 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 1 Starts 0") + + brk1.state = 0.0 # Force 0 + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Short GND") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 1 Short GND") + + brk1.state = BRK_1_REST_V + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 2 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 2 Starts 0") + + brk2.state = 0.0 # Force 0 + time.sleep(0.1) + hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Short GND") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 2 Short GND") + + brk2.state = BRK_2_REST_V + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 3 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 3 Starts 0") + + brk1.state = 5.0 # Short VCC + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Short VCC") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 1 Short VCC") + + brk1.state = BRK_1_REST_V + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 4 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 4 Starts 0") + + brk2.state = 5.0 # Short VCC + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Short VCC") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 2 Short VCC") + + brk2.state = BRK_2_REST_V + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 5 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 5 Starts 0") + + brk1.hiZ() + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Hi-Z") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 1 Hi-Z") + + brk1.state = BRK_1_REST_V + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 6 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 6 Starts 0") + + brk2.hiZ() + time.sleep(0.1) + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Hi-Z") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 2 Hi-Z") + + brk2.state = BRK_2_REST_V + + # End the test + # hil.end_test() +# ---------------------------------------------------------------------------- # + + +# TODO: add throttle checks + diff --git a/scripts/test_main_base.py b/scripts/test_main_base.py index 133eb27..3a50363 100644 --- a/scripts/test_main_base.py +++ b/scripts/test_main_base.py @@ -1,11 +1,18 @@ -from os import sys, path -sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) -from hil import HIL -import utils +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +from hil.hil import HIL +import hil.utils as utils import time from rules_constants import * from vehicle_constants import * +import pytest_check as check +import pytest + + +# ---------------------------------------------------------------------------- # AMS_STAT_OKAY = 1 AMS_STAT_TRIP = 0 AMS_CTRL_OKAY = 1 @@ -45,10 +52,33 @@ def cycle_power(): time.sleep(0.75) power.state = 0 time.sleep(CYCLE_POWER_ON_DELAY) +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def hil(): + global power + + hil_instance = HIL() + + hil_instance.load_config("config_main_base_bench.json") + hil_instance.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + hil_instance.init_can() + + power = hil_instance.dout("Arduino2", "RLY1") + + yield hil_instance + + hil_instance.shutdown() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # def test_precharge(hil): # Begin the test - hil.start_test(test_precharge.__name__) + # hil.start_test(test_precharge.__name__) # Outputs v_bat = hil.aout("Main_Module", "VBatt") @@ -64,14 +94,19 @@ def test_precharge(hil): # Initial State reset_pchg(v_bat, v_mc) time.sleep(2.5) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on startup") - hil.check(not_pchg_cmplt_delayed.state == 1, "Not precharge complete delayed high on startup") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on startup") + # hil.check(not_pchg_cmplt_delayed.state == 1, "Not precharge complete delayed high on startup") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on startup") + check.equal(not_pchg_cmplt_delayed.state, 1, "Not precharge complete delayed high on startup") # Check delay v_mc.state = tiff_hv_to_lv(ACCUM_NOM_V) t = utils.measure_trip_time(not_pchg_cmplt_delayed, PCHG_COMPLETE_DELAY_S*3, is_falling=True) - hil.check(not_pchg_cmplt_delayed.state == 0, "Precharge complete delayed") - hil.check_within(t, PCHG_COMPLETE_DELAY_S, 0.25, f"Precharge complete delay of {t:.3}s close to expected {PCHG_COMPLETE_DELAY_S}s") + # hil.check(not_pchg_cmplt_delayed.state == 0, "Precharge complete delayed") + # hil.check_within(t, PCHG_COMPLETE_DELAY_S, 0.25, f"Precharge complete delay of {t:.3}s close to expected {PCHG_COMPLETE_DELAY_S}s") + check.equal(not_pchg_cmplt_delayed.state, 0, "Precharge complete delayed") + check.almost_equal(t, PCHG_COMPLETE_DELAY_S, abs=0.25, rel=0.0, msg=f"Precharge complete delay of {t:.3}s close to expected {PCHG_COMPLETE_DELAY_S}s") + # Find threshold at nominal pack voltage for v in [ACCUM_MIN_V, ACCUM_NOM_V, ACCUM_MAX_V]: @@ -80,7 +115,8 @@ def test_precharge(hil): v_bat.state = tiff_hv_to_lv(v) v_mc.state = tiff_hv_to_lv(v*0.8) time.sleep(0.01) - hil.check(pchg_cmplt.state == 0, "Precharge Complete Low at Initial State") + # hil.check(pchg_cmplt.state == 0, "Precharge Complete Low at Initial State") + check.equal(pchg_cmplt.state, 0, "Precharge Complete Low at Initial State") start = tiff_hv_to_lv(v*0.8) stop = tiff_hv_to_lv(v) @@ -89,38 +125,49 @@ def test_precharge(hil): pchg_cmplt, is_falling=0) thresh_hv = tiff_lv_to_hv(thresh) print(f"Precharge triggered at {thresh_hv / v * 100:.4}% ({thresh_hv:.5}V) of vbat={v}.") - hil.check_within(thresh_hv / v, R_PCHG_V_BAT_THRESH, 0.03, f"Precharge threshold of {R_PCHG_V_BAT_THRESH*100}% at vbat = {v}V") + # hil.check_within(thresh_hv / v, R_PCHG_V_BAT_THRESH, 0.03, f"Precharge threshold of {R_PCHG_V_BAT_THRESH*100}% at vbat = {v}V") + check.almost_equal(thresh_hv / v, R_PCHG_V_BAT_THRESH, abs=0.03, rel=0.0, msg=f"Precharge threshold of {R_PCHG_V_BAT_THRESH*100}% at vbat = {v}V") + v_mc.state = tiff_hv_to_lv(v) time.sleep(0.25) - hil.check(pchg_cmplt.state == 1, f"Precharge completed at vbat = {v}V") + # hil.check(pchg_cmplt.state == 1, f"Precharge completed at vbat = {v}V") + check.equal(pchg_cmplt.state, 1, f"Precharge completed at vbat = {v}V") # Floating conditions (check never precharge complete) reset_pchg(v_bat, v_mc) v_bat.hiZ() v_mc.state = tiff_hv_to_lv(0) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc 0V") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc 0V") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat float, v_mc 0V") + v_mc.state = tiff_hv_to_lv(ACCUM_MAX_V) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc max V") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc max V") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat float, v_mc max V") reset_pchg(v_bat, v_mc) v_mc.hiZ() v_bat.state = tiff_hv_to_lv(ACCUM_MIN_V) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat min, v_mc float") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat min, v_mc float") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat min, v_mc float") v_bat.state = tiff_hv_to_lv(ACCUM_MAX_V) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat max, v_mc float") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat max, v_mc float") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat max, v_mc float") reset_pchg(v_bat, v_mc) v_bat.hiZ() v_mc.hiZ() - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc float") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc float") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat float, v_mc float") # TODO: software precharge validity checks (make precharge take forever) - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # def test_bspd(hil): # Begin the test - hil.start_test(test_bspd.__name__) + # hil.start_test(test_bspd.__name__) # Outputs brk_fail = hil.dout("Main_Module", "Brake Fail") @@ -141,13 +188,16 @@ def test_bspd(hil): # Brake Fail reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk_fail.state = 1 t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + check.less(t, R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") brk_fail.state = 0 time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + # hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + check.equal(bspd_ctrl.state, 0, "Brake Fail Stays Latched") # Brake Fail on Power On reset_bspd(brk_fail, brk_stat, c_sense) @@ -158,61 +208,74 @@ def test_bspd(hil): brk_fail.state = 1 power.state = 0 time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + # hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + check.equal(bspd_ctrl.state, 0, "Power On Brake Fail") # Current no brake reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") time.sleep(2) # TODO: I am not sure why this fails, but oh well set_bspd_current(c_sense, 75) time.sleep(R_BSPD_MAX_TRIP_TIME_S) # time.sleep(100) - hil.check(bspd_ctrl.state == 1, "Current no brake") + # hil.check(bspd_ctrl.state == 1, "Current no brake") + check.equal(bspd_ctrl.state, 1, "Current no brake") # Current Sense Short to Ground reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") c_sense.state = 0.0 t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + check.less(t, R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") set_bspd_current(c_sense, 0.0) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + # hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + check.equal(bspd_ctrl.state, 0, "Current short to ground stays latched") # Current Sense Short to 5V reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") c_sense.state = ABOX_DHAB_CH1_DIV.div(5.0) t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + check.less(t, R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") set_bspd_current(c_sense, 0.0) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + # hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + check.equal(bspd_ctrl.state, 0, "Current short to 5V stays latched") # Braking reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk_stat.state = 1 time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Brake no current") + # hil.check(bspd_ctrl.state == 1, "Brake no current") + check.equal(bspd_ctrl.state, 1, "Brake no current") # Lowest current required to trip at min_trip_current = R_BSPD_POWER_THRESH_W / ACCUM_MAX_V set_bspd_current(c_sense, min_trip_current) t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + check.less(t, R_BSPD_MAX_TRIP_TIME_S, "Braking with current") # Measure braking with current threshold reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() brk_stat.state = 1 - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") start = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(0.0)) stop = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(DHAB_S124_CH1_MAX_A)) step = 0.1 @@ -221,15 +284,22 @@ def test_bspd(hil): bspd_ctrl, is_falling=True) thresh_amps = dhab_ch1_v_to_a(ABOX_DHAB_CH1_DIV.reverse(thresh)) print(f"Current while braking threshold: {thresh}V = {thresh_amps}A") - hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + # hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + check.almost_equal( + thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), + abs=0.1, rel=0.0, + msg="Current while braking threshold" + ) # Determine the current sense short to gnd threshold reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") set_bspd_current(c_sense, DHAB_S124_CH1_MIN_A) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Min output current okay") + # hil.check(bspd_ctrl.state == 1, "Min output current okay") + check.equal(bspd_ctrl.state, 1, "Min output current okay") start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MIN_OUT_V) stop = 0.0 step = -0.1 @@ -237,17 +307,20 @@ def test_bspd(hil): R_BSPD_MAX_TRIP_TIME_S, bspd_ctrl, is_falling=True) print(f"Short to ground threshold: {thresh}V") - hil.check(stop < (thresh) < start, "Current short to ground threshold") + # hil.check(stop < (thresh) < start, "Current short to ground threshold") + check.between(thresh, stop, start, "Current short to ground threshold") # Determine the current sense short to 5V threshold reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") time.sleep(2) set_bspd_current(c_sense, DHAB_S124_CH1_MAX_A) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Max output current okay") + # hil.check(bspd_ctrl.state == 1, "Max output current okay") + check.equal(bspd_ctrl.state, 1, "Max output current okay") start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MAX_OUT_V) stop = ABOX_DHAB_CH1_DIV.div(5.0) step = 0.01 @@ -256,40 +329,49 @@ def test_bspd(hil): R_BSPD_MAX_TRIP_TIME_S, bspd_ctrl, is_falling=True) print(f"Short to 5V threshold: {thresh}V") - hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + # hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + check.equal(bspd_ctrl.state, 0, "Short to 5V trips") print(stop) print(start) - hil.check(start < (thresh) <= stop, "Current short to 5V threshold") + # hil.check(start < (thresh) <= stop, "Current short to 5V threshold") + check.between(thresh, stop, start, "Current short to 5V threshold", le=True) # Floating current reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") c_sense.hiZ() time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Floating current") + # hil.check(bspd_ctrl.state == 0, "Floating current") + check.equal(bspd_ctrl.state, 0, "Floating current") # Floating brake_fail reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk_fail.hiZ() time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Floating brake fail") + # hil.check(bspd_ctrl.state == 0, "Floating brake fail") + check.equal(bspd_ctrl.state, 0, "Floating brake fail") # Floating brake_stat reset_bspd(brk_fail, brk_stat, c_sense) cycle_power() - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk_stat.hiZ() set_bspd_current(c_sense, min_trip_current) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Floating brake status") + # hil.check(bspd_ctrl.state == 0, "Floating brake status") + check.equal(bspd_ctrl.state, 0, "Floating brake status") # End the test - hil.end_test() - + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # IMD_RC_MIN_TRIP_TIME_S = IMD_STARTUP_TIME_S IMD_RC_MAX_TRIP_TIME_S = R_IMD_MAX_TRIP_TIME_S - IMD_MEASURE_TIME_S IMD_CTRL_OKAY = 1 @@ -297,13 +379,13 @@ def test_bspd(hil): def test_imd(hil): # Begin the test - hil.start_test(test_imd.__name__) + # hil.start_test(test_imd.__name__) # Outputs imd_stat = hil.dout("Main_Module", "IMD_Status") # Outputs to set SDC status to okay - ams_stat = hil.dout("Main_Module", "BMS-Status-Main") + ams_stat = hil.dout("Main_Module", "BMS-Status-Main") brk_fail = hil.dout("Main_Module", "Brake Fail") brk_stat = hil.dout("Main_Module", "Brake Status") c_sense = hil.aout("Main_Module", "Current Sense C1") @@ -318,37 +400,46 @@ def test_imd(hil): # IMD Fault reset_imd(imd_stat) cycle_power() - hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + # hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + check.equal(imd_ctrl.state, IMD_CTRL_OKAY, "Power On") time.sleep(1) imd_stat.state = IMD_STAT_TRIP t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) print(f"Target trip time: [{IMD_RC_MIN_TRIP_TIME_S}, {IMD_RC_MAX_TRIP_TIME_S}]") - hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + # hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + check.between(t, IMD_RC_MIN_TRIP_TIME_S, IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Trip") imd_stat.state = IMD_STAT_OKAY time.sleep(IMD_RC_MAX_TRIP_TIME_S * 1.1) - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Fault Stays Latched") # IMD Fault on Power On reset_imd(imd_stat) imd_stat.state = IMD_STAT_TRIP cycle_power() time.sleep(IMD_RC_MAX_TRIP_TIME_S) - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Fault Power On") # IMD Floating reset_imd(imd_stat) imd_stat.hiZ() cycle_power() t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) - hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + # hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + check.between(t, 0, R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Floating Trip") - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # def test_ams(hil): # Begin the test - hil.start_test(test_ams.__name__) + # hil.start_test(test_ams.__name__) # Outputs ams_stat = hil.dout("Main_Module", "BMS-Status-Main") @@ -369,37 +460,46 @@ def test_ams(hil): # AMS Fault reset_ams(ams_stat) cycle_power() - hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + # hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + check.equal(ams_ctrl.state, AMS_CTRL_OKAY, "Power On") time.sleep(1) ams_stat.state = AMS_STAT_TRIP t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) - hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + # hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + check.between(t, 0, AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Trip") ams_stat.state = AMS_STAT_OKAY time.sleep(AMS_MAX_TRIP_DELAY_S * 1.1) - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Fault Stays Latched") # AMS Fault on Power On reset_ams(ams_stat) ams_stat.state = AMS_STAT_TRIP cycle_power() time.sleep(AMS_MAX_TRIP_DELAY_S) - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Fault Power On") # AMS Floating reset_ams(ams_stat) ams_stat.hiZ() cycle_power() t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) - hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + # hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + check.between(t, 0, AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Floating Trip") - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # def test_tsal(hil): # Begin the test - hil.start_test(test_tsal.__name__) + # hil.start_test(test_tsal.__name__) # Outputs v_mc = hil.aout("Main_Module", "Voltage MC Transducer") @@ -413,21 +513,27 @@ def test_tsal(hil): time.sleep(0.2) # No need to power cycle - hil.check(lval.state == 1, "LVAL on at v_mc = 0") - hil.check(tsal.state == 0, "TSAL off at v_mc = 0") + # hil.check(lval.state == 1, "LVAL on at v_mc = 0") + # hil.check(tsal.state == 0, "TSAL off at v_mc = 0") + check.equal(lval.state, 1, "LVAL on at v_mc = 0") + check.equal(tsal.state, 0, "TSAL off at v_mc = 0") time.sleep(5) - hil.check(lval.state == 1, "LVAL stays on") + # hil.check(lval.state == 1, "LVAL stays on") + check.equal(lval.state, 1, "LVAL stays on") v_mc.state = tiff_hv_to_lv(ACCUM_MIN_V) time.sleep(0.1) - hil.check(lval.state == 0, f"LVAL off at {ACCUM_MIN_V:.4} V") - hil.check(tsal.state == 1, f"TSAL on at {ACCUM_MIN_V:.4} V") + # hil.check(lval.state == 0, f"LVAL off at {ACCUM_MIN_V:.4} V") + # hil.check(tsal.state == 1, f"TSAL on at {ACCUM_MIN_V:.4} V") + check.equal(lval.state, 0, f"LVAL off at {ACCUM_MIN_V:.4} V") + check.equal(tsal.state, 1, f"TSAL on at {ACCUM_MIN_V:.4} V") reset_tsal(v_mc) time.sleep(0.2) - hil.check(lval.state == 1, f"LVAL turns back on") + # hil.check(lval.state == 1, f"LVAL turns back on") + check.equal(lval.state, 1, f"LVAL turns back on") start = tiff_hv_to_lv(0.0) stop = tiff_hv_to_lv(R_TSAL_HV_V * 1.5) @@ -437,27 +543,12 @@ def test_tsal(hil): tsal, is_falling=False) thresh = tiff_lv_to_hv(thresh) print(f"TSAL on at {thresh:.4} V") - hil.check_within(thresh, R_TSAL_HV_V, 4, f"TSAL trips at {R_TSAL_HV_V:.4} +-4") - hil.check(lval.state == 0, f"LVAL off V") - hil.check(tsal.state == 1, f"TSAL on V") - - hil.end_test() - - -if __name__ == "__main__": - hil = HIL() - - hil.load_config("config_main_base_bench.json") - hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") - - hil.init_can() - - power = hil.dout("Arduino2", "RLY1") - - test_precharge(hil) - test_bspd(hil) - test_imd(hil) - test_ams(hil) - test_tsal(hil) - - hil.shutdown() + # hil.check_within(thresh, R_TSAL_HV_V, 4, f"TSAL trips at {R_TSAL_HV_V:.4} +-4") + # hil.check(lval.state == 0, f"LVAL off V") + # hil.check(tsal.state == 1, f"TSAL on V") + check.almost_equal(thresh, R_TSAL_HV_V, abs=4, rel=0.0, msg=f"TSAL trips at {R_TSAL_HV_V:.4} +-4") + check.equal(lval.state, 0, f"LVAL off V") + check.equal(tsal.state, 1, f"TSAL on V") + + # hil.end_test() +# ---------------------------------------------------------------------------- # \ No newline at end of file diff --git a/scripts/test_main_sdc.py b/scripts/test_main_sdc.py index c4ad102..c084436 100644 --- a/scripts/test_main_sdc.py +++ b/scripts/test_main_sdc.py @@ -1,11 +1,34 @@ -from os import sys, path -sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) -from hil import HIL -import utils +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +from hil.hil import HIL +import hil.utils as utils import time from rules_constants import * from vehicle_constants import * +import pytest_check as check +import pytest + + +# ---------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def hil(): + hil_instance = HIL() + + hil_instance.load_config("config_main_sdc_bench.json") + hil_instance.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + # hil_instance.init_can() + + yield hil_instance + + hil_instance.shutdown() +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # # SETUP for CSENSE # Current Sensor (DHAB) -> ABOX V Divider -> MAIN_SDC @@ -25,9 +48,11 @@ def reset_bspd(fail, stat, ch1): stat.state = 0 set_bspd_current(ch1, 0.0) + +# ---------------------------------------------------------------------------- # def test_bspd(hil): # Begin the test - hil.start_test(test_bspd.__name__) + # hil.start_test(test_bspd.__name__) # Outputs brk_fail = hil.dout("MainSDC", "Brake Fail") @@ -41,74 +66,90 @@ def test_bspd(hil): # Brake Fail reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk_fail.state = 1 t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + check.below(t, R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") brk_fail.state = 0 time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + # hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + check.equal(bspd_ctrl.state, 0, "Brake Fail Stays Latched") # Brake Fail on Power On reset_bspd(brk_fail, brk_stat, c_sense) brk_fail.state = 1 cycle_power(pow) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + # hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + check.equal(bspd_ctrl.state, 0, "Power On Brake Fail") # Current no brake reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") time.sleep(2) # TODO: I am not sure why this fails, but oh well set_bspd_current(c_sense, 75) time.sleep(R_BSPD_MAX_TRIP_TIME_S) # time.sleep(100) - hil.check(bspd_ctrl.state == 1, "Current no brake") + # hil.check(bspd_ctrl.state == 1, "Current no brake") + check.equal(bspd_ctrl.state, 1, "Current no brake") # Current Sense Short to Ground reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") c_sense.state = 0.0 t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + check.below(t, R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") set_bspd_current(c_sense, 0.0) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + # hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + check.equal(bspd_ctrl.state, 0, "Current short to ground stays latched") # Current Sense Short to 5V reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") c_sense.state = ABOX_DHAB_CH1_DIV.div(5.0) / DAC_GAIN t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + check.below(t, R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") set_bspd_current(c_sense, 0.0) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + # hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + check.equal(bspd_ctrl.state, 0, "Current short to 5V stays latched") # Braking reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk_stat.state = 1 time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Brake no current") + # hil.check(bspd_ctrl.state == 1, "Brake no current") + check.equal(bspd_ctrl.state, 1, "Brake no current") # Lowest current required to trip at min_trip_current = R_BSPD_POWER_THRESH_W / ACCUM_MAX_V set_bspd_current(c_sense, min_trip_current) t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + check.below(t, R_BSPD_MAX_TRIP_TIME_S, "Braking with current") # Measure braking with current threshold reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) brk_stat.state = 1 - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") start = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(0.0)) / DAC_GAIN stop = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(DHAB_S124_CH1_MAX_A)) / DAC_GAIN step = 0.1 / DAC_GAIN @@ -118,15 +159,18 @@ def test_bspd(hil): thresh *= DAC_GAIN thresh_amps = dhab_ch1_v_to_a(ABOX_DHAB_CH1_DIV.reverse(thresh)) print(f"Current while braking threshold: {thresh}V = {thresh_amps}A") - hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + # hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + check.within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") # Determine the current sense short to gnd threshold reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") set_bspd_current(c_sense, DHAB_S124_CH1_MIN_A) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Min output current okay") + # hil.check(bspd_ctrl.state == 1, "Min output current okay") + check.equal(bspd_ctrl.state, 1, "Min output current okay") start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MIN_OUT_V) / DAC_GAIN stop = 0.0 step = -0.1 / DAC_GAIN @@ -135,17 +179,20 @@ def test_bspd(hil): bspd_ctrl, is_falling=True) thresh *= DAC_GAIN print(f"Short to ground threshold: {thresh}V") - hil.check(stop < (thresh / DAC_GAIN) < start, "Current short to ground threshold") + # hil.check(stop < (thresh / DAC_GAIN) < start, "Current short to ground threshold") + check.between(thresh / DAC_GAIN, stop, start, "Current short to ground threshold") # Determine the current sense short to 5V threshold reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") time.sleep(2) set_bspd_current(c_sense, DHAB_S124_CH1_MAX_A) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Max output current okay") + # hil.check(bspd_ctrl.state == 1, "Max output current okay") + check.equal(bspd_ctrl.state, 1, "Max output current okay") start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MAX_OUT_V) / DAC_GAIN stop = ABOX_DHAB_CH1_DIV.div(5.0) / DAC_GAIN step = 0.01 / DAC_GAIN @@ -155,40 +202,50 @@ def test_bspd(hil): bspd_ctrl, is_falling=True) thresh *= DAC_GAIN print(f"Short to 5V threshold: {thresh}V") - hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + # hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + check.equal(bspd_ctrl.state, 0, "Short to 5V trips") print(stop * DAC_GAIN) print(start * DAC_GAIN) - hil.check(start < (thresh / DAC_GAIN) <= stop, "Current short to 5V threshold") + # hil.check(start < (thresh / DAC_GAIN) <= stop, "Current short to 5V threshold") + check.between(thresh / DAC_GAIN, stop, start, "Current short to 5V threshold") # Floating current reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") c_sense.hiZ() time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Floating current") + # hil.check(bspd_ctrl.state == 0, "Floating current") + check.equal(bspd_ctrl.state, 0, "Floating current") # Floating brake_fail reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk_fail.hiZ() time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Floating brake fail") + # hil.check(bspd_ctrl.state == 0, "Floating brake fail") + check.equal(bspd_ctrl.state, 0, "Floating brake fail") # Floating brake_stat reset_bspd(brk_fail, brk_stat, c_sense) cycle_power(pow) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk_stat.hiZ() set_bspd_current(c_sense, min_trip_current) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Floating brake status") + # hil.check(bspd_ctrl.state == 0, "Floating brake status") + check.equal(bspd_ctrl.state, 0, "Floating brake status") # End the test - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # IMD_RC_MIN_TRIP_TIME_S = IMD_STARTUP_TIME_S IMD_RC_MAX_TRIP_TIME_S = R_IMD_MAX_TRIP_TIME_S - IMD_MEASURE_TIME_S IMD_STAT_OKAY = 1 @@ -199,9 +256,10 @@ def test_bspd(hil): def reset_imd(imd_stat): imd_stat.state = IMD_STAT_OKAY + def test_imd(hil): # Begin the test - hil.start_test(test_imd.__name__) + # hil.start_test(test_imd.__name__) # Outputs imd_stat = hil.dout("MainSDC", "IMD_Status") @@ -213,33 +271,43 @@ def test_imd(hil): # IMD Fault reset_imd(imd_stat) cycle_power(pow) - hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + # hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + check.equal(imd_ctrl.state, IMD_CTRL_OKAY, "Power On") time.sleep(1) imd_stat.state = IMD_STAT_TRIP t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) - hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + # hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + check.between(t, IMD_RC_MIN_TRIP_TIME_S, IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Trip") imd_stat.state = IMD_STAT_OKAY time.sleep(IMD_RC_MAX_TRIP_TIME_S * 1.1) - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Fault Stays Latched") # IMD Fault on Power On reset_imd(imd_stat) imd_stat.state = IMD_STAT_TRIP cycle_power(pow) time.sleep(IMD_RC_MAX_TRIP_TIME_S) - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Fault Power On") # IMD Floating reset_imd(imd_stat) imd_stat.hiZ() cycle_power(pow) t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) - hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + # hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + check.below(t, R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Floating Trip") - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # AMS_STAT_OKAY = 1 AMS_STAT_TRIP = 0 AMS_CTRL_OKAY = 1 @@ -248,9 +316,10 @@ def test_imd(hil): def reset_ams(ams_stat): ams_stat.state = AMS_STAT_OKAY + def test_ams(hil): # Begin the test - hil.start_test(test_ams.__name__) + # hil.start_test(test_ams.__name__) # Outputs ams_stat = hil.dout("MainSDC", "BMS-Status-Main") @@ -262,41 +331,37 @@ def test_ams(hil): # AMS Fault reset_ams(ams_stat) cycle_power(pow) - hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + # hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + check.equal(ams_ctrl.state, AMS_CTRL_OKAY, "Power On") time.sleep(1) ams_stat.state = AMS_STAT_TRIP t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) - hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + # hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + check.between(t, 0, AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Trip") ams_stat.state = AMS_STAT_OKAY time.sleep(AMS_MAX_TRIP_DELAY_S * 1.1) - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Fault Stays Latched") # AMS Fault on Power On reset_ams(ams_stat) ams_stat.state = AMS_STAT_TRIP cycle_power(pow) time.sleep(AMS_MAX_TRIP_DELAY_S) - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Fault Power On") # AMS Floating reset_ams(ams_stat) ams_stat.hiZ() cycle_power(pow) t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) - hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + # hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + check.between(t, 0, AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time", ge=True) + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Floating Trip") - hil.end_test() - - -if __name__ == "__main__": - hil = HIL() - hil.load_config("config_main_sdc_bench.json") - hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") - - test_bspd(hil) - test_imd(hil) - test_ams(hil) - - hil.shutdown() + # hil.end_test() +# ---------------------------------------------------------------------------- # \ No newline at end of file diff --git a/scripts/test_system.py b/scripts/test_system.py index 30ffc5a..62202b2 100644 --- a/scripts/test_system.py +++ b/scripts/test_system.py @@ -1,11 +1,18 @@ -from os import sys, path -sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) -from hil import HIL -import utils +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +from hil.hil import HIL +import hil.utils as utils import time from rules_constants import * from vehicle_constants import * +import pytest_check as check +import pytest + + +# ---------------------------------------------------------------------------- # AMS_STAT_OKAY = 1 AMS_STAT_TRIP = 0 AMS_CTRL_OKAY = 1 @@ -48,10 +55,33 @@ def cycle_power(): time.sleep(CYCLE_POWER_OFF_DELAY) power.state = 0 time.sleep(CYCLE_POWER_ON_DELAY) +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def hil(): + global power + + hil_instance = HIL() + + hil_instance.load_config("config_charger.json") + hil_instance.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + + # hil_instance.init_can() + + power = hil_instance.dout("RearTester", "RLY1") + + yield hil_instance + + hil_instance.shutdown() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # def test_precharge(hil): # Begin the test - hil.start_test(test_precharge.__name__) + # hil.start_test(test_precharge.__name__) # Outputs v_bat = hil.aout("Main_Module", "VBatt") @@ -79,13 +109,17 @@ def test_precharge(hil): # Initial State reset_pchg(v_bat, v_mc) time.sleep(2.5) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on startup") - hil.check(not_pchg_cmplt_delayed.state == 1, "Not precharge complete delayed high on startup") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on startup") + # hil.check(not_pchg_cmplt_delayed.state == 1, "Not precharge complete delayed high on startup") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on startup") + check.equal(not_pchg_cmplt_delayed.state, 1, "Not precharge complete delayed high on startup") # Check delay v_mc.state = tiff_hv_to_lv(ACCUM_NOM_V) t = utils.measure_trip_time(not_pchg_cmplt_delayed, PCHG_COMPLETE_DELAY_S*3, is_falling=True) - hil.check(not_pchg_cmplt_delayed.state == 0, "Precharge complete delayed") - hil.check_within(t, PCHG_COMPLETE_DELAY_S, 0.25, f"Precharge complete delay of {t:.3}s close to expected {PCHG_COMPLETE_DELAY_S}s") + # hil.check(not_pchg_cmplt_delayed.state == 0, "Precharge complete delayed") + # hil.check_within(t, PCHG_COMPLETE_DELAY_S, 0.25, f"Precharge complete delay of {t:.3}s close to expected {PCHG_COMPLETE_DELAY_S}s") + check.equal(not_pchg_cmplt_delayed.state, 0, "Precharge complete delayed") + check.almost_equal(t, PCHG_COMPLETE_DELAY_S, abs=0.25, rel=0.0, msg=f"Precharge complete delay of {t:.3}s close to expected {PCHG_COMPLETE_DELAY_S}s") # Find threshold at nominal pack voltage for v in [ACCUM_MIN_V, ACCUM_NOM_V, ACCUM_MAX_V]: @@ -95,7 +129,8 @@ def test_precharge(hil): v_mc.state = tiff_hv_to_lv(v*0.8) #time.sleep(0.01) time.sleep(0.5) - hil.check(pchg_cmplt.state == 0, "Precharge Complete Low at Initial State") + # hil.check(pchg_cmplt.state == 0, "Precharge Complete Low at Initial State") + check.equal(pchg_cmplt.state, 0, "Precharge Complete Low at Initial State") start = tiff_hv_to_lv(v*0.8) stop = tiff_hv_to_lv(v) @@ -104,41 +139,52 @@ def test_precharge(hil): pchg_cmplt, is_falling=0) thresh_hv = tiff_lv_to_hv(thresh) print(f"Precharge triggered at {thresh_hv / v * 100:.4}% ({thresh_hv:.5}V) of vbat={v}.") - hil.check_within(thresh_hv / v, R_PCHG_V_BAT_THRESH, 0.03, f"Precharge threshold of {R_PCHG_V_BAT_THRESH*100}% at vbat = {v}V") + # hil.check_within(thresh_hv / v, R_PCHG_V_BAT_THRESH, 0.03, f"Precharge threshold of {R_PCHG_V_BAT_THRESH*100}% at vbat = {v}V") + check.almost_equal(thresh_hv / v, R_PCHG_V_BAT_THRESH, abs=0.03, rel=0.0, msg=f"Precharge threshold of {R_PCHG_V_BAT_THRESH*100}% at vbat = {v}V") v_mc.state = tiff_hv_to_lv(v) #time.sleep(0.25) time.sleep(8) - hil.check(pchg_cmplt.state == 1, f"Precharge completed at vbat = {v}V") + # hil.check(pchg_cmplt.state == 1, f"Precharge completed at vbat = {v}V") + check.equal(pchg_cmplt.state, 1, f"Precharge completed at vbat = {v}V") # Floating conditions (check never precharge complete) reset_pchg(v_bat, v_mc) v_bat.hiZ() v_mc.state = tiff_hv_to_lv(0) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc 0V") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc 0V") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat float, v_mc 0V") v_mc.state = tiff_hv_to_lv(ACCUM_MAX_V) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc max V") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc max V") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat float, v_mc max V") reset_pchg(v_bat, v_mc) v_mc.hiZ() v_bat.state = tiff_hv_to_lv(ACCUM_MIN_V) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat min, v_mc float") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat min, v_mc float") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat min, v_mc float") v_bat.state = tiff_hv_to_lv(ACCUM_MAX_V) - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat max, v_mc float") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat max, v_mc float") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat max, v_mc float") reset_pchg(v_bat, v_mc) v_bat.hiZ() v_mc.hiZ() - hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc float") + # hil.check(pchg_cmplt.state == 0, "Precharge not complete on v_bat float, v_mc float") + check.equal(pchg_cmplt.state, 0, "Precharge not complete on v_bat float, v_mc float") # TODO: software precharge validity checks (make precharge take forever) - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # BRK_SWEEP_DELAY = 0.1 BSPD_DASH_ON_TIME = 0 + def test_bspd(hil): # Begin the test - hil.start_test(test_bspd.__name__) + # hil.start_test(test_bspd.__name__) # Outputs brk1 = hil.aout("Dashboard", "BRK1_RAW") @@ -163,23 +209,29 @@ def test_bspd(hil): # Brake threshold check brk1.state = BRK_1_REST_V brk2.state = BRK_2_REST_V - hil.check(brk_stat_tap.state == 0, "Brake stat starts low") + # hil.check(brk_stat_tap.state == 0, "Brake stat starts low") + check.equal(brk_stat_tap.state, 0, "Brake stat starts low") brk1.state = BRK_1_THRESH_V time.sleep(0.1) - hil.check(brk_stat_tap.state == 1, "Brake stat goes high at brk 1 thresh") + # hil.check(brk_stat_tap.state == 1, "Brake stat goes high at brk 1 thresh") + check.equal(brk_stat_tap.state, 1, "Brake stat goes high at brk 1 thresh") brk1.state = BRK_1_REST_V - hil.check(brk_stat_tap.state == 0, "Brake stat starts low") + # hil.check(brk_stat_tap.state == 0, "Brake stat starts low") + check.equal(brk_stat_tap.state, 0, "Brake stat starts low") brk2.state = BRK_2_THRESH_V time.sleep(0.1) - hil.check(brk_stat_tap.state == 1, "Brake stat goes high at brk 2 thresh") + # hil.check(brk_stat_tap.state == 1, "Brake stat goes high at brk 2 thresh") + check.equal(brk_stat_tap.state, 1, "Brake stat goes high at brk 2 thresh") brk1.state = BRK_1_THRESH_V - hil.check(brk_stat_tap.state == 1, "Brake stat stays high for both brakes") + # hil.check(brk_stat_tap.state == 1, "Brake stat stays high for both brakes") + check.equal(brk_stat_tap.state, 1, "Brake stat stays high for both brakes") # Brake threshold scan brk1.state = BRK_MIN_OUT_V brk2.state = BRK_MIN_OUT_V time.sleep(0.1) - hil.check(brk_stat_tap.state == 0, "Brake Stat Starts Low Brk 1") + # hil.check(brk_stat_tap.state == 0, "Brake Stat Starts Low Brk 1") + check.equal(brk_stat_tap.state, 0, "Brake Stat Starts Low Brk 1") start = BRK_MIN_OUT_V stop = BRK_MAX_OUT_V @@ -189,72 +241,108 @@ def test_bspd(hil): BRK_SWEEP_DELAY, brk_stat_tap, is_falling=False) print(f"Brake 1 braking threshold: {thresh}") - hil.check_within(thresh, BRK_1_THRESH_V, 0.2, "Brake 1 trip voltage") - hil.check(brk_stat_tap.state == 1, "Brake Stat Tripped for Brk 1") + # hil.check_within(thresh, BRK_1_THRESH_V, 0.2, "Brake 1 trip voltage") + # hil.check(brk_stat_tap.state == 1, "Brake Stat Tripped for Brk 1") + check.almost_equal(thresh, BRK_1_THRESH_V, abs=0.2, rel=0.0, msg="Brake 1 trip voltage") + check.equal(brk_stat_tap.state, 1, "Brake Stat Tripped for Brk 1") brk1.state = BRK_MIN_OUT_V brk2.state = BRK_MIN_OUT_V - hil.check(brk_stat_tap.state == 0, "Brake Stat Starts Low Brk 2") + # hil.check(brk_stat_tap.state == 0, "Brake Stat Starts Low Brk 2") + check.equal(brk_stat_tap.state, 0, "Brake Stat Starts Low Brk 2") thresh = utils.measure_trip_thresh(brk2, start, stop, step, BRK_SWEEP_DELAY, brk_stat_tap, is_falling=False) print(f"Brake 2 braking threshold: {thresh}") - hil.check_within(thresh, BRK_2_THRESH_V, 0.2, "Brake 2 trip voltage") - hil.check(brk_stat_tap.state == 1, "Brake Stat Tripped for Brk 2") + # hil.check_within(thresh, BRK_2_THRESH_V, 0.2, "Brake 2 trip voltage") + # hil.check(brk_stat_tap.state == 1, "Brake Stat Tripped for Brk 2") + check.almost_equal(thresh, BRK_2_THRESH_V, abs=0.2, rel=0.0, msg="Brake 2 trip voltage") + check.equal(brk_stat_tap.state, 1, "Brake Stat Tripped for Brk 2") # Brake Fail scan brk1.state = BRK_1_REST_V brk2.state = BRK_2_REST_V time.sleep(0.1) - hil.check(brk_fail_tap.state == 0, "Brake Fail Check 1 Starts 0") + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 1 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 1 Starts 0") + brk1.state = 0.0 # Force 0 time.sleep(0.1) - hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Short GND") + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Short GND") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 1 Short GND") + brk1.state = BRK_1_REST_V time.sleep(0.1) - hil.check(brk_fail_tap.state == 0, "Brake Fail Check 2 Starts 0") + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 2 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 2 Starts 0") + brk2.state = 0.0 # Force 0 time.sleep(0.1) - hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Short GND") + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Short GND") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 2 Short GND") + brk2.state = BRK_2_REST_V time.sleep(0.1) - hil.check(brk_fail_tap.state == 0, "Brake Fail Check 3 Starts 0") + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 3 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 3 Starts 0") + brk1.state = 5.0 # Short VCC time.sleep(0.1) - hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Short VCC") + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Short VCC") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 1 Short VCC") + brk1.state = BRK_1_REST_V time.sleep(0.1) - hil.check(brk_fail_tap.state == 0, "Brake Fail Check 4 Starts 0") + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 4 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 4 Starts 0") + brk2.state = 5.0 # Short VCC time.sleep(0.1) - hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Short VCC") + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Short VCC") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 2 Short VCC") + brk2.state = BRK_2_REST_V time.sleep(0.1) - hil.check(brk_fail_tap.state == 0, "Brake Fail Check 5 Starts 0") + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 5 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 5 Starts 0") + brk1.hiZ() time.sleep(0.1) - hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Hi-Z") + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 1 Hi-Z") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 1 Hi-Z") + brk1.state = BRK_1_REST_V time.sleep(0.1) - hil.check(brk_fail_tap.state == 0, "Brake Fail Check 6 Starts 0") + # hil.check(brk_fail_tap.state == 0, "Brake Fail Check 6 Starts 0") + check.equal(brk_fail_tap.state, 0, "Brake Fail Check 6 Starts 0") + brk2.hiZ() time.sleep(0.1) - hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Hi-Z") + # hil.check(brk_fail_tap.state == 1, "Brake Fail Brk 2 Hi-Z") + check.equal(brk_fail_tap.state, 1, "Brake Fail Brk 2 Hi-Z") + brk2.state = BRK_2_REST_V # Brake Fail reset_bspd(brk1, brk2, c_sense) cycle_power() time.sleep(BSPD_DASH_ON_TIME) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") + brk1.state = 0.0 t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") - hil.check(brk_fail_tap.state == 1, "Brake Fail went high") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + # hil.check(brk_fail_tap.state == 1, "Brake Fail went high") + check.less(t, R_BSPD_MAX_TRIP_TIME_S, "Brake Fail") + check.equal(brk_fail_tap.state, 1, "Brake Fail went high") + brk1.state = BRK_1_REST_V time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(brk_fail_tap.state == 0, "Brake Fail returned low") - hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + # hil.check(brk_fail_tap.state == 0, "Brake Fail returned low") + # hil.check(bspd_ctrl.state == 0, "Brake Fail Stays Latched") + check.equal(brk_fail_tap.state, 0, "Brake Fail returned low") + check.equal(bspd_ctrl.state, 0, "Brake Fail Stays Latched") # Brake Fail on Power On reset_bspd(brk1, brk2, c_sense) @@ -266,7 +354,8 @@ def test_bspd(hil): power.state = 0 time.sleep(R_BSPD_MAX_TRIP_TIME_S) time.sleep(BSPD_DASH_ON_TIME) # NOTE: test can't check for the trip time - hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + # hil.check(bspd_ctrl.state == 0, "Power On Brake Fail") + check.equal(bspd_ctrl.state, 0, "Power On Brake Fail") # Current no brake reset_bspd(brk1, brk2, c_sense) @@ -280,47 +369,59 @@ def test_bspd(hil): time.sleep(R_BSPD_MAX_TRIP_TIME_S) # time.sleep(100) time.sleep(3) - hil.check(bspd_ctrl.state == 1, "Current no brake") + # hil.check(bspd_ctrl.state == 1, "Current no brake") + check.equal(bspd_ctrl.state, 1, "Current no brake") # Current Sense Short to Ground reset_bspd(brk1, brk2, c_sense) cycle_power() time.sleep(BSPD_DASH_ON_TIME) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On" + check.equal(bspd_ctrl.state, 1, "Power On") + c_sense.state = 0.0 t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") + check.less(t, R_BSPD_MAX_TRIP_TIME_S, "Current short to ground") set_bspd_current(c_sense, 0.0) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + # hil.check(bspd_ctrl.state == 0, "Current short to ground stays latched") + check.equal(bspd_ctrl.state, 0, "Current short to ground stays latched") # Current Sense Short to 5V reset_bspd(brk1, brk2, c_sense) cycle_power() time.sleep(BSPD_DASH_ON_TIME*1.2) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") c_sense.state = ABOX_DHAB_CH1_DIV.div(5.0) t = utils.measure_trip_time(bspd_ctrl, 5.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") + check.less(t, R_BSPD_MAX_TRIP_TIME_S, "Current short to 5V") set_bspd_current(c_sense, 0.0) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + # hil.check(bspd_ctrl.state == 0, "Current short to 5V stays latched") + check.equal(bspd_ctrl.state, 0, "Current short to 5V stays latched") # Braking reset_bspd(brk1, brk2, c_sense) cycle_power() time.sleep(BSPD_DASH_ON_TIME) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") brk1.state = BRK_1_THRESH_V time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Brake no current") - hil.check(brk_stat_tap.state == 1, "Brake stat went high") + # hil.check(bspd_ctrl.state == 1, "Brake no current") + # hil.check(brk_stat_tap.state == 1, "Brake stat went high") + check.equal(bspd_ctrl.state, 1, "Brake no current") + check.equal(brk_stat_tap.state, 1, "Brake stat went high") # Lowest current required to trip at min_trip_current = R_BSPD_POWER_THRESH_W / ACCUM_MAX_V set_bspd_current(c_sense, min_trip_current) t = utils.measure_trip_time(bspd_ctrl, 10.0, is_falling=True) - hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + # hil.check(t < R_BSPD_MAX_TRIP_TIME_S, "Braking with current") + check.less(t, R_BSPD_MAX_TRIP_TIME_S, "Braking with current") # Measure braking with current threshold reset_bspd(brk1, brk2, c_sense) @@ -328,7 +429,8 @@ def test_bspd(hil): time.sleep(BSPD_DASH_ON_TIME) brk1.state = BRK_1_THRESH_V - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") start = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(0.0)) stop = ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(DHAB_S124_CH1_MAX_A)) step = 0.1 @@ -337,16 +439,23 @@ def test_bspd(hil): bspd_ctrl, is_falling=True) thresh_amps = dhab_ch1_v_to_a(ABOX_DHAB_CH1_DIV.reverse(thresh)) print(f"Current while braking threshold: {thresh}V = {thresh_amps}A") - hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + # hil.check_within(thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), 0.1, "Current while braking threshold") + check.almost_equal( + thresh, ABOX_DHAB_CH1_DIV.div(dhab_ch1_a_to_v(min_trip_current)), + abs=0.1, rel=0.0, + msg="Current while braking threshold" + ) # Determine the current sense short to gnd threshold reset_bspd(brk1, brk2, c_sense) cycle_power() time.sleep(BSPD_DASH_ON_TIME) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") set_bspd_current(c_sense, DHAB_S124_CH1_MIN_A) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Min output current okay") + # hil.check(bspd_ctrl.state == 1, "Min output current okay") + check.equal(bspd_ctrl.state, 1, "Min output current okay") start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MIN_OUT_V) stop = 0.0 step = -0.1 @@ -354,18 +463,21 @@ def test_bspd(hil): R_BSPD_MAX_TRIP_TIME_S, bspd_ctrl, is_falling=True) print(f"Short to ground threshold: {thresh}V") - hil.check(stop < (thresh) < start, "Current short to ground threshold") + # hil.check(stop < (thresh) < start, "Current short to ground threshold") + check.between(thresh, stop, start, "Current short to ground threshold") # Determine the current sense short to 5V threshold reset_bspd(brk1, brk2, c_sense) cycle_power() time.sleep(BSPD_DASH_ON_TIME) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") time.sleep(2) set_bspd_current(c_sense, DHAB_S124_CH1_MAX_A) time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Max output current okay") + # hil.check(bspd_ctrl.state == 1, "Max output current okay") + check.equal(bspd_ctrl.state, 1, "Max output current okay") start = ABOX_DHAB_CH1_DIV.div(DHAB_S124_MAX_OUT_V) stop = ABOX_DHAB_CH1_DIV.div(5.0) @@ -373,26 +485,31 @@ def test_bspd(hil): c_sense.state = start time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 1, "Max output voltage okay") + # hil.check(bspd_ctrl.state == 1, "Max output voltage okay") + check.equal(bspd_ctrl.state, 1, "Max output voltage okay") input("enter to continue") thresh = utils.measure_trip_thresh(c_sense, start, stop, step, R_BSPD_MAX_TRIP_TIME_S, bspd_ctrl, is_falling=True) print(f"Short to 5V threshold: {thresh}V") - hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + # hil.check(bspd_ctrl.state == 0, "Short to 5V trips") + check.equal(bspd_ctrl.state, 0, "Short to 5V trips") print(stop) print(start) - hil.check(start < (thresh) <= stop, "Current short to 5V threshold") + # hil.check(start < (thresh) <= stop, "Current short to 5V threshold") + check.between(thresh, start, stop, "Current short to 5V threshold", le=True) # Floating current reset_bspd(brk1, brk2, c_sense) cycle_power() time.sleep(BSPD_DASH_ON_TIME) - hil.check(bspd_ctrl.state == 1, "Power On") + # hil.check(bspd_ctrl.state == 1, "Power On") + check.equal(bspd_ctrl.state, 1, "Power On") c_sense.hiZ() time.sleep(R_BSPD_MAX_TRIP_TIME_S) - hil.check(bspd_ctrl.state == 0, "Floating current") + # hil.check(bspd_ctrl.state == 0, "Floating current") + check.equal(bspd_ctrl.state, 0, "Floating current") # Floating brake_fail # Can't test this at system level! @@ -401,9 +518,11 @@ def test_bspd(hil): # Can't test this at system level! # End the test - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # IMD_RC_MIN_TRIP_TIME_S = IMD_STARTUP_TIME_S IMD_RC_MAX_TRIP_TIME_S = R_IMD_MAX_TRIP_TIME_S - IMD_MEASURE_TIME_S IMD_CTRL_OKAY = 1 @@ -411,7 +530,7 @@ def test_bspd(hil): def test_imd(hil): # Begin the test - hil.start_test(test_imd.__name__) + # hil.start_test(test_imd.__name__) # Outputs imd_stat = hil.dout("Main_Module", "IMD_Status") @@ -432,37 +551,47 @@ def test_imd(hil): # IMD Fault reset_imd(imd_stat) cycle_power() - hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + # hil.check(imd_ctrl.state == IMD_CTRL_OKAY, "Power On") + check.equal(imd_ctrl.state, IMD_CTRL_OKAY, "Power On") time.sleep(1) imd_stat.state = IMD_STAT_TRIP t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) print(f"Target trip time: [{IMD_RC_MIN_TRIP_TIME_S}, {IMD_RC_MAX_TRIP_TIME_S}]") - hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + # hil.check(IMD_RC_MIN_TRIP_TIME_S < t < IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Trip") + check.between(t, IMD_RC_MIN_TRIP_TIME_S, IMD_RC_MAX_TRIP_TIME_S, "IMD Trip Time") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Trip") imd_stat.state = IMD_STAT_OKAY time.sleep(IMD_RC_MAX_TRIP_TIME_S * 1.1) - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Stays Latched") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Fault Stays Latched") # IMD Fault on Power On reset_imd(imd_stat) imd_stat.state = IMD_STAT_TRIP cycle_power() time.sleep(IMD_RC_MAX_TRIP_TIME_S) - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Fault Power On") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Fault Power On") # IMD Floating reset_imd(imd_stat) imd_stat.hiZ() cycle_power() t = utils.measure_trip_time(imd_ctrl, R_IMD_MAX_TRIP_TIME_S, is_falling=True) - hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") - hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + # hil.check(t < R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + # hil.check(imd_ctrl.state == IMD_CTRL_TRIP, "IMD Floating Trip") + check.less(t, R_IMD_MAX_TRIP_TIME_S, "IMD Floating Trip Time") + check.equal(imd_ctrl.state, IMD_CTRL_TRIP, "IMD Floating Trip") - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # def test_ams(hil): # Begin the test - hil.start_test(test_ams.__name__) + # hil.start_test(test_ams.__name__) # Outputs ams_stat = hil.dout("Main_Module", "BMS-Status-Main") @@ -483,33 +612,43 @@ def test_ams(hil): # AMS Fault reset_ams(ams_stat) cycle_power() - hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + # hil.check(ams_ctrl.state == AMS_CTRL_OKAY, "Power On") + check.equal(ams_ctrl.state, AMS_CTRL_OKAY, "Power On") time.sleep(1) ams_stat.state = AMS_STAT_TRIP t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) - hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + # hil.check(0 < t < AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Trip") + check.between(t, 0, AMS_MAX_TRIP_DELAY_S, "AMS Trip Time") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Trip") ams_stat.state = AMS_STAT_OKAY time.sleep(AMS_MAX_TRIP_DELAY_S * 1.1) - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Stays Latched") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Fault Stays Latched") # AMS Fault on Power On reset_ams(ams_stat) ams_stat.state = AMS_STAT_TRIP cycle_power() time.sleep(AMS_MAX_TRIP_DELAY_S) - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Fault Power On") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Fault Power On") # AMS Floating reset_ams(ams_stat) ams_stat.hiZ() cycle_power() t = utils.measure_trip_time(ams_ctrl, AMS_MAX_TRIP_DELAY_S * 2, is_falling=True) - hil.check(0 <= t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") - hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + # hil.check(0 <= t < AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + # hil.check(ams_ctrl.state == AMS_CTRL_TRIP, "AMS Floating Trip") + check.between(t, 0, AMS_MAX_TRIP_DELAY_S, "AMS Floating Trip Time") + check.equal(ams_ctrl.state, AMS_CTRL_TRIP, "AMS Floating Trip") - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # def tsal_is_red(): while 1: i = input("Is TSAL Green (g) or Red (r): ") @@ -536,21 +675,25 @@ def test_tsal(hil): time.sleep(0.2) # No need to power cycle - hil.check(tsal_is_red() == False, "LVAL on at v_mc = 0") + # hil.check(tsal_is_red() == False, "LVAL on at v_mc = 0") + check.equal(tsal_is_red(), False, "LVAL on at v_mc = 0") #hil.check(tsal.state == 0, "TSAL off at v_mc = 0") time.sleep(5) - hil.check(tsal_is_red() == False, "LVAL stays on") + # hil.check(tsal_is_red() == False, "LVAL stays on") + check.equal(tsal_is_red(), False, "LVAL stays on") v_mc.state = tiff_hv_to_lv(ACCUM_MIN_V) time.sleep(0.1) #hil.check(lval.state == 0, f"LVAL off at {ACCUM_MIN_V:.4} V") - hil.check(tsal_is_red() == True, f"TSAL on at {ACCUM_MIN_V:.4} V") + # hil.check(tsal_is_red() == True, f"TSAL on at {ACCUM_MIN_V:.4} V") + check.equal(tsal_is_red(), True, f"TSAL on at {ACCUM_MIN_V:.4} V") reset_tsal(v_mc) time.sleep(0.2) - hil.check(tsal_is_red() == False, f"LVAL turns back on") + # hil.check(tsal_is_red() == False, f"LVAL turns back on") + check.equal(tsal_is_red(), False, f"LVAL turns back on") start = tiff_hv_to_lv(50) stop = tiff_hv_to_lv(R_TSAL_HV_V * 1.5) @@ -574,27 +717,36 @@ def test_tsal(hil): if (not tripped): utils.log_warning(f"TSAL did not trip at stop of {stop}.") thresh = stop - hil.check(tripped, "TSAL tripped") + # hil.check(tripped, "TSAL tripped") + check.is_true(tripped, "TSAL tripped") thresh = tiff_lv_to_hv(thresh) print(f"TSAL on at {thresh:.4} V") hil.check_within(thresh, R_TSAL_HV_V, 4, f"TSAL trips at {R_TSAL_HV_V:.4} +-4") + check.almost_equal(thresh, R_TSAL_HV_V, abs=4, rel=0.0, msg=f"TSAL trips at {R_TSAL_HV_V:.4} +-4") + + # hil.end_test() +# ---------------------------------------------------------------------------- # - hil.end_test() +# ---------------------------------------------------------------------------- # def test_sdc(hil): ''' Check that every node in the sdc trips ''' # Begin the test - hil.start_test(test_sdc.__name__) + # hil.start_test(test_sdc.__name__) # Outputs # Inputs - hil.check(0, "TODO") + # hil.check(False, "TODO") + check.is_true(False, "TODO") - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # def is_buzzer_on(): while 1: i = input("Is Buzzer On (y) or No (n): ") @@ -606,7 +758,7 @@ def is_buzzer_on(): def test_buzzer(hil): # Begin the test - hil.start_test(test_buzzer.__name__) + # hil.start_test(test_buzzer.__name__) # Outputs buzzer_ctrl = hil.daq_var("Main_Module", "daq_buzzer") @@ -616,21 +768,29 @@ def test_buzzer(hil): buzzer_ctrl.state = 0 time.sleep(0.02) - hil.check(buzzer_stat.state == 0, "Buzzer Off") + # hil.check(buzzer_stat.state == 0, "Buzzer Off") + check.equal(buzzer_stat.state, 0, "Buzzer Off") buzzer_ctrl.state = 1 print(buzzer_ctrl.state) time.sleep(0.02) - hil.check(buzzer_stat.state == 1, "Buzzer On") - hil.check(is_buzzer_on() == True, "Buzzer Making Noise") + # hil.check(buzzer_stat.state == 1, "Buzzer On") + # hil.check(is_buzzer_on() == True, "Buzzer Making Noise") + check.equal(buzzer_stat.state, 1, "Buzzer On") + check.is_true(is_buzzer_on(), "Buzzer Making Noise") buzzer_ctrl.state = 0 time.sleep(0.02) - hil.check(buzzer_stat.state == 0, "Buzzer back Off") - hil.check(is_buzzer_on() == False, "Buzzer Not Making Noise") + # hil.check(buzzer_stat.state == 0, "Buzzer back Off") + # hil.check(is_buzzer_on() == False, "Buzzer Not Making Noise") + check.equal(buzzer_stat.state, 0, "Buzzer back Off") + check.is_false(is_buzzer_on(), "Buzzer Not Making Noise") + + # hil.end_test() +# ---------------------------------------------------------------------------- # - hil.end_test() +# ---------------------------------------------------------------------------- # def is_brake_light_on(): while 1: i = input("Is Brake Light On (y) or No (n): ") @@ -642,7 +802,7 @@ def is_brake_light_on(): def test_brake_light(hil): # Begin the test - hil.start_test(test_brake_light.__name__) + # hil.start_test(test_brake_light.__name__) # Outputs brk_ctrl = hil.daq_var("Main_Module", "daq_brake") @@ -652,28 +812,36 @@ def test_brake_light(hil): brk_ctrl.state = 0 time.sleep(0.02) - hil.check(brk_ctrl.state == 0, "Brake Off") + # hil.check(brk_ctrl.state == 0, "Brake Off") + check.equal(brk_ctrl.state, 0, "Brake Off") brk_ctrl.state = 1 print(brk_ctrl.state) time.sleep(0.02) - hil.check(brk_ctrl.state == 1, "Brake Light On") - hil.check(is_brake_light_on() == True, "Brake Light is On") + # hil.check(brk_ctrl.state == 1, "Brake Light On") + # hil.check(is_brake_light_on() == True, "Brake Light is On") + check.equal(brk_ctrl.state, 1, "Brake Light On") + check.is_true(is_brake_light_on(), "Brake Light is On") brk_ctrl.state = 0 time.sleep(0.02) - hil.check(brk_ctrl.state == 0, "Brake Light back Off") - hil.check(is_brake_light_on() == False, "Brake Light is Off") + # hil.check(brk_ctrl.state == 0, "Brake Light back Off") + # hil.check(is_brake_light_on() == False, "Brake Light is Off") + check.equal(brk_ctrl.state, 0, "Brake Light back Off") + check.is_false(is_brake_light_on(), "Brake Light is Off") # Can copy lot from bspd # Read the brake control mcu pin # Finally have user verify light actually turned on - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # def test_light_tsal_buz(hil): - hil.start_test(test_light_tsal_buz.__name__) + # hil.start_test(test_light_tsal_buz.__name__) # Outputs brk_ctrl = hil.daq_var("Main_Module", "daq_brake") @@ -685,30 +853,7 @@ def test_light_tsal_buz(hil): brk_ctrl.state = 0 buzzer_ctrl.state = 0 - hil.end_test() - - -if __name__ == "__main__": - hil = HIL() - - hil.load_config("config_system_hil_attached.json") - hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") - - hil.init_can() - - power = hil.dout("RearTester", "RLY1") - - # Drive Critical Tests - # test_precharge(hil) - test_bspd(hil) - # test_imd(hil) # note: tsal needs to be tripped - # test_ams(hil) - # test_tsal(hil) - # test_sdc(hil) - # test_buzzer(hil) - # test_brake_light(hil) - # test_light_tsal_buz(hil) - - # Peripheral Sensor Tests + check.is_true(True, "TODO") - hil.shutdown() + # hil.end_test() +# ---------------------------------------------------------------------------- # \ No newline at end of file diff --git a/scripts/test_test.py b/scripts/test_test.py index 08af75a..b2536bb 100644 --- a/scripts/test_test.py +++ b/scripts/test_test.py @@ -1,15 +1,37 @@ -from os import sys, path -sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) -from hil import HIL -import utils +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +from hil.hil import HIL +# import hil.utils as utils import time import can from rules_constants import * from vehicle_constants import * +import pytest_check as check +import pytest + + +# ---------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def hil(): + hil_instance = HIL() + + # hil.load_config("config_testing.json") + hil_instance.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") + hil_instance.init_can() + + yield hil_instance + + hil_instance.shutdown() +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # def test_bspd(hil): # Begin the test - hil.start_test(test_bspd.__name__) + # hil.start_test(test_bspd.__name__) # Inputs d2 = hil.din("Test_HIL", "AI2") @@ -53,10 +75,15 @@ def test_bspd(hil): time.sleep(1) time.sleep(2) - hil.end_test() + check.is_true(True, "TODO") + + # hil.end_test() +# ---------------------------------------------------------------------------- # + +# ---------------------------------------------------------------------------- # def test_dac(hil): - hil.start_test(test_dac.__name__) + # hil.start_test(test_dac.__name__) dac1 = hil.aout("Test_HIL", "DAC1") dac2 = hil.aout("Test_HIL", "DAC2") @@ -71,8 +98,13 @@ def test_dac(hil): dac2.state = 0.25 input(".25") - hil.end_test() + check.is_true(True, "TODO") + # hil.end_test() +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # def test_pot(hil): pot1 = hil.pot("Test_HIL", "POT1") @@ -107,8 +139,13 @@ def test_pot(hil): pot2.state = 0.5 input("-------") + check.is_true(True, "TODO") +# ---------------------------------------------------------------------------- # + + +# ---------------------------------------------------------------------------- # def test_mcu_pin(hil): - hil.start_test(test_mcu_pin.__name__) + # hil.start_test(test_mcu_pin.__name__) brk_stat_tap = hil.mcu_pin("Dashboard", "BRK_STAT_TAP") @@ -123,12 +160,16 @@ def test_mcu_pin(hil): delta_cnt = delta_cnt + 1 print(f"Average: {delta_avg/delta_cnt}") + + check.is_true(True, "TODO") - hil.end_test() + # hil.end_test() +# ---------------------------------------------------------------------------- # +# ---------------------------------------------------------------------------- # def test_daq(hil): - hil.start_test(test_daq.__name__) + # hil.start_test(test_daq.__name__) counter = 0 start_time = time.time() @@ -153,20 +194,8 @@ def test_daq(hil): if (delta < 0): delta = 0 time.sleep(delta) + check.is_true(True, "TODO") + print("Done") print(f"Last count sent: {counter - 1}") - - -if __name__ == "__main__": - hil = HIL() - #hil.load_config("config_testing.json") - hil.load_pin_map("per_24_net_map.csv", "stm32f407_pin_map.csv") - - hil.init_can() - #test_bspd(hil) - #test_dac(hil) - # test_pot(hil) - # test_mcu_pin(hil) - test_daq(hil) - - hil.shutdown() +# ---------------------------------------------------------------------------- # \ No newline at end of file diff --git a/scripts/vehicle_constants.py b/scripts/vehicle_constants.py index 35a4050..4db3f2a 100644 --- a/scripts/vehicle_constants.py +++ b/scripts/vehicle_constants.py @@ -1,6 +1,8 @@ -from os import sys, path -sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'hil')) -import utils +from os import sys, path +# adds "./HIL-Testing" to the path, basically making it so these scripts were run one folder level higher +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +import hil.utils as utils # NOTE: each value in this file should be a physical or electrical property of the vehicle diff --git a/testbench/TestBench.ino b/testbench/TestBench.ino deleted file mode 100644 index 8618ef4..0000000 --- a/testbench/TestBench.ino +++ /dev/null @@ -1,214 +0,0 @@ - -#include -#include "MCP4021.h" - -//#define STM32 -#ifdef STM32 -#define SERIAL SerialUSB -#define HAL_DAC_MODULE_ENABLED 1 -#else -#define SERIAL Serial -#endif - -const int TESTER_ID = 2; - -#define DAC - -#ifdef STM32 -#ifdef DAC -#warning "Can't have both DAC and STM32 enabled" -#endif -#endif - -#ifdef DAC -#include -#define NUM_DACS 2 -DFRobot_MCP4725 dacs[NUM_DACS]; -uint8_t dac_power_down[NUM_DACS]; -const uint16_t dac_vref = 255; -#endif - -#define DIGIPOT_EN -#ifdef DIGIPOT_EN -const uint8_t DIGIPOT_UD_PIN = 7; -const uint8_t DIGIPOT_CS1_PIN = 22; // A4 -const uint8_t DIGIPOT_CS2_PIN = 23; // A5 -MCP4021 digipot1(DIGIPOT_CS1_PIN, DIGIPOT_UD_PIN, false); // initialize Digipot 1 -MCP4021 digipot2(DIGIPOT_CS2_PIN, DIGIPOT_UD_PIN, false); // initialize Digipot 2 -#endif - -int count = 0; -char data[3]; - -struct Command -{ - Command(uint8_t command=0, uint8_t pin=0, uint8_t value=0) - { - data[0] = command; - data[1] = pin; - data[2] = value; - }; - - void reinit(char data[]) - { - this->data[0] = data[0]; - this->data[1] = data[1]; - this->data[2] = data[2]; - }; - - uint8_t command() { return data[0];}; - uint8_t pin() { return data[1]; }; - uint16_t value() { - return data[2]; - }; - int size() { return 3 * sizeof(char); }; - char data[3]; -}; - - -enum GpioCommands -{ - READ_ADC = 0, - READ_GPIO = 1, - WRITE_DAC = 2, - WRITE_GPIO = 3, - READ_ID = 4, - WRITE_POT = 5, -}; - - -void setup() -{ - SERIAL.begin(115200); - -#ifdef DIGIPOT_EN - // Setting up Digipot 1 - digipot1.setup(); - digipot1.begin(); - - // Setting up Digipot 2 - digipot2.setup(); - digipot2.begin(); -#endif -#ifdef DAC - dacs[0].init(0x63, dac_vref); - dacs[1].init(0x62, dac_vref); - dacs[0].setMode(MCP4725_POWER_DOWN_500KRES); - dacs[1].setMode(MCP4725_POWER_DOWN_500KRES); - dac_power_down[0] = 1; - dac_power_down[1] = 1; -#endif -} - -void error(String error_string) -{ - SERIAL.write(0xFF); - SERIAL.write(0xFF); - SERIAL.println(error_string); -} - -void loop() -{ - if (SERIAL.available() >= 3) - { - data[0] = SERIAL.read(); - data[1] = SERIAL.read(); - data[2] = SERIAL.read(); - Command c; - c.reinit(data); - count++; - uint8_t command = c.command(); - uint8_t pin = c.pin(); - uint16_t value = c.value(); - - switch (command) - { - case (WRITE_GPIO): - { - //if (pin < DIGITAL_PIN_COUNT) - if (1) - { - pinMode(pin, OUTPUT); - digitalWrite(pin, value); - } - else - { - error("GPIO PIN COUNT EXCEEDED"); - } - break; - } - case (READ_GPIO): - { -#ifdef DAC - if (pin >= 200 && pin < 200 + NUM_DACS) - { - dacs[pin - 200].setMode(MCP4725_POWER_DOWN_500KRES); - dac_power_down[pin - 200] = 1; - SERIAL.write(0x01); - } - else -#endif - { - pinMode(pin, INPUT); - int val = digitalRead(pin); - SERIAL.write(val & 0xFF); - } - break; - } - case (WRITE_DAC): - { -#ifdef DAC - if (pin >= 200 && pin < 200 + NUM_DACS) - { - if (dac_power_down[pin-200]) - { - dacs[pin-200].setMode(MCP4725_NORMAL_MODE); - dac_power_down[pin - 200] = 0; - } - dacs[pin - 200].outputVoltage(value & 0xFF); - } -#endif -#ifdef STM32 - // 4 and 5 have DAC on f407 - pinMode(pin, OUTPUT); - analogWrite(pin, value & 0xFF); // max val 255 -#endif - break; - } - case (READ_ADC): - { - //if (pin <= ANALOG_PIN_COUNT) - if (1) - { - int val = analogRead(pin); - SERIAL.write((val >> 8) & 0xFF); - SERIAL.write(val & 0xFF); - } - else - { - error("ADC PIN COUNT EXCEEDED"); - } - break; - } - case (READ_ID): - { - SERIAL.write(TESTER_ID); - break; - } - case (WRITE_POT): - { -#ifdef DIGIPOT_EN - if (pin == 1) - digipot1.setTap((uint8_t) value); - else if (pin == 2) - digipot2.setTap((uint8_t) value); - else -#endif - { - error("POT PIN COUNT EXCEEDED"); - } - break; - } - } - } -}