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
17 changes: 13 additions & 4 deletions src/Declination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,22 @@ Declination Declination::fromCelestialDegrees(int deg, int min, int sec)
// deg carries the only sign on the wire, so min and sec are unsigned
// magnitudes and must move away from zero. joinSeconds is the inverse of the
// splitSeconds that getCelestialDegrees uses.
// Declinations between -1 and 0 degrees still cannot round-trip here: they
// arrive as deg == 0, and integer 0 has no sign, so -00*30:00 reads the same
// as +00*30:00. Fixing that means carrying the sign separately from the
// magnitude across this boundary, not changing the join.
// Declinations between -1 and 0 degrees cannot round-trip here: they arrive
// as deg == 0, and integer 0 has no sign, so -00*30:00 reads the same as
// +00*30:00. That is why the wire boundary uses fromCelestialSeconds
// instead -- the sign travels in the total, not in a degrees component.
const long wireSecs = core::DayTime::joinSeconds(deg, min, sec);
Declination result;
result.totalSeconds = core::Declination::celestialToAxisSeconds(wireSecs, inNorthernHemisphere);
result.checkHours();
return result;
}

Declination Declination::fromCelestialSeconds(long celestialSeconds)
{
// The sign lives in the total, so there is no zero-degrees blind spot here.
Declination result;
result.totalSeconds = core::Declination::celestialToAxisSeconds(celestialSeconds, inNorthernHemisphere);
result.checkHours();
return result;
}
6 changes: 6 additions & 0 deletions src/Declination.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class Declination : public core::Declination
// minutes/seconds.
static Declination fromCelestialDegrees(int deg, int min, int sec);

// Build from signed celestial arc-seconds. Preferred over
// fromCelestialDegrees at the wire boundary: a signed degrees component
// cannot express a coordinate between 0 and -1 degree. Pair it with
// core::Declination::celestialSecondsFrom to do the join.
static Declination fromCelestialSeconds(long celestialSeconds);

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

static Declination ParseFromMeade(String const &s);
Expand Down
40 changes: 34 additions & 6 deletions src/MeadeCommandProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,34 @@ meade::DecCoordinate decFrom(const Declination &d)
// correction before splitting into components.
int deg, min, sec;
d.getCelestialDegrees(deg, min, sec);
// getCelestialDegrees folds the sign into `deg`, where anything between 0
// and -1 degrees comes back as +0. Read the sign off the undivided total.
const long celestialSeconds = Declination::axisToCelestialSeconds(d.getTotalSeconds(), inNorthernHemisphere);
return meade::DecCoordinate {
static_cast<int16_t>(deg),
static_cast<uint16_t>(deg < 0 ? -deg : deg),
static_cast<uint8_t>(min),
static_cast<uint8_t>(sec),
celestialSeconds < 0,
};
}

Declination decFromWire(meade::DecCoordinate const &d)
{
return Declination::fromCelestialDegrees(d.degrees, d.minutes, d.seconds);
// The parser keeps sign and magnitude apart, and they stay apart all the way
// into the join. Flattening `negative` back into a signed degrees component
// here would lose it again for "-00*30:00", which is the whole point of the
// separate flag. celestialSecondsFrom is the declination counterpart of
// siteSecondsFrom below.
return Declination::fromCelestialSeconds(core::Declination::celestialSecondsFrom(d.degrees, d.minutes, d.seconds, d.negative));
}

// Signed arc-seconds for a magnitude/sign pair. The Latitude and Longitude
// constructors take signed degrees, which cannot express a site between 0 and
// -1 degree, so callers add this total to a zeroed coordinate instead.
long siteSecondsFrom(uint16_t degrees, uint8_t minutes, bool negative)
{
const long seconds = ((static_cast<long>(degrees) * 60L) + minutes) * 60L;
return negative ? -seconds : seconds;
}
} // namespace

Expand Down Expand Up @@ -161,18 +179,24 @@ bool MeadeCommandProcessor::onIsGuiding()
meade::MeadeLatitude MeadeCommandProcessor::onSiteLatitude()
{
const Latitude lat = _mount->latitude();
// getHours() folds the sign into the degrees component, so a site between
// 0 and -1 degrees reports as +0. Read the sign off the total instead.
const int degrees = lat.getHours();
return meade::MeadeLatitude {
static_cast<int16_t>(lat.getHours()),
static_cast<uint16_t>(degrees < 0 ? -degrees : degrees),
static_cast<uint8_t>(lat.getMinutes()),
lat.getTotalSeconds() < 0,
};
}

