Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/Declination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,21 @@ Declination Declination::FromSeconds(long seconds)

const char *Declination::formatString(char *targetBuffer, const char *format, long *) const
{
long secs
= inNorthernHemisphere ? (arcSecondsPerHemisphere / 2) - labs(totalSeconds) : -(arcSecondsPerHemisphere / 2) + labs(totalSeconds);
long secs = core::Declination::axisToCelestialSeconds(totalSeconds, inNorthernHemisphere);
return core::DayTime::formatString(targetBuffer, format, &secs);
}

void Declination::getCelestialDegrees(int &deg, int &min, int &sec) const
{
const long celestialSecs = core::Declination::axisToCelestialSeconds(totalSeconds, inNorthernHemisphere);
core::DayTime::splitSeconds(celestialSecs, deg, min, sec);
}

Declination Declination::fromCelestialDegrees(int deg, int min, int sec)
{
const long wireSecs = ((60L * deg) + min) * 60L + sec;
Declination result;
result.totalSeconds = core::Declination::celestialToAxisSeconds(wireSecs, inNorthernHemisphere);
result.checkHours();
return result;
}
8 changes: 8 additions & 0 deletions src/Declination.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ class Declination : public core::Declination
virtual const char *ToString() const;
virtual const char *formatString(char *targetBuffer, const char *format, long *pSeconds = nullptr) const;

// Split into celestial (hemisphere-corrected) degree components, as used
// on the Meade wire: signed degrees, unsigned minutes/seconds.
void getCelestialDegrees(int &deg, int &min, int &sec) const;

// Build from celestial (Meade wire) components: signed degrees, unsigned
// minutes/seconds.
static Declination fromCelestialDegrees(int deg, int min, int sec);

const char *ToDisplayString(char sep1, char sep2) const;

static Declination ParseFromMeade(String const &s);
Expand Down
20 changes: 15 additions & 5 deletions src/MeadeCommandProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,22 @@ meade::RaCoordinate raFrom(const DayTime &t)

meade::DecCoordinate decFrom(const Declination &d)
{
// The mount stores DEC as an axis coordinate (0 = pole above the mount);
// Meade clients expect celestial declination. Apply the hemisphere
// correction before splitting into components.
int deg, min, sec;
d.getCelestialDegrees(deg, min, sec);
return meade::DecCoordinate {
static_cast<int16_t>(d.getHours()),
static_cast<uint8_t>(d.getMinutes()),
static_cast<uint8_t>(d.getSeconds()),
static_cast<int16_t>(deg),
static_cast<uint8_t>(min),
static_cast<uint8_t>(sec),
};
}

Declination decFromWire(meade::DecCoordinate const &d)
{
return Declination::fromCelestialDegrees(d.degrees, d.minutes, d.seconds);
}
} // namespace

meade::RaCoordinate MeadeCommandProcessor::onCurrentRa()
Expand Down Expand Up @@ -248,7 +258,7 @@ void MeadeCommandProcessor::onSyncToTarget()
/////////////////////////////
bool MeadeCommandProcessor::onSetTargetDec(meade::DecCoordinate dec)
{
_mount->targetDEC() = Declination(static_cast<int>(dec.degrees), static_cast<int>(dec.minutes), static_cast<int>(dec.seconds));
_mount->targetDEC() = decFromWire(dec);
LOG(DEBUG_MEADE, "[MEADE]: SetInfo: Received Target DEC: %s", _mount->targetDEC().ToString());
return true;
}
Expand Down Expand Up @@ -282,7 +292,7 @@ bool MeadeCommandProcessor::onSetHourAngle(uint8_t hours, uint8_t minutes)

bool MeadeCommandProcessor::onSyncCoordinates(meade::DecCoordinate dec, meade::RaCoordinate ra)
{
Declination decValue(static_cast<int>(dec.degrees), static_cast<int>(dec.minutes), static_cast<int>(dec.seconds));
Declination decValue = decFromWire(dec);
DayTime raValue(static_cast<int>(ra.hours), static_cast<int>(ra.minutes), static_cast<int>(ra.seconds));
_mount->syncPosition(raValue, decValue);
return true;
Expand Down
10 changes: 10 additions & 0 deletions src/core/types/DayTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ void DayTime::getTime(int &h, int &m, int &s) const
h *= sign(totalSeconds);
}

