From 297874065a95524e066e9efdbe5fb1301683b287 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 23:05:56 +0000 Subject: [PATCH] Fix Meade DEC conversion and 64-byte response truncation Two protocol regressions in the refactored Meade layer, both verified against an OpenAstroExplorer (ESP32, v1.13.20): 1. DEC convention leak: the mount stores declination internally as 'degrees from the pole' (0 = pole) but decFrom()/onSetTargetDec()/ onSyncCoordinates() passed raw internal values across the protocol boundary. :GD# reported +00*00'00 at home instead of +90*00'00 (the :GX# path converts correctly via Declination::formatString, which is why OATControl looked right while INDI/KStars was off by 90 degrees), and :Sd/:CM interpreted celestial DEC as pole distance, sending GOTO slews toward the ground. Convert at the boundary in both directions using the same arithmetic as Declination::formatString/ParseFromMeade. Validated by a native roundtrip test against core::Declination over -89..+89 deg in both hemispheres, including the observed value pair (-1*37' wire vs +88*22' actual). 2. MeadeResponse::Capacity was 64 bytes; the :XGM# reply (board, two stepper descriptors, full addon list) exceeds 100 bytes on an OAE, so it was silently truncated mid-token and the '#' terminator was dropped, hanging every client that reads to the terminator. Raise the capacity to 160. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KSo3nJCWH137eBiNTL7JVo --- src/MeadeCommandProcessor.cpp | 39 ++++++++++++++++++++++++++++------ src/core/meade/MeadeParser.hpp | 6 +++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/MeadeCommandProcessor.cpp b/src/MeadeCommandProcessor.cpp index 419f7630..dd36fcb9 100644 --- a/src/MeadeCommandProcessor.cpp +++ b/src/MeadeCommandProcessor.cpp @@ -97,13 +97,40 @@ meade::RaCoordinate raFrom(const DayTime &t) }; } +// The mount stores declination internally as "degrees from the pole" +// (0 = pole, see Declination.cpp), but the Meade protocol carries standard +// celestial declination (+90 = north celestial pole). Convert at the +// protocol boundary, using the same arithmetic as Declination::formatString +// and Declination::FromSeconds. +constexpr long polarDistanceArcSeconds = 90L * 60L * 60L; + meade::DecCoordinate decFrom(const Declination &d) { - return meade::DecCoordinate { - static_cast(d.getHours()), - static_cast(d.getMinutes()), - static_cast(d.getSeconds()), + const long internal = d.getTotalSeconds(); + const long distance = internal < 0 ? -internal : internal; + const long celestial = inNorthernHemisphere ? polarDistanceArcSeconds - distance : -polarDistanceArcSeconds + distance; + + long secs = celestial < 0 ? -celestial : celestial; + meade::DecCoordinate out { + static_cast(secs / 3600), + static_cast((secs / 60) % 60), + static_cast(secs % 60), }; + if (celestial < 0) + { + out.degrees = static_cast(-out.degrees); + } + return out; +} + +// Inverse conversion for values received from the wire (:Sd, :CM), matching +// Declination::ParseFromMeade. Note: the wire struct cannot carry the sign +// of "-00*MM:SS" values (degrees == 0), a pre-existing parser limitation. +Declination declinationFromMeade(const meade::DecCoordinate &dec) +{ + const long degrees = dec.degrees < 0 ? -static_cast(dec.degrees) : static_cast(dec.degrees); + const long magnitude = degrees * 3600L + static_cast(dec.minutes) * 60L + dec.seconds; + return Declination::FromSeconds(dec.degrees < 0 ? -magnitude : magnitude); } } // namespace @@ -248,7 +275,7 @@ void MeadeCommandProcessor::onSyncToTarget() ///////////////////////////// bool MeadeCommandProcessor::onSetTargetDec(meade::DecCoordinate dec) { - _mount->targetDEC() = Declination(static_cast(dec.degrees), static_cast(dec.minutes), static_cast(dec.seconds)); + _mount->targetDEC() = declinationFromMeade(dec); LOG(DEBUG_MEADE, "[MEADE]: SetInfo: Received Target DEC: %s", _mount->targetDEC().ToString()); return true; } @@ -282,7 +309,7 @@ bool MeadeCommandProcessor::onSetHourAngle(uint8_t hours, uint8_t minutes) bool MeadeCommandProcessor::onSyncCoordinates(meade::DecCoordinate dec, meade::RaCoordinate ra) { - Declination decValue(static_cast(dec.degrees), static_cast(dec.minutes), static_cast(dec.seconds)); + Declination decValue = declinationFromMeade(dec); DayTime raValue(static_cast(ra.hours), static_cast(ra.minutes), static_cast(ra.seconds)); _mount->syncPosition(raValue, decValue); return true; diff --git a/src/core/meade/MeadeParser.hpp b/src/core/meade/MeadeParser.hpp index 3dd9912a..71ac159d 100644 --- a/src/core/meade/MeadeParser.hpp +++ b/src/core/meade/MeadeParser.hpp @@ -39,7 +39,11 @@ namespace meade class MeadeResponse { public: - static constexpr size_t Capacity = 64; + // Must fit the longest reply: :XGM# (board + two stepper descriptors + + // the full addon list) exceeds 100 bytes on an OAE. At 64 the reply was + // silently truncated mid-token and the '#' terminator was dropped, + // breaking every client waiting for it. + static constexpr size_t Capacity = 160; MeadeResponse() : _length(0) {