meade::MeadeLongitude MeadeCommandProcessor::onSiteLongitude()
{
const Longitude lon = _mount->longitude();
const int degrees = lon.getHours();
return meade::MeadeLongitude {
static_cast<int16_t>(lon.getHours()),
static_cast<uint16_t>(degrees < 0 ? -degrees : degrees),
static_cast<uint8_t>(lon.getMinutes()),
lon.getTotalSeconds() < 0,
};
}

Expand Down Expand Up @@ -300,13 +324,17 @@ bool MeadeCommandProcessor::onSyncCoordinates(meade::DecCoordinate dec, meade::R

bool MeadeCommandProcessor::onSetSiteLatitude(meade::MeadeLatitude lat)
{
_mount->setLatitude(Latitude(static_cast<int>(lat.degrees), static_cast<int>(lat.minutes), 0));
Latitude value;
value.addSeconds(siteSecondsFrom(lat.degrees, lat.minutes, lat.negative));
_mount->setLatitude(value);
return true;
}

bool MeadeCommandProcessor::onSetSiteLongitude(meade::MeadeLongitude lon)
{
_mount->setLongitude(Longitude(static_cast<int>(lon.degrees), static_cast<int>(lon.minutes), 0));
Longitude value;
value.addSeconds(siteSecondsFrom(lon.degrees, lon.minutes, lon.negative));
_mount->setLongitude(value);
return true;
}

Expand Down
29 changes: 23 additions & 6 deletions src/core/meade/MeadeParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,23 +160,40 @@ struct RaCoordinate {
uint8_t seconds;
};

/** @brief Declination coordinate; `degrees` carries the sign (-180..180). */
/**
* @brief Declination coordinate: unsigned magnitude plus a separate sign.
*
* The sign is a field of its own rather than the sign bit of `degrees`
* because the Meade wire format has coordinates such as `-00*30:00` whose
* degrees component is zero; folding the sign into `degrees` would round
* those to `+00*30:00`, a one-degree error either side of the equator.
*/
struct DecCoordinate {
int16_t degrees;
uint16_t degrees; ///< Magnitude only, 0..180.
uint8_t minutes;
uint8_t seconds;
bool negative;
};

/** @brief Site latitude; `degrees` is signed (-90..90). */
/** @brief Site latitude: magnitude 0..90 in `degrees`, sign in `negative`. */
struct MeadeLatitude {
int16_t degrees;
uint16_t degrees;
uint8_t minutes;
bool negative;
};

/** @brief Site longitude; `degrees` is signed (-180..180). */
/** @brief Site longitude: magnitude 0..180 in `degrees`, sign in `negative`.
*
* EAST-POSITIVE: `negative` means west of Greenwich. The Meade wire is the
* other way round -- :Sg/:Gg are east-negative -- so readLongitude and
* writeLongitude both flip the sign, and they have to stay in step. The
* convention is recorded here because the struct alone cannot show it, which
* is how a flip on both sides at once once went unnoticed.
*/
struct MeadeLongitude {
int16_t degrees;
uint16_t degrees;
uint8_t minutes;
bool negative;
};

/** @brief Wall-clock time (24h). The parser handles 12h conversion for `:Ga#`. */
Expand Down
71 changes: 28 additions & 43 deletions src/core/meade/MeadeParserHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ bool Cursor::matchIn(const char *set)
return false;
}

void Cursor::advance()
{
if (*_p != '\0')
{
++_p;
}
}

bool Cursor::digits(int n, unsigned &out)
{
unsigned v = 0;
Expand All @@ -79,29 +87,14 @@ bool Cursor::digits(int n, unsigned &out)
return true;
}

bool Cursor::signed2(int &out)
{
char sign = peek();
if (sign != '+' && sign != '-')
return false;
++_p;
unsigned v = 0;
if (!digits(2, v))
return false;
out = (sign == '-') ? -static_cast<int>(v) : static_cast<int>(v);
return true;
}

