st-4-esp32/test/test_controller/test_controller.cpp
Ryan Malloy 71e5484507 Add native tests, ASCOM Alpaca API, and README
Native test suite (61 tests, 5 suites) with thin mock layer for
Arduino/FreeRTOS/esp_timer enabling host-side testing without hardware.

ASCOM Alpaca REST API on port 32323 with UDP discovery, implementing
Telescope v3 interface for N.I.N.A., PHD2, and compatible software.
Follows existing ST4WiFi conditional compilation pattern.

README documents wiring, all three protocols (serial, WebSocket, Alpaca),
pin/rate configuration, and build instructions.
2026-02-18 13:40:34 -07:00

160 lines
5.2 KiB
C++

// ST4Controller unit tests
#include <unity.h>
#include "mock_state.h"
#include "ST4Controller.h"
static ST4Controller* ctrl;
void setUp() {
MockState::reset();
ctrl = new ST4Controller();
ctrl->begin();
}
void tearDown() {
delete ctrl;
ctrl = nullptr;
}
void test_not_connected_guard() {
// Before connect(), move() should be a no-op
ctrl->move(ST4AxisId::RA, ST4Direction::PLUS);
TEST_ASSERT_EQUAL(ST4Direction::STOP, ctrl->axisDirection(ST4AxisId::RA));
TEST_ASSERT_FALSE(ctrl->axisActive(ST4AxisId::RA));
}
void test_connect_sets_led_high() {
ctrl->connect();
TEST_ASSERT_EQUAL(HIGH, MockState::gpioStates[ST4_PIN_LED]);
TEST_ASSERT_TRUE(ctrl->isConnected());
}
void test_disconnect_sets_led_low() {
ctrl->connect();
ctrl->disconnect();
TEST_ASSERT_EQUAL(LOW, MockState::gpioStates[ST4_PIN_LED]);
TEST_ASSERT_FALSE(ctrl->isConnected());
}
void test_move_ra_plus() {
ctrl->connect();
ctrl->move(ST4AxisId::RA, ST4Direction::PLUS);
TEST_ASSERT_EQUAL(ST4Direction::PLUS, ctrl->axisDirection(ST4AxisId::RA));
TEST_ASSERT_TRUE(ctrl->axisActive(ST4AxisId::RA));
}
void test_move_dec_minus() {
ctrl->connect();
ctrl->move(ST4AxisId::DECLINATION, ST4Direction::MINUS);
TEST_ASSERT_EQUAL(ST4Direction::MINUS, ctrl->axisDirection(ST4AxisId::DECLINATION));
}
void test_rate_calculation_ra_plus() {
ctrl->connect();
ctrl->move(ST4AxisId::RA, ST4Direction::PLUS);
// RA PLUS: 9 * RA_PER_SECOND = 9 / 3600
MockState::advanceTime(1000000); // 1 second
double expected = 9.0 * ST4Constants::RA_PER_SECOND;
double pos = ctrl->position(ST4AxisId::RA);
TEST_ASSERT_DOUBLE_WITHIN(1e-9, expected, pos);
}
void test_rate_calculation_ra_minus() {
ctrl->connect();
ctrl->move(ST4AxisId::RA, ST4Direction::MINUS);
MockState::advanceTime(1000000);
// RA MINUS: -7 * RA_PER_SECOND
double expected = -7.0 * ST4Constants::RA_PER_SECOND;
double pos = ctrl->position(ST4AxisId::RA);
TEST_ASSERT_DOUBLE_WITHIN(1e-9, expected, pos);
}
void test_rate_calculation_dec_plus() {
ctrl->connect();
ctrl->move(ST4AxisId::DECLINATION, ST4Direction::PLUS);
MockState::advanceTime(1000000);
double expected = 8.0 * ST4Constants::DEGREES_PER_SECOND;
double pos = ctrl->position(ST4AxisId::DECLINATION);
TEST_ASSERT_DOUBLE_WITHIN(1e-9, expected, pos);
}
void test_state_snapshot() {
ctrl->connect();
ctrl->move(ST4AxisId::RA, ST4Direction::PLUS);
ST4State s = ctrl->state();
TEST_ASSERT_TRUE(s.connected);
TEST_ASSERT_TRUE(s.ra.active);
TEST_ASSERT_EQUAL(ST4Direction::PLUS, s.ra.direction);
TEST_ASSERT_FALSE(s.dec.active);
TEST_ASSERT_EQUAL(ST4Direction::STOP, s.dec.direction);
}
void test_sync() {
ctrl->connect();
ctrl->sync(10.0, 20.0);
TEST_ASSERT_DOUBLE_WITHIN(1e-9, 10.0, ctrl->position(ST4AxisId::RA));
TEST_ASSERT_DOUBLE_WITHIN(1e-9, 20.0, ctrl->position(ST4AxisId::DECLINATION));
}
void test_stop_all() {
ctrl->connect();
ctrl->move(ST4AxisId::RA, ST4Direction::PLUS);
ctrl->move(ST4AxisId::DECLINATION, ST4Direction::MINUS);
ctrl->stopAll();
TEST_ASSERT_EQUAL(ST4Direction::STOP, ctrl->axisDirection(ST4AxisId::RA));
TEST_ASSERT_EQUAL(ST4Direction::STOP, ctrl->axisDirection(ST4AxisId::DECLINATION));
TEST_ASSERT_FALSE(ctrl->axisActive(ST4AxisId::RA));
TEST_ASSERT_FALSE(ctrl->axisActive(ST4AxisId::DECLINATION));
}
void test_stop_axis() {
ctrl->connect();
ctrl->move(ST4AxisId::RA, ST4Direction::PLUS);
ctrl->move(ST4AxisId::DECLINATION, ST4Direction::MINUS);
ctrl->stopAxis(ST4AxisId::RA);
TEST_ASSERT_EQUAL(ST4Direction::STOP, ctrl->axisDirection(ST4AxisId::RA));
TEST_ASSERT_EQUAL(ST4Direction::MINUS, ctrl->axisDirection(ST4AxisId::DECLINATION));
}
void test_pulse_guide_not_connected() {
bool result = ctrl->pulseGuide(ST4AxisId::RA, ST4Direction::PLUS, 500);
TEST_ASSERT_FALSE(result);
}
void test_pulse_guide_connected() {
ctrl->connect();
bool result = ctrl->pulseGuide(ST4AxisId::RA, ST4Direction::PLUS, 500);
TEST_ASSERT_TRUE(result);
}
void test_set_rates() {
ST4RateConfig customRates = {4.0, 3.0, 2.0, 2.0};
ctrl->setRates(customRates);
ctrl->connect();
ctrl->move(ST4AxisId::RA, ST4Direction::PLUS);
MockState::advanceTime(1000000);
double expected = 4.0 * ST4Constants::RA_PER_SECOND;
double pos = ctrl->position(ST4AxisId::RA);
TEST_ASSERT_DOUBLE_WITHIN(1e-9, expected, pos);
}
int main() {
UNITY_BEGIN();
RUN_TEST(test_not_connected_guard);
RUN_TEST(test_connect_sets_led_high);
RUN_TEST(test_disconnect_sets_led_low);
RUN_TEST(test_move_ra_plus);
RUN_TEST(test_move_dec_minus);
RUN_TEST(test_rate_calculation_ra_plus);
RUN_TEST(test_rate_calculation_ra_minus);
RUN_TEST(test_rate_calculation_dec_plus);
RUN_TEST(test_state_snapshot);
RUN_TEST(test_sync);
RUN_TEST(test_stop_all);
RUN_TEST(test_stop_axis);
RUN_TEST(test_pulse_guide_not_connected);
RUN_TEST(test_pulse_guide_connected);
RUN_TEST(test_set_rates);
return UNITY_END();
}