void DayTime::splitSeconds(long secs, int &h, int &m, int &s)
{
long remainder = labs(secs);
h = static_cast<int>(remainder / 3600L);
remainder = remainder - (h * 3600L);
m = static_cast<int>(remainder / 60L);
s = static_cast<int>(remainder - (m * 60L));
h *= sign(secs);
}

void DayTime::set(int h, int m, int s)
{
DayTime dt(h, m, s);
Expand Down
4 changes: 4 additions & 0 deletions src/core/types/DayTime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class DayTime
long getTotalSeconds() const;

void getTime(int &h, int &m, int &s) const;

// Split signed seconds into (signed) hours plus unsigned minutes/seconds.
static void splitSeconds(long secs, int &h, int &m, int &s);

virtual void set(int h, int m, int s);
void set(const DayTime &other);

Expand Down
26 changes: 26 additions & 0 deletions src/core/types/Declination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,30 @@ void Declination::checkHours()
}
}

Declination Declination::fromTotalSeconds(long totalSeconds)
{
Declination d;
d.totalSeconds = totalSeconds;
d.checkHours();
return d;
}

long Declination::axisToCelestialSeconds(long axisSeconds, bool northernHemisphere)
{
// Northern: celestial = 90 - |axis|. Southern: celestial = |axis| - 90.
// Mirrors the hemisphere tables in src/Declination.cpp.
const long hemiArcsecs = arcSecondsPerHemisphere / 2;
return northernHemisphere ? hemiArcsecs - labs(axisSeconds) : labs(axisSeconds) - hemiArcsecs;
}

long Declination::celestialToAxisSeconds(long celestialSeconds, bool northernHemisphere)
{
// Inverse of axisToCelestialSeconds on the mount's home-side branch:
// northern mounts keep the axis non-negative, southern non-positive.
// Note this intentionally does NOT take labs() of the input — celestial
// values on the far side of the equator push the axis past 90 degrees.
const long hemiArcsecs = arcSecondsPerHemisphere / 2;
return northernHemisphere ? hemiArcsecs - celestialSeconds : -hemiArcsecs - celestialSeconds;
}

} // namespace core
10 changes: 10 additions & 0 deletions src/core/types/Declination.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ class Declination : public DayTime
// Get total degrees (-180..180)
float getTotalDegrees() const;

// Hemisphere-aware conversions between the mount-axis coordinate (0 at the
// pole above the mount, +/-180 at the opposite pole) and celestial
// declination arc-seconds (-90 at the south celestial pole, +90 at the
// north celestial pole).
static long axisToCelestialSeconds(long axisSeconds, bool northernHemisphere);
static long celestialToAxisSeconds(long celestialSeconds, bool northernHemisphere);

// Construct from total (axis) seconds directly, avoiding float rounding.
static Declination fromTotalSeconds(long totalSeconds);

protected:
virtual void checkHours() override;

Expand Down
91 changes: 91 additions & 0 deletions unit_tests/test_core/types/test_declination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,94 @@ TEST(DeclinationTest, GetTotalDegrees)
Declination dec(30, 0, 0);
EXPECT_FLOAT_EQ(30.0f, dec.getTotalDegrees());
}

// ---------------------------------------------------------------------------
// Hemisphere conversion (mount-axis coordinate <-> celestial declination).
//
// The mount stores DEC as an axis coordinate: 0 at the pole above the mount,
// +/-180 at the opposite pole. Meade clients speak celestial declination.
// These tests pin the exact relationship documented in src/Declination.cpp.
// ---------------------------------------------------------------------------

TEST(DeclinationTest, AxisToCelestialNorthernPole)
{
// Axis 0 is the north celestial pole in the northern hemisphere.
EXPECT_EQ(90L * 3600L, Declination::axisToCelestialSeconds(0, true));
}

