Files
radar_system/python_app/tests/test_cart_firmware.py
T
2026-10-14 12:04:45 +03:00

90 lines
3.6 KiB
Python

"""Run firmware logic on the host; timing and physical I/O still need the board."""
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
PROJECT = Path(__file__).resolve().parents[2]
FIRMWARE = PROJECT / 'device_firmware/cart_firmware/src/main.cpp'
ARDUINO_STUB = r'''
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
inline uint32_t test_time = 0;
inline int transmissions = 0;
inline uint32_t millis() { return test_time; }
inline int analogRead(uint32_t pin) { return pin == 0 ? 2014 : 1981; }
inline void analogReadResolution(int) {}
inline void pinMode(uint32_t, int) {}
inline float constrain(float v, float lo, float hi) { return std::clamp(v, lo, hi); }
struct SerialStub {
void begin(int) {}
template<class T> void print(T, int = 0) {}
template<class T> void println(T) {}
};
inline SerialStub Serial;
struct GPIO_InitTypeDef { int Pin, Mode, Pull, Speed, Alternate; };
struct UART_HandleTypeDef {
int Instance;
struct { int BaudRate, WordLength, StopBits, Parity, Mode, HwFlowCtl, OverSampling; } Init;
struct { int AdvFeatureInit, TxPinLevelInvert; } AdvancedInit;
};
enum { PA0, PA1, GPIO_PIN_9, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW,
GPIO_AF7_USART1, GPIOA, USART1, UART_WORDLENGTH_9B, UART_STOPBITS_2,
UART_PARITY_EVEN, UART_MODE_TX, UART_HWCONTROL_NONE, UART_OVERSAMPLING_16,
UART_ADVFEATURE_TXINVERT_INIT, UART_ADVFEATURE_TXINV_ENABLE, INPUT_ANALOG };
inline void __HAL_RCC_GPIOA_CLK_ENABLE() {}
inline void __HAL_RCC_USART1_CLK_ENABLE() {}
inline void HAL_GPIO_Init(int, GPIO_InitTypeDef*) {}
inline void HAL_UART_Init(UART_HandleTypeDef*) {}
inline void HAL_UART_Transmit(UART_HandleTypeDef*, uint8_t*, int, int) { ++transmissions; }
'''
@unittest.skipUnless(shutil.which('g++'), 'requires a host C++ compiler')
@unittest.skipUnless(FIRMWARE.is_file(), 'firmware sources are not deployed to hosts')
class CartFirmwareTest(unittest.TestCase):
def test_schedule_rollover_and_sbus_encoding(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / 'Arduino.h').write_text(ARDUINO_STUB)
source = root / 'check.cpp'
source.write_text(f'#include "{FIRMWARE}"\n' + r'''
#include <cassert>
int main() {
test_time = UINT32_MAX - 100;
loop(); assert(transmissions == 1);
test_time = 1;
loop(); assert(transmissions == 2);
test_time += 49;
loop(); assert(transmissions == 2);
++test_time;
loop(); assert(transmissions == 3);
assert(axisNorm(X_MID, X_FWD, X_MID, X_BACK) == 0);
assert(axisNorm(X_FWD, X_FWD, X_MID, X_BACK) == 1);
assert(driveToSbus(0, CH2_SPAN, CH2_DB, POWER_FWD, POWER_BACK) == SBUS_MID);
uint16_t channels[16];
for (int n = 0; n < 16; ++n) channels[n] = n * 127;
uint8_t packed[25]; sbusPack(packed, channels);
assert(packed[0] == 0x0f && packed[23] == 0 && packed[24] == 0);
for (int n = 0; n < 16; ++n) {
uint16_t value = 0;
for (int b = 0; b < 11; ++b) {
const int bit = n * 11 + b;
value |= ((packed[1 + bit / 8] >> (bit % 8)) & 1) << b;
}
assert(value == channels[n]);
}
}
''')
binary = root / 'check'
built = subprocess.run(['g++', '-std=c++20', '-Wall', '-Wextra', '-Werror', f'-I{root}',
str(source), '-o', str(binary)], capture_output=True, text=True, timeout=15)
self.assertEqual(built.returncode, 0, built.stderr)
subprocess.run([str(binary)], check=True, timeout=5)