Instrument Neutral Distributed Interface INDI 2.2.5
test_rotator_limits.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2026 INDI Contributors. All rights reserved.
3 This library is free software; you can redistribute it and/or
4 modify it under the terms of the GNU Library General Public
5 License version 2 as published by the Free Software Foundation.
6
7 This library is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 Library General Public License for more details.
11
12 You should have received a copy of the GNU Library General Public License
13 along with this library; see the file COPYING.LIB. If not, write to
14 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
15 Boston, MA 02110-1301, USA.
16*******************************************************************************/
17
32#include <gtest/gtest.h>
33#include <cmath>
34#include <algorithm>
35
36// ---------------------------------------------------------------------------
37// Pure reimplementation of the formula from indirotatorinterface.cpp so the
38// tests are self-contained and do not require linking the full INDI library.
39// ---------------------------------------------------------------------------
40static bool rotatorExceedsLimit(double target, double reference, double limit)
41{
42 if (limit <= 0.0)
43 return false; // limits disabled
44
45 double delta = target - reference;
46 while (delta > 180.0) delta -= 360.0;
47 while (delta < -180.0) delta += 360.0;
48 return std::abs(delta) > limit / 2.0;
49}
50
51// ===========================================================================
52// Test suite: disabled limits (limit == 0)
53// ===========================================================================
54TEST(RotatorLimits, DisabledLimits_NothingBlocked)
55{
56 // limit == 0 means no restriction regardless of target or reference
57 EXPECT_FALSE(rotatorExceedsLimit(0.0, 0.0, 0.0));
58 EXPECT_FALSE(rotatorExceedsLimit(180.0, 0.0, 0.0));
59 EXPECT_FALSE(rotatorExceedsLimit(359.0, 0.0, 0.0));
60 EXPECT_FALSE(rotatorExceedsLimit(270.0, 45.0, 0.0));
61}
62
63// ===========================================================================
64// Test suite: full range (limit == 360) — nothing should be blocked
65// ===========================================================================
66TEST(RotatorLimits, FullRange_NothingBlocked)
67{
68 // With limit=360, the safe window is ±180° — every target is within range.
69 EXPECT_FALSE(rotatorExceedsLimit(181.0, 0.0, 360.0));
70 EXPECT_FALSE(rotatorExceedsLimit(340.0, 0.0, 360.0));
71 EXPECT_FALSE(rotatorExceedsLimit(350.0, 0.0, 360.0));
72 EXPECT_FALSE(rotatorExceedsLimit(358.0, 0.0, 360.0));
73 EXPECT_FALSE(rotatorExceedsLimit(359.0, 0.0, 360.0));
74 EXPECT_FALSE(rotatorExceedsLimit(0.0, 0.0, 360.0));
75 EXPECT_FALSE(rotatorExceedsLimit(90.0, 0.0, 360.0));
76 EXPECT_FALSE(rotatorExceedsLimit(180.0, 0.0, 360.0));
77}
78
79// ===========================================================================
80// Test suite: cable wrap scenario — the fundamental correctness test.
81// With reference=0, limit=360 (±180°), incremental moves must not bypass
82// the limit by "walking" around the circle.
83// ===========================================================================
84TEST(RotatorLimits, CableWrap_IncrementalMovesCannotBypassLimit)
85{
86 // reference=0, limit=190 (±95°)
87 // Move 1: 0 → 80° (delta=80, within ±95) — allowed
88 EXPECT_FALSE(rotatorExceedsLimit(80.0, 0.0, 190.0));
89 // Move 2: 80 → 170° (delta from reference=0 is 170, exceeds 95) — BLOCKED
90 // (Even though it's only 90° from current position 80°, it's 170° from reference)
91 EXPECT_TRUE (rotatorExceedsLimit(170.0, 0.0, 190.0));
92 // Move 3: 0 → 350° (delta = -10°, within ±95) — allowed (350° is 10° CCW from 0)
93 EXPECT_FALSE(rotatorExceedsLimit(350.0, 0.0, 190.0));
94 // Move 4: 350 → 265° — from reference 0°, 265° = -95° (exactly on boundary) — allowed
95 EXPECT_FALSE(rotatorExceedsLimit(265.0, 0.0, 190.0));
96 // Move 5: 350 → 264° — from reference 0°, 264° = -96° (just outside) — BLOCKED
97 EXPECT_TRUE (rotatorExceedsLimit(264.0, 0.0, 190.0));
98}
99
100// ===========================================================================
101// Test suite: typical limit, reference at 0°
102// ===========================================================================
103TEST(RotatorLimits, Reference0_Limit180_SafeRange_270to90)
104{
105 // reference=0, limit=180 → safe: 270°–360° and 0°–90° (i.e. within ±90° of 0°)
106 EXPECT_FALSE(rotatorExceedsLimit(0.0, 0.0, 180.0)); // at reference
107 EXPECT_FALSE(rotatorExceedsLimit(90.0, 0.0, 180.0)); // boundary CW
108 EXPECT_FALSE(rotatorExceedsLimit(270.0, 0.0, 180.0)); // boundary CCW (wrap)
109 EXPECT_TRUE (rotatorExceedsLimit(91.0, 0.0, 180.0)); // just outside CW
110 EXPECT_TRUE (rotatorExceedsLimit(269.0, 0.0, 180.0)); // just outside CCW
111 EXPECT_TRUE (rotatorExceedsLimit(180.0, 0.0, 180.0)); // antipodal — blocked
112}
113
114// ===========================================================================
115// Test suite: typical limit, reference at 180°
116// ===========================================================================
117TEST(RotatorLimits, Reference180_Limit90_SafeRange_135to225)
118{
119 // reference=180, limit=90 → safe: 135°–225° (±45° around 180°)
120 EXPECT_FALSE(rotatorExceedsLimit(180.0, 180.0, 90.0)); // at reference
121 EXPECT_FALSE(rotatorExceedsLimit(135.0, 180.0, 90.0)); // boundary CCW
122 EXPECT_FALSE(rotatorExceedsLimit(225.0, 180.0, 90.0)); // boundary CW
123 EXPECT_TRUE (rotatorExceedsLimit(134.0, 180.0, 90.0)); // just outside CCW
124 EXPECT_TRUE (rotatorExceedsLimit(226.0, 180.0, 90.0)); // just outside CW
125 EXPECT_TRUE (rotatorExceedsLimit(0.0, 180.0, 90.0)); // far away
126 EXPECT_TRUE (rotatorExceedsLimit(360.0, 180.0, 90.0)); // 360° == 0°, far away
127}
128
129// ===========================================================================
130// Test suite: wrap-around — reference near 360°
131// ===========================================================================
132TEST(RotatorLimits, Reference350_Limit90_SafeRange_305to35)
133{
134 // reference=350, limit=90 → safe: 305°–35° (spans the 0°/360° boundary)
135 EXPECT_FALSE(rotatorExceedsLimit(350.0, 350.0, 90.0)); // at reference
136 EXPECT_FALSE(rotatorExceedsLimit(305.0, 350.0, 90.0)); // boundary CCW
137 EXPECT_FALSE(rotatorExceedsLimit(35.0, 350.0, 90.0)); // boundary CW (wrapped)
138 EXPECT_FALSE(rotatorExceedsLimit(10.0, 350.0, 90.0)); // inside, past 0°
139 EXPECT_FALSE(rotatorExceedsLimit(0.0, 350.0, 90.0)); // inside, exactly 0°
140 EXPECT_TRUE (rotatorExceedsLimit(304.0, 350.0, 90.0)); // just outside CCW
141 EXPECT_TRUE (rotatorExceedsLimit(36.0, 350.0, 90.0)); // just outside CW
142 EXPECT_TRUE (rotatorExceedsLimit(180.0, 350.0, 90.0)); // far away
143}
144
145// ===========================================================================
146// Test suite: small limit — only tiny window allowed
147// ===========================================================================
148TEST(RotatorLimits, SmallLimit_TightWindow)
149{
150 // reference=90, limit=10 → safe: 85°–95°
151 EXPECT_FALSE(rotatorExceedsLimit(90.0, 90.0, 10.0)); // at reference
152 EXPECT_FALSE(rotatorExceedsLimit(85.0, 90.0, 10.0)); // boundary
153 EXPECT_FALSE(rotatorExceedsLimit(95.0, 90.0, 10.0)); // boundary
154 EXPECT_TRUE (rotatorExceedsLimit(84.0, 90.0, 10.0)); // outside
155 EXPECT_TRUE (rotatorExceedsLimit(96.0, 90.0, 10.0)); // outside
156}
157
158// ===========================================================================
159// Test suite: user-reported bug scenario
160// limit=190, reference=0, rotator moves to 10, then 90, then commanded to 265.24
161// With the fixed reference approach, 265.24° from reference 0° is -94.76° — allowed.
162// But 264° from reference 0° is -96° — blocked.
163// ===========================================================================
164TEST(RotatorLimits, BugReport_Limit190_Reference0)
165{
166 // reference=0, limit=190 (±95°)
167 // 265.24° from 0° = -94.76° (within ±95°) — allowed
168 EXPECT_FALSE(rotatorExceedsLimit(265.24, 0.0, 190.0));
169 // 264° from 0° = -96° (outside ±95°) — blocked
170 EXPECT_TRUE (rotatorExceedsLimit(264.0, 0.0, 190.0));
171 // The intermediate moves (10°, 90°) don't change the reference — still 0°
172 EXPECT_FALSE(rotatorExceedsLimit(10.0, 0.0, 190.0));
173 EXPECT_FALSE(rotatorExceedsLimit(90.0, 0.0, 190.0));
174}
TEST(RotatorLimits, DisabledLimits_NothingBlocked)