TEST(DeclinationTest, AxisToCelestialNorthernEquator)
{
// Both equator crossings (+90 and -90 axis) are celestial 0.
EXPECT_EQ(0L, Declination::axisToCelestialSeconds(90L * 3600L, true));
EXPECT_EQ(0L, Declination::axisToCelestialSeconds(-90L * 3600L, true));
}

TEST(DeclinationTest, AxisToCelestialNorthernSouthPole)
{
EXPECT_EQ(-90L * 3600L, Declination::axisToCelestialSeconds(180L * 3600L, true));
EXPECT_EQ(-90L * 3600L, Declination::axisToCelestialSeconds(-180L * 3600L, true));
}

TEST(DeclinationTest, AxisToCelestialSouthernPole)
{
// Axis 0 is the south celestial pole in the southern hemisphere.
EXPECT_EQ(-90L * 3600L, Declination::axisToCelestialSeconds(0, false));
}

TEST(DeclinationTest, AxisToCelestialSouthernEquator)
{
EXPECT_EQ(0L, Declination::axisToCelestialSeconds(90L * 3600L, false));
EXPECT_EQ(0L, Declination::axisToCelestialSeconds(-90L * 3600L, false));
}

TEST(DeclinationTest, AxisToCelestialSouthernNorthPole)
{
EXPECT_EQ(90L * 3600L, Declination::axisToCelestialSeconds(180L * 3600L, false));
EXPECT_EQ(90L * 3600L, Declination::axisToCelestialSeconds(-180L * 3600L, false));
}

TEST(DeclinationTest, AxisToCelestialNorthernSignFlip)
{
// Northern mount with axis +100 points 10 degrees below the equator:
// celestial sign is negative while the axis coordinate stays positive.
EXPECT_EQ(-10L * 3600L, Declination::axisToCelestialSeconds(100L * 3600L, true));
}

TEST(DeclinationTest, CelestialToAxisNorthern)
{
EXPECT_EQ(10L * 3600L, Declination::celestialToAxisSeconds(80L * 3600L, true));
EXPECT_EQ(0L, Declination::celestialToAxisSeconds(90L * 3600L, true));
EXPECT_EQ(180L * 3600L, Declination::celestialToAxisSeconds(-90L * 3600L, true));
}

TEST(DeclinationTest, CelestialToAxisSouthern)
{
EXPECT_EQ(-10L * 3600L, Declination::celestialToAxisSeconds(-80L * 3600L, false));
EXPECT_EQ(0L, Declination::celestialToAxisSeconds(-90L * 3600L, false));
EXPECT_EQ(-180L * 3600L, Declination::celestialToAxisSeconds(90L * 3600L, false));
}

TEST(DeclinationTest, CelestialAxisRoundTrip)
{
// The axis->celestial mapping is two-to-one (|axis|): the arm at +100 and
// -100 both point at celestial -10, on opposite sides of the meridian.
// The inverse maps back to the home branch only: non-negative axis in the
// northern hemisphere, non-positive in the southern.
for (long axis = 0; axis <= 180L * 3600L; axis += 1800L)
{
const long celestial = Declination::axisToCelestialSeconds(axis, true);
EXPECT_EQ(axis, Declination::celestialToAxisSeconds(celestial, true)) << "axis=" << axis;
}
for (long axis = 0; axis >= -180L * 3600L; axis -= 1800L)
{
const long celestial = Declination::axisToCelestialSeconds(axis, false);
EXPECT_EQ(axis, Declination::celestialToAxisSeconds(celestial, false)) << "axis=" << axis;
}
}

TEST(DeclinationTest, FromTotalSecondsClamps)
{
EXPECT_EQ(180L * 3600L, Declination::fromTotalSeconds(200L * 3600L).getTotalSeconds());
EXPECT_EQ(-180L * 3600L, Declination::fromTotalSeconds(-200L * 3600L).getTotalSeconds());
EXPECT_EQ(12345L, Declination::fromTotalSeconds(12345L).getTotalSeconds());
}