diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 1348238f7..000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# All Pr's must be reviewed by at least one of the following members -* @samparent97 @BlakeFreer @TylerStAmour @langei @CameronBeneteau diff --git a/.github/workflows/build-test-projects.yml b/.github/workflows/build-test-projects.yml index 884d24560..0f77c96a8 100644 --- a/.github/workflows/build-test-projects.yml +++ b/.github/workflows/build-test-projects.yml @@ -7,6 +7,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/cache@v4 with: @@ -52,10 +54,20 @@ jobs: - name: Perform Static Code Analysis on PlatformIO Projects shell: bash run: | - git ls-files -z 'projects/**/platformio.ini' | while IFS= read -r -d '' ini; do + git ls-files -z 'projects/**/platformio.ini' | grep -zv 'projects/demo/' | while IFS= read -r -d '' ini; do dir=$(dirname "$ini") echo "============================================================" echo "==== Performing Static Code Analysis on $dir" echo "============================================================" - pio check --project-dir "$dir" --fail-on-defect=high || exit 1 + + SUBDIRS=("$dir/src" "$dir/include") + EXCLUDE_PREFIX="stm*" + + # Check for changes in $dir/src and $dir/include, excluding files within $dir/src/platforms/stm* + if git diff --name-only origin/main...HEAD -- "${SUBDIRS[@]}" ":!$dir/src/platforms/$EXCLUDE_PREFIX" | grep -q .; then + echo "Changes detected in $dir → running pio check" + pio check --project-dir "$dir" --flags "--checks=bugprone-*,clang-analyzer-*,google-*,performance-*" --fail-on-defect=high || exit 1 + else + echo "No changes in $dir → skipping" + fi done diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fb6ed2d0..6571a0f72 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,11 +16,3 @@ repos: - id: clang-format require_serial: true stages: [pre-commit] - - - repo: local - hooks: - - id: pio-check - name: Platform IO Static Analysis - entry: bash scripts/static_analysis/static_analysis.sh - language: system - stages: [pre-commit] \ No newline at end of file diff --git a/lib/cobs/.gitignore b/lib/cobs/.gitignore new file mode 100644 index 000000000..89cc49cbd --- /dev/null +++ b/lib/cobs/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/lib/cobs/cobs.cpp b/lib/cobs/cobs.cpp new file mode 100644 index 000000000..4f00e194a --- /dev/null +++ b/lib/cobs/cobs.cpp @@ -0,0 +1,69 @@ +#include "cobs.hpp" + +#include +#include + +namespace macfe::cobs { + +Decoder::Decoder(uint8_t* buffer) + : buffer(buffer), length(0), block_remaining_(0), code_(0xff) {} + +bool Decoder::Decode(const uint8_t* encoded, size_t encoded_length) { + // adapted from + // https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing + // differences: supports procedural decoding / does not require `encoded` to + // contain the entire message + + const uint8_t* input_cursor = encoded; + + for (; input_cursor < encoded + encoded_length; --block_remaining_) { + if (block_remaining_ > 0) { + buffer[length++] = *input_cursor++; + } else { + block_remaining_ = *input_cursor++; + if (block_remaining_ == 0) { + return true; + } + if (code_ != 0xff) { + buffer[length++] = 0; + } + code_ = block_remaining_; + } + } + return false; +} + +constexpr size_t MaxEncodedLength(size_t raw_length) { + constexpr size_t LEADING_ZERO = 1; + constexpr size_t TERMINATING_ZERO = 1; + size_t MAX_STUFF_BYTES = (raw_length + 253) / 254; + return LEADING_ZERO + raw_length + MAX_STUFF_BYTES + TERMINATING_ZERO; +} + +size_t Encode(const uint8_t* raw, size_t length, uint8_t* output) { + uint8_t* encode_cursor = output; + + uint8_t zero_offset = 1; + uint8_t* zero_offset_p = encode_cursor++; + + for (const uint8_t* byte = raw; length--; ++byte) { + if (*byte != 0) { + *encode_cursor++ = *byte; + ++zero_offset; + } + if ((*byte == 0) || (zero_offset == 0xff)) { + *zero_offset_p = zero_offset; + zero_offset = 1; + zero_offset_p = encode_cursor; + if ((*byte == 0) || (length > 0)) { + ++encode_cursor; + } + } + } + *zero_offset_p = zero_offset; // write the final zero_offset value + *encode_cursor++ = 0; // write delimiter + + return size_t(encode_cursor - output); +} + +} // namespace macfe::cobs diff --git a/lib/cobs/cobs.hpp b/lib/cobs/cobs.hpp new file mode 100644 index 000000000..6efe27ef1 --- /dev/null +++ b/lib/cobs/cobs.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace macfe::cobs { + +class Decoder { +public: + Decoder(uint8_t* buffer); + bool Decode(const uint8_t* encoded, size_t encoded_length); + + uint8_t* buffer; + size_t length; + +private: + uint8_t block_remaining_; + uint8_t code_; +}; + +constexpr size_t MaxEncodedLength(size_t raw_length); + +size_t Encode(const uint8_t* raw, size_t length, uint8_t* output); + +} // namespace macfe::cobs \ No newline at end of file diff --git a/lib/cobs/platformio.ini b/lib/cobs/platformio.ini new file mode 100644 index 000000000..fc09d0803 --- /dev/null +++ b/lib/cobs/platformio.ini @@ -0,0 +1,9 @@ +[platformio] +include_dir = . +src_dir = . + +[env:native] +test_framework = googletest +platform = native +test_build_src = true +build_src_filter = + \ No newline at end of file diff --git a/lib/cobs/test/cobs_test.cpp b/lib/cobs/test/cobs_test.cpp new file mode 100644 index 000000000..a18232848 --- /dev/null +++ b/lib/cobs/test/cobs_test.cpp @@ -0,0 +1,157 @@ +#include "cobs.hpp" + +#include "gtest/gtest.h" + +using namespace macfe::cobs; + +void EXPECT_ARRAY_EQUAL(uint8_t* expected, size_t expected_len, uint8_t* actual, + size_t actual_len) { + ASSERT_EQ(expected_len, actual_len); + for (size_t i = 0; i < expected_len; i++) { + EXPECT_EQ(expected[i], actual[i]) << "Differ at index " << i; + } +} + +TEST(COBS, ZeroString) { + uint8_t decoded[] = {0x00}; + size_t dec_length = sizeof(decoded) / sizeof(decoded[0]); + + uint8_t encoded[] = {0x01, 0x01, 0x00}; + size_t enc_length = sizeof(encoded) / sizeof(encoded[0]); + + uint8_t enc_output[256]; + size_t enc_length_actual = Encode(decoded, dec_length, enc_output); + EXPECT_ARRAY_EQUAL(encoded, enc_length, enc_output, enc_length_actual); + + uint8_t dec_output[256]; + Decoder decoder(dec_output); + EXPECT_TRUE(decoder.Decode(encoded, enc_length)); + EXPECT_ARRAY_EQUAL(decoded, dec_length, decoder.buffer, decoder.length); +} + +TEST(COBS, EmptyString) { + uint8_t decoded[] = {}; + size_t dec_length = sizeof(decoded) / sizeof(decoded[0]); + + uint8_t encoded[] = {0x01, 0x00}; + size_t enc_length = sizeof(encoded) / sizeof(encoded[0]); + + uint8_t enc_output[256]; + size_t enc_length_actual = Encode(decoded, dec_length, enc_output); + EXPECT_ARRAY_EQUAL(encoded, enc_length, enc_output, enc_length_actual); + + uint8_t dec_output[256]; + Decoder decoder(dec_output); + EXPECT_TRUE(decoder.Decode(encoded, enc_length)); + EXPECT_ARRAY_EQUAL(decoded, dec_length, decoder.buffer, decoder.length); +} + +TEST(COBS, Short) { + uint8_t decoded[] = {0x11, 0x22, 0x00, 0x33}; + size_t dec_length = sizeof(decoded) / sizeof(decoded[0]); + + uint8_t encoded[] = {0x03, 0x11, 0x22, 0x02, 0x33, 0x00}; + size_t enc_length = sizeof(encoded) / sizeof(encoded[0]); + + uint8_t enc_output[256]; + size_t enc_length_actual = Encode(decoded, dec_length, enc_output); + EXPECT_ARRAY_EQUAL(encoded, enc_length, enc_output, enc_length_actual); + + uint8_t dec_output[256]; + Decoder decoder(dec_output); + EXPECT_TRUE(decoder.Decode(encoded, enc_length)); + EXPECT_ARRAY_EQUAL(decoded, dec_length, decoder.buffer, decoder.length); +} + +TEST(COBS, ManyZeros) { + uint8_t decoded[] = {0x11, 0x00, 0x00, 0x00}; + size_t dec_length = sizeof(decoded) / sizeof(decoded[0]); + + uint8_t encoded[] = {0x02, 0x11, 0x01, 0x01, 0x01, 0x00}; + size_t enc_length = sizeof(encoded) / sizeof(encoded[0]); + + uint8_t enc_output[256]; + size_t enc_length_actual = Encode(decoded, dec_length, enc_output); + EXPECT_ARRAY_EQUAL(encoded, enc_length, enc_output, enc_length_actual); + + uint8_t dec_output[256]; + Decoder decoder(dec_output); + EXPECT_TRUE(decoder.Decode(encoded, enc_length)); + EXPECT_ARRAY_EQUAL(decoded, dec_length, decoder.buffer, decoder.length); +} + +TEST(COBS, FFBlock) { + uint8_t decoded[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, + 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, + 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, + 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, + 0xfd, 0xfe, 0xff}; + size_t dec_length = sizeof(decoded) / sizeof(decoded[0]); + + uint8_t encoded[] = { + 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0x02, 0xff, 0x00}; + size_t enc_length = sizeof(encoded) / sizeof(encoded[0]); + + uint8_t enc_output[512]; + size_t enc_length_actual = Encode(decoded, dec_length, enc_output); + EXPECT_ARRAY_EQUAL(encoded, enc_length, enc_output, enc_length_actual); + + uint8_t dec_output[512] = {0}; + Decoder decoder(dec_output); + EXPECT_TRUE(decoder.Decode(encoded, enc_length)); + EXPECT_ARRAY_EQUAL(decoded, dec_length, decoder.buffer, decoder.length); + + // Test partial decoding + uint8_t dec_output2[512] = {0}; + decoder = Decoder(dec_output2); + size_t i; + for (i = 0; i < enc_length - 50; i += 10) { + EXPECT_FALSE(decoder.Decode(encoded + i, 10)); + } + EXPECT_TRUE(decoder.Decode(encoded + i, enc_length - i)); + EXPECT_ARRAY_EQUAL(decoded, dec_length, decoder.buffer, decoder.length); +} + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/lib/periph/periph/spi.hpp b/lib/periph/periph/spi.hpp index da02bd731..07428db6f 100644 --- a/lib/periph/periph/spi.hpp +++ b/lib/periph/periph/spi.hpp @@ -7,16 +7,17 @@ namespace macfe::periph { class SpiMaster { public: + virtual ~SpiMaster() = default; /// Transmit `length` bytes from `tx_data` - virtual void Transmit(uint8_t* tx_data, size_t length); + virtual void Transmit(uint8_t* tx_data, size_t length) {} /// Receive `length` bytes and place them into `rx_data` - virtual void Receive(uint8_t* rx_data, size_t length); + virtual void Receive(uint8_t* rx_data, size_t length) {} /// Transmit `length` bytes from `tx_data` and receive `length` bytes, /// placing them into `rx_data` virtual void TransmitReceive(uint8_t* tx_data, uint8_t* rx_data, - size_t length); + size_t length) {} }; } // namespace macfe::periph \ No newline at end of file diff --git a/projects/dashboard/platformio.ini b/projects/dashboard/platformio.ini index af8d23496..0c76b4fb2 100644 --- a/projects/dashboard/platformio.ini +++ b/projects/dashboard/platformio.ini @@ -35,6 +35,8 @@ flags = -Wall -Wno-unused-function ; having problems with unused MPU_Config() -D LV_BUILD_TEST=0 +extra_scripts = + pre:../../scripts/build/generate_git_hash.py [env:vehicle] platform = ststm32 @@ -47,6 +49,7 @@ lib_deps = ${common.lib_deps} extra_scripts = + ${common.extra_scripts} pre:../../scripts/build/add_hardfloat.py build_src_filter = @@ -79,3 +82,6 @@ build_flags = -l SDL2 lib_deps = ${common.lib_deps} + +extra_scripts = + ${common.extra_scripts} diff --git a/projects/front_controller/platformio.ini b/projects/front_controller/platformio.ini index b66343bcd..3cf3510ed 100644 --- a/projects/front_controller/platformio.ini +++ b/projects/front_controller/platformio.ini @@ -30,6 +30,8 @@ flags = -Werror -Wall -Wno-unused-function ; having problems with unused MPU_Config() +extra_scripts = + pre:../../scripts/build/generate_git_hash.py [env:vehicle] platform = ststm32 @@ -45,6 +47,7 @@ custom_freertos_heap_impl = "" custom_freertos_config_location = src/platforms/stm32-ev6/Inc/FreeRTOSConfig.h extra_scripts = + ${common.extra_scripts} pre:../../scripts/build/add_hardfloat.py build_src_filter = diff --git a/projects/front_controller/src/main.cc b/projects/front_controller/src/main.cc index 96d68686f..ef2ea1981 100644 --- a/projects/front_controller/src/main.cc +++ b/projects/front_controller/src/main.cc @@ -10,6 +10,7 @@ #include "generated/can/pt_messages.hpp" #include "generated/can/veh_bus.hpp" #include "generated/can/veh_messages.hpp" +#include "generated/githash.hpp" #include "motors/motors.hpp" #include "physical.hpp" #include "sensors/driver/driver.hpp" @@ -28,6 +29,7 @@ static const size_t STACK_SIZE_WORDS = // higher number = higher priority static const uint32_t PRIORITY_100HZ = 3; static const uint32_t PRIORITY_10HZ = 2; +static const uint32_t PRIORITY_1HZ = 1; StaticTask_t t100hz_control_block; StackType_t t100hz_buffer[STACK_SIZE_WORDS]; @@ -35,6 +37,9 @@ StackType_t t100hz_buffer[STACK_SIZE_WORDS]; StaticTask_t t10hz_control_block; StackType_t t10hz_buffer[STACK_SIZE_WORDS]; +StaticTask_t t1hz_control_block; +StackType_t t1hz_buffer[STACK_SIZE_WORDS]; + using namespace generated::can; VehBus veh_can_bus{bindings::veh_can_base}; @@ -263,6 +268,11 @@ void task_1hz(void* argument) { TickType_t wake_time = xTaskGetTickCount(); while (true) { + veh_can_bus.Send(TxFcGitHash{ + .commit = macfe::generated::GIT_HASH, + .dirty = macfe::generated::GIT_DIRTY, + }); + vTaskDelayUntil(&wake_time, pdMS_TO_TICKS(1000)); } } @@ -371,6 +381,9 @@ int main(void) { xTaskCreateStatic(task_10hz, "10HZ", STACK_SIZE_WORDS, NULL, PRIORITY_10HZ, t10hz_buffer, &t10hz_control_block); + xTaskCreateStatic(task_1hz, "1HZ", STACK_SIZE_WORDS, NULL, PRIORITY_1HZ, + t1hz_buffer, &t1hz_control_block); + vTaskStartScheduler(); while (true) continue; diff --git a/projects/lvcontroller/platformio.ini b/projects/lvcontroller/platformio.ini index 75d4f7ac3..078c43e40 100644 --- a/projects/lvcontroller/platformio.ini +++ b/projects/lvcontroller/platformio.ini @@ -29,6 +29,8 @@ flags = -Werror -Wall -Wno-unused-function ; having problems with unused MPU_Config() +extra_scripts = + pre:../../scripts/build/generate_git_hash.py [env:vehicle] platform = ststm32 @@ -47,6 +49,7 @@ build_flags = board_build.ldscript = src/platforms/stm32-ev6/STM32F767ZITX_FLASH.ld extra_scripts = + ${common.extra_scripts} pre:../../scripts/build/add_hardfloat.py [env:cli] @@ -56,4 +59,6 @@ build_src_filter = + + build_flags = - ${common.flags} \ No newline at end of file + ${common.flags} +extra_scripts = + ${common.extra_scripts} \ No newline at end of file diff --git a/projects/lvcontroller/src/lvbms/lvbms.cc b/projects/lvcontroller/src/lvbms/lvbms.cc new file mode 100644 index 000000000..231777c66 --- /dev/null +++ b/projects/lvcontroller/src/lvbms/lvbms.cc @@ -0,0 +1,170 @@ + + +#include "lvbms.hpp" + +// #include +#include +#include + +#include "periph/spi.hpp" + +namespace macfe::lv { +macfe::lv::LvBms::LvBms(macfe::periph::SpiMaster& spi) : spi_(spi) {} + +/** + * @note The ADI codes are a little more intricate, and need to be looked into + * @note ADI1, ADI2, ADV have dummy values, so this will need to change + */ + +/** + * @note everytime we utilize a read command, we should check for the CCNT + * frames, and check them with our count. This will tell us if any commands may + * have been ignored. Increment expected CCNT each time a write or command + * function has been called + **/ + +// Helper Functions ----------------------------------------------------- +std::array LvBms::splitMessage(uint16_t cmd) { + uint8_t hi = (cmd >> 8) & 0xFF; + uint8_t lo = cmd & 0xFF; + + std::array split = {hi, lo}; + return split; +} + +void LvBms::sendControlCmd(LvBms::commandCode cmdCode) { + std::array cmd = LvBms::splitMessage(uint16_t(cmdCode)); + uint8_t tx[4] = {0}; + tx[0] = cmd[0]; // HI + tx[1] = cmd[1]; // LO + // Command PEC for transmit + uint16_t pec = commandPec15(tx, 2); + tx[2] = (pec >> 8) & 0xFF; + tx[3] = pec & 0xFF; + + spi_.Transmit(tx, 4); + expectedCCNT = + (expectedCCNT % 63) + 1; // Increment CCNT for Command Functions +} + +bool LvBms::sendReadCmd(LvBms::commandCode cmdCode) { + // Read Command: Command Bytes + Command PEC + std::array cmd = LvBms::splitMessage(uint16_t(cmdCode)); + // Frame 1: Sends Read Command + uint8_t tx[12] = {0}; + uint8_t rx[12] = {0}; + + tx[0] = cmd[0]; + tx[1] = cmd[1]; + + // Compute PEC15 for command + uint16_t pec = commandPec15(tx, 2); + tx[2] = (pec >> 8) & 0xFF; + tx[3] = pec & 0xFF; + + for (int i = 4; i < 12; i++) { + tx[i] = 0xFF; + } + + spi_.TransmitReceive(tx, rx, 12); + + // Check returned PEC10 + uint16_t rxPec = (rx[10] << 8) | rx[11]; + + // CCNT and PEC must be identical + actualCCNT = (rx[10] >> 2) & 0x3F; + + uint16_t calcPec = dataPec10(&rx[4], 6); + + if (actualCCNT != expectedCCNT) { + // resend the message 5 times, but each time check if CCNT has + // increased, indicating a message sent + uint8_t prevCCNT = actualCCNT; + for (int i = 0; i < 5; i++) { + spi_.TransmitReceive(tx, rx, 12); + rxPec = (rx[10] << 8) | rx[11]; + actualCCNT = (rx[10] >> 2) & 0x3F; + calcPec = dataPec10(&rx[4], 6); + if (actualCCNT > prevCCNT) { // this means the bms successfully + // received atleast one message + break; + } + } + } + if (rxPec != calcPec) { + // resend the message once, this error should not happen often and very + // rarely twice + spi_.TransmitReceive(tx, rx, 12); + } + + // return true if dpec matches + return (rxPec == calcPec); +} + +bool LvBms::sendWriteCmd(LvBms::commandCode cmdCode, const uint8_t data[6]) { + // Write Command: 2 Command + 2 PEC15 + 6 Data + 2 PEC10 = 12 bytes + std::array cmd = LvBms::splitMessage(uint16_t(cmdCode)); + // Command bytes + uint8_t tx[12]; + tx[0] = cmd[0]; + tx[1] = cmd[1]; + + // Command PEC15 bytes + uint16_t pec = commandPec15(tx, 2); + tx[2] = (pec >> 8) & 0xFF; + tx[3] = pec & 0xFF; + + // Copy Data (6 bytes) + std::memcpy(&tx[4], data, 6); + + // Data PEC10 + uint16_t dataPec = dataPec10(&tx[4], 6); + tx[10] = (dataPec >> 8) & 0xFF; + tx[11] = dataPec & 0xFF; + + // Sends all 12 bytes + spi_.Transmit(tx, 12); + + expectedCCNT = (expectedCCNT % 63) + 1; // Increment CCNT for Write + // Commands + + return true; +} + +uint16_t LvBms::commandPec15(const uint8_t* data, int len) const { + // Packet Error Checker (CRC) + uint16_t rem = 0x0010; + uint16_t msbMask = 0x4000; // bit 14 + constexpr uint16_t poly = + 0x4599; // x^15 +x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1 + for (int i = 0; i < len; i++) { + rem ^= static_cast(data[i]) << 7; // shifts to 15 bits, so + // XOR + for (int b = 0; b < 8; b++) { + bool top = rem & msbMask; // checks for leftmost bit = 1 + rem <<= 1; + if (top) rem ^= poly; + rem &= 0x7FFF; // forces remainder to be 15 bits + } + } + + return rem; +} + +uint16_t LvBms::dataPec10(const uint8_t* data, int len) const { + uint16_t rem = 0x20; // 00000010000 (10-bit initial value) + constexpr uint16_t msbMask = 0x0200; // bit9 + constexpr uint16_t poly = 0x8F; // x^10 + x^7 + x^3 + x^2 + x^1 + 1 + for (int i = 0; i < len; i++) { + rem ^= static_cast(data[i]) << 2; + for (int b = 0; b < 8; b++) { + bool top = rem & msbMask; + rem <<= 1; + if (top) rem ^= poly; + rem &= 0x3FF; // keep CRC to 10 bits + } + } + return rem; +} + +} // namespace macfe::lv \ No newline at end of file diff --git a/projects/lvcontroller/src/lvbms/lvbms.hpp b/projects/lvcontroller/src/lvbms/lvbms.hpp new file mode 100644 index 000000000..dcdfeb3f8 --- /dev/null +++ b/projects/lvcontroller/src/lvbms/lvbms.hpp @@ -0,0 +1,85 @@ +#pragma once +#include +#include + +namespace macfe::periph { +class SpiMaster; +} + +namespace macfe::lv { + +class LvBms { + enum class commandCode : uint16_t { + SRST = 0x27, + RSTCC = 0x2E, + SNAP = 0x2D, + UNSNAP = 0x2F, + ADI1 = 0x00, + ADI2 = 0x00, + ADV = 0x00, + ADX = 0x530, + CLRI = 0x711, + CLRA = 0x714, + CLRVX = 0x712, + CLRO = 0x713, + CLRFLAG = 0x717, + RDFLAG = 0x72, // Includes ERR code, set to 1 + RDSTAT = 0x34, + RDI = 0x12, + RDVB = 0x13, + RDIACC = 0x44, + RDVBACC = 0x46, + RDIVB1 = 0x14, + RDIVB1ACC = 0x48, + RDV1A = 0xA, + RDV1B = 0x9, + RDV1C = 0x3, + RDV1D = 0xB, + RDV2A = 0x7, + RDV2B = 0xD, + RDV2C = 0x5, + RDV2D = 0x1F, + RDV2E = 0x25, + RDXA = 0x30, + RDXB = 0x31, + RDXC = 0x33, + RDOC = 0xB, + RDSID = 0x2C, + RDCFGA = 0x2, + RDCFGB = 0x26, + RDCOMM = 0x722, + WRCFGA = 0x1, + WRCFGB = 0x24, + WRCOMM = 0x721, + STCOMM = 0x723, + PLADC = 0x718, + PLI1 = 0x71C, + PLI2 = 0x71D, + PLV = 0x71E, + PLX = 0x71F, + RDALLI = 0xC, + RDALLA = 0x4C, + RDALLV = 0x35, + RDALLR = 0x11, + RDALLX = 0x51, + RDALLC = 0x10, + }; + +public: + LvBms(macfe::periph::SpiMaster&); + +private: + macfe::periph::SpiMaster& spi_; + uint8_t expectedCCNT = 0; // tracks expected command counter + uint8_t actualCCNT = 0; + + bool sendReadCmd(commandCode cmdCode); + bool sendWriteCmd(commandCode cmdCode, const uint8_t data[6]); + void sendControlCmd(commandCode cmdCode); + std::array splitMessage(uint16_t cmd); + + uint16_t commandPec15(const uint8_t* data, int len) const; + uint16_t dataPec10(const uint8_t* data, int len) const; +}; + +} // namespace macfe::lv \ No newline at end of file diff --git a/projects/lvcontroller/src/main.cc b/projects/lvcontroller/src/main.cc index 75905c745..112363a8c 100644 --- a/projects/lvcontroller/src/main.cc +++ b/projects/lvcontroller/src/main.cc @@ -7,6 +7,7 @@ #include "bindings.hpp" #include "generated/can/veh_bus.hpp" #include "generated/can/veh_messages.hpp" +#include "generated/githash.hpp" #include "periph/gpio.hpp" // LV Modules @@ -14,7 +15,9 @@ #include "brakelight/brakelight.hpp" #include "dcdc/dcdc.hpp" #include "fans/fans.hpp" +#include "lvbms/lvbms.hpp" #include "motor_controller/motor_controller.hpp" +#include "periph/spi.hpp" #include "scheduler/scheduler.hpp" #include "suspension/suspension.hpp" #include "tssi/tssi.hpp" @@ -210,6 +213,10 @@ void check_can_flash(void) { void task_1hz(void) { veh_can.Send(TxLvDbcHash(generated::can::kVehDbcHash)); + veh_can.Send(TxLvGitHash{ + .commit = macfe::generated::GIT_HASH, + .dirty = macfe::generated::GIT_DIRTY, + }); } void task_10hz(void) { @@ -247,6 +254,9 @@ int main(void) { fsm::Init(); motor_controller::Init(); + macfe::periph::SpiMaster spi; + macfe::lv::LvBms bms(spi); + scheduler::register_task(task_100hz, 10); scheduler::register_task(task_10hz, 100); scheduler::register_task(task_1hz, 1000); diff --git a/projects/tms/platformio.ini b/projects/tms/platformio.ini index ab65d4105..81728ead2 100644 --- a/projects/tms/platformio.ini +++ b/projects/tms/platformio.ini @@ -29,6 +29,8 @@ flags = -Werror -Wall -Wno-unused-function ; having problems with unused MPU_Config() +extra_scripts = + pre:../../scripts/build/generate_git_hash.py [env:vehicle] platform = ststm32 @@ -52,4 +54,5 @@ custom_freertos_heap_impl = "" custom_freertos_config_location = src/platforms/stm32f767/Inc/FreeRTOSConfig.h extra_scripts = + ${common.extra_scripts} pre:../../scripts/build/add_hardfloat.py diff --git a/projects/tms/src/main.cc b/projects/tms/src/main.cc index 888a8a1b0..312b51755 100644 --- a/projects/tms/src/main.cc +++ b/projects/tms/src/main.cc @@ -8,6 +8,7 @@ #include "fan_controller/fan_controller.hpp" #include "generated/can/veh_bus.hpp" #include "generated/can/veh_messages.hpp" +#include "generated/githash.hpp" #include "periph/gpio.hpp" #include "temp_sensor/temp_sensor.hpp" @@ -16,11 +17,15 @@ #include "task.h" static const size_t STACK_SIZE_WORDS = 2048 * 16; -static const uint32_t PRIORITY_10HZ = 1; +static const uint32_t PRIORITY_10HZ = 2; +static const uint32_t PRIORITY_1HZ = 1; StaticTask_t t10hz_control_block; StackType_t t10hz_buffer[STACK_SIZE_WORDS]; +StaticTask_t t1hz_control_block; +StackType_t t1hz_buffer[STACK_SIZE_WORDS]; + using namespace generated::can; etl::array temp_sensors{ @@ -108,6 +113,22 @@ void Update(float update_period_ms) { toggle = !toggle; } +void task_1hz(void* argument) { + (void)argument; + + const uint32_t kUpdatePeriodMs = 1000; + TickType_t wake_time = xTaskGetTickCount(); + + while (true) { + veh_can_bus.Send(TxTmsGitHash{ + .commit = macfe::generated::GIT_HASH, + .dirty = macfe::generated::GIT_DIRTY, + }); + + vTaskDelayUntil(&wake_time, pdMS_TO_TICKS(kUpdatePeriodMs)); + } +} + void task_10hz(void* argument) { (void)argument; @@ -133,6 +154,9 @@ int main(void) { xTaskCreateStatic(task_10hz, "10HZ", STACK_SIZE_WORDS, NULL, PRIORITY_10HZ, t10hz_buffer, &t10hz_control_block); + xTaskCreateStatic(task_1hz, "1HZ", STACK_SIZE_WORDS, NULL, PRIORITY_1HZ, + t1hz_buffer, &t1hz_control_block); + vTaskStartScheduler(); while (true) continue; diff --git a/projects/veh.dbc b/projects/veh.dbc index 42969db86..d958c54df 100644 --- a/projects/veh.dbc +++ b/projects/veh.dbc @@ -167,7 +167,7 @@ BO_ 410 LvGitHash: 5 LVC SG_ Commit : 0|32@1+ (1,0) [0|0] "" RPI SG_ Dirty : 32|1@1+ (1,0) [0|0] "" RPI -BO_ 420 TmsGitHash: 5 LVC +BO_ 420 TmsGitHash: 5 TMS SG_ Commit : 0|32@1+ (1,0) [0|0] "" RPI SG_ Dirty : 32|1@1+ (1,0) [0|0] "" RPI diff --git a/scripts/build/generate_git_hash.py b/scripts/build/generate_git_hash.py new file mode 100644 index 000000000..16caef778 --- /dev/null +++ b/scripts/build/generate_git_hash.py @@ -0,0 +1,46 @@ +Import("env") + +import subprocess +from pathlib import Path + +include_path = Path(env.subst("$PROJECT_INCLUDE_DIR")) / "generated" + +def get_git_hash(): + # Executed command line command to retrieve a 7 character git hash + git_hash = subprocess.check_output( + ["git", "rev-parse", "--short=7", "HEAD"], + cwd=include_path + ).decode().strip() + + # Create the is_dirty variable to check if the repository has uncommitted changes + dirty_output = subprocess.check_output( + ["git", "status", "--porcelain"], + cwd=include_path + ).decode().strip() + is_dirty = bool(dirty_output) + + git_hash_int = int(git_hash, 16) & 0x0FFFFFFF + return git_hash_int, is_dirty + +def generate_git_hash_file(): + # Create the output directory path if it doesn't exist + include_path.mkdir(parents=True, exist_ok=True) + header_path = include_path / "githash.hpp" + + git_hash, is_dirty = get_git_hash() + + header_content = f"""#pragma once + +#include + +namespace macfe::generated {{ + +const uint32_t GIT_HASH = 0x{git_hash:08x}; +const bool GIT_DIRTY = {'true' if is_dirty else 'false'}; + +}} // namespace macfe::generated +""" + + header_path.write_text(header_content) + +generate_git_hash_file() \ No newline at end of file diff --git a/scripts/static_analysis/static_analysis.sh b/scripts/static_analysis/static_analysis.sh index cce99cc3d..0a85e0bd0 100644 --- a/scripts/static_analysis/static_analysis.sh +++ b/scripts/static_analysis/static_analysis.sh @@ -1,13 +1,17 @@ #!/bin/bash -git ls-files -z 'projects/**/platformio.ini' | while IFS= read -r -d '' ini; do +git ls-files -z 'projects/**/platformio.ini' | grep -zv 'projects/demo/' | while IFS= read -r -d '' ini; do dir=$(dirname "$ini") echo "============================================================" echo "==== Performing Static Code Analysis on $dir" echo "============================================================" - if ! git diff --quiet --staged --exit-code "$dir"; then + SUBDIRS=("$dir/src" "$dir/include") + EXCLUDE_PREFIX="stm*" + + # Check for changes in $dir/src and $dir/include, excluding files within $dir/src/platforms/stm* + if git diff --name-only --staged -- "${SUBDIRS[@]}" ":!$dir/src/platforms/$EXCLUDE_PREFIX" | grep -q .; then echo "Changes detected in $dir → running pio check" - pio check --project-dir "$dir" --fail-on-defect=high || exit 1 + pio check --project-dir "$dir" --flags "--checks=bugprone-*,clang-analyzer-*,google-*,performance-*" --fail-on-defect=high || exit 1 else echo "No changes in $dir → skipping" fi