bool Cursor::signed3(int &out)
bool Cursor::optionalSign(int &sign)
{
char sign = peek();
if (sign != '+' && sign != '-')
return false;
++_p;
unsigned v = 0;
if (!digits(3, v))
return false;
out = (sign == '-') ? -static_cast<int>(v) : static_cast<int>(v);
const char c = peek();
sign = (c == '-') ? -1 : 1;
if ((c == '+') || (c == '-'))
{
advance();
}
return true;
}

Expand Down Expand Up @@ -230,13 +223,8 @@ void writeRa(MeadeResponse &r, const RaCoordinate &ra)

void writeDec(MeadeResponse &r, const DecCoordinate &d)
{
int deg = d.degrees;
writeChar(r, deg < 0 ? '-' : '+');
if (deg < 0)
{
deg = -deg;
}
writeUnsignedPadded(r, static_cast<unsigned>(deg), 2);
writeChar(r, d.negative ? '-' : '+');
writeUnsignedPadded(r, d.degrees, 2);
writeChar(r, '*');
writeUnsignedPadded(r, d.minutes, 2);
writeChar(r, '\'');
Expand All @@ -246,27 +234,24 @@ void writeDec(MeadeResponse &r, const DecCoordinate &d)

void writeLatitude(MeadeResponse &r, const MeadeLatitude &l)
{
int deg = l.degrees;
writeChar(r, deg < 0 ? '-' : '+');
if (deg < 0)
{
deg = -deg;
}
writeUnsignedPadded(r, static_cast<unsigned>(deg), 2);
writeChar(r, l.negative ? '-' : '+');
writeUnsignedPadded(r, l.degrees, 2);
writeChar(r, '*');
writeUnsignedPadded(r, l.minutes, 2);
writeTerminator(r);
}

void writeLongitude(MeadeResponse &r, const MeadeLongitude &l)
{
int deg = l.degrees;
writeChar(r, deg < 0 ? '-' : '+');
if (deg < 0)
{
deg = -deg;
}
writeUnsignedPadded(r, static_cast<unsigned>(deg), 3);
// :Gg is east-negative (MeadeProtocol.hpp), while MeadeLongitude is
// east-positive, so the sign flips on the way out. This has to move with
// readLongitude: if only one side flips, a client sets its site, reads it
// back mirrored, and pushes the mirror straight back on the next connect.
// Greenwich has no side, and a bare '-000*00' reads as a negative zero, so
// it goes out positive.
const bool atGreenwich = (l.degrees == 0) && (l.minutes == 0);
writeChar(r, (l.negative || atGreenwich) ? '+' : '-');
writeUnsignedPadded(r, l.degrees, 3);
writeChar(r, '*');
writeUnsignedPadded(r, l.minutes, 2);
writeTerminator(r);
Expand Down
21 changes: 13 additions & 8 deletions src/core/meade/MeadeParserHelpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ namespace meade
// ---------------------------------------------------------------------------
// Cursor — single-pass input cursor with small grammar primitives
//
// Forward-only; never backtracks. Each primitive returns `false` on mismatch
// (cursor is advanced on success). Ideal for fixed-format Meade sub-commands
// like coordinates, times, and dates.
// Forward-only; never backtracks. The matching primitives return `false` on
// mismatch and advance only on success. The two unconditional ones are the
// exception: `advance` returns nothing and `optionalSign` always returns
// `true`. Ideal for fixed-format Meade sub-commands like coordinates, times,
// and dates.
// ---------------------------------------------------------------------------

class Cursor
Expand All @@ -43,14 +45,17 @@ class Cursor
/// Consume one character if it is any of the chars in `set`.
bool matchIn(const char *set);

/// Consume one character unconditionally; a no-op at end of input.
void advance();

/// Read exactly `n` decimal digits into `out` (big-endian, no separators).
bool digits(int n, unsigned &out);

/// Read "+DD" or "-DD" into a signed int.
bool signed2(int &out);

/// Read "+DDD" or "-DDD" into a signed int.
bool signed3(int &out);
/// Consume a leading '+' or '-' if present and report it in `sign` as -1
/// or +1 (+1 when absent). Always succeeds — callers that require a sign
/// check `peek()` first. Keeping the sign out of the magnitude is what
/// lets "-00" survive; a signed magnitude cannot hold it.
bool optionalSign(int &sign);

private:
const char *_p;
Expand Down
Loading
Loading