89 lines
3.5 KiB
Python
89 lines
3.5 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]
|
|
|
|
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')
|
|
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 <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)
|