"""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] ARDUINO_STUB = r''' #pragma once #include #include #include #include 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 void print(T, int = 0) {} template 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') 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' firmware = PROJECT / 'device_firmware/cart_firmware/src/main.cpp' source.write_text(f'#include "{firmware}"\n' + r''' #include 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)