28#include <libnova/libnova.h>
35static double clampDec(
double d)
37 if (d > 90.0)
return 90.0;
38 if (d < -90.0)
return -90.0;
42static double normalizeHours(
double h)
44 while (h < 0.0) h += 24.0;
45 while (h >= 24.0) h -= 24.0;
49static double normalizeSignedHours(
double h)
51 h = normalizeHours(h);
57static bool extractNumbers(
const std::string &s, std::vector<double> &out)
60 static const std::regex re(R
"([-+]?\d+(\.\d+)?)");
61 for (
auto it = std::sregex_iterator(s.begin(), s.end(), re); it != std::sregex_iterator(); ++it)
65 out.push_back(std::stod((*it).str()));
74static bool parseRaHours(
const std::string &
ra,
double &hours)
76 std::vector<double> nums;
77 if (!extractNumbers(
ra, nums))
return false;
80 hours = normalizeHours(nums[0]);
83 const double hh = nums[0];
84 const double mm = nums.size() >= 2 ? nums[1] : 0.0;
85 const double ss = nums.size() >= 3 ? nums[2] : 0.0;
86 hours = normalizeHours(hh + mm / 60.0 + ss / 3600.0);
90static bool parseDecDeg(
const std::string &
dec,
double °)
92 const auto firstDigit =
dec.find_first_of(
"0123456789");
93 const bool explicitNegative = (firstDigit != std::string::npos &&
94 dec.find(
'-', 0) != std::string::npos &&
95 dec.find(
'-', 0) < firstDigit);
97 std::vector<double> nums;
98 if (!extractNumbers(
dec, nums))
return false;
101 deg = clampDec(nums[0]);
104 const double dd = nums[0];
105 const double mm = nums.size() >= 2 ? std::fabs(nums[1]) : 0.0;
106 const double ss = nums.size() >= 3 ? std::fabs(nums[2]) : 0.0;
107 const double sign = (dd < 0.0 || explicitNegative) ? -1.0 : 1.0;
108 deg = clampDec(sign * (std::fabs(dd) + mm / 60.0 + ss / 3600.0));
112static void normalizeSexagesimal(
int &high,
int &mid,
int &low)
126static int roundUtcOffsetHours(
double utcOffsetHoursEast)
128 return static_cast<int>(std::lround(utcOffsetHoursEast));
131static std::string formatUtcOffsetForMount(
int utcOffsetHoursEast)
134 const int mountHours = std::max(-14, std::min(14, -utcOffsetHoursEast));
136 std::snprintf(buf,
sizeof(buf),
"%c%02d", mountHours >= 0 ?
'+' :
'-',
std::abs(mountHours));
137 return std::string(buf);
140static std::string formatDateMdy(
const ln_date &date)
143 std::snprintf(buf,
sizeof(buf),
"%02d/%02d/%02d",
147 return std::string(buf);
150static bool makeLocalDateFromUtc(
const ln_date *utc,
double utcOffsetHoursEast, ln_date &localDate)
155 const double utcJd = ln_get_julian_day(
const_cast<ln_date *
>(utc));
156 ln_get_date(utcJd + (utcOffsetHoursEast / 24.0), &localDate);
160static bool normalizeLocalDateTime(
const ln_date &date, ln_date &normalized)
163 tm.tm_year = date.years - 1900;
164 tm.tm_mon = date.months - 1;
165 tm.tm_mday = date.days;
166 tm.tm_hour = date.hours;
167 tm.tm_min = date.minutes;
168 tm.tm_sec =
static_cast<int>(std::lround(date.seconds));
171 time_t t = ::timegm(&tm);
172 if (t ==
static_cast<time_t
>(-1))
175 std::tm tmNormalized {};
176 if (!::gmtime_r(&t, &tmNormalized))
179 normalized.years = tmNormalized.tm_year + 1900;
180 normalized.months = tmNormalized.tm_mon + 1;
181 normalized.days = tmNormalized.tm_mday;
182 normalized.hours = tmNormalized.tm_hour;
183 normalized.minutes = tmNormalized.tm_min;
184 normalized.seconds = tmNormalized.tm_sec;
188static std::string formatTimeHms(
const ln_date &date)
191 std::snprintf(buf,
sizeof(buf),
"%02d:%02d:%02d",
194 static_cast<int>(std::lround(date.seconds)));
195 return std::string(buf);
198static std::string formatLatDmm(
double latitudeDegrees)
200 const double lat = std::max(-90.0, std::min(90.0, latitudeDegrees));
201 const char sign = lat >= 0.0 ?
'+' :
'-';
202 const double absLat = std::fabs(lat);
203 int deg =
static_cast<int>(std::floor(absLat));
204 int min =
static_cast<int>(std::lround((absLat - deg) * 60.0));
216 std::snprintf(buf,
sizeof(buf),
"%c%02d*%02d", sign, deg,
min);
217 return std::string(buf);
220static std::string formatLonDmmMountWestPositive(
double longitudeDegreesEastPositive)
222 double lonE = std::fmod(longitudeDegreesEastPositive, 360.0);
227 double lonW = std::fmod(360.0 - lonE, 360.0);
231 int totalMinutes =
static_cast<int>(std::lround(lonW * 60.0));
232 totalMinutes %= (360 * 60);
233 if (totalMinutes < 0)
234 totalMinutes += (360 * 60);
236 const int deg = totalMinutes / 60;
237 const int min = totalMinutes % 60;
239 std::snprintf(buf,
sizeof(buf),
"+%03d*%02d", deg,
min);
240 return std::string(buf);
243static double currentUtcJulianDay()
245 const auto now = std::chrono::system_clock::now();
246 const auto epochSeconds = std::chrono::duration<double>(now.time_since_epoch()).count();
247 return 2440587.5 + (epochSeconds / 86400.0);
250static double greenwichMeanSiderealHours(
double julianDay)
252 const double t = (julianDay - 2451545.0) / 36525.0;
253 double gmstDegrees = 280.46061837 +
254 360.98564736629 * (julianDay - 2451545.0) +
255 0.000387933 * t * t -
256 (t * t * t) / 38710000.0;
258 gmstDegrees = std::fmod(gmstDegrees, 360.0);
259 if (gmstDegrees < 0.0)
260 gmstDegrees += 360.0;
262 return gmstDegrees / 15.0;
265static std::string formatRaSr(
double raHours)
268 raHours = normalizeHours(raHours);
269 int hh =
static_cast<int>(std::floor(raHours));
270 const double m = (raHours - hh) * 60.0;
271 int mm =
static_cast<int>(std::floor(m));
272 int ss =
static_cast<int>(std::round((m - mm) * 60.0));
273 normalizeSexagesimal(hh, mm, ss);
274 if (hh >= 24) hh -= 24;
276 std::snprintf(buf,
sizeof(buf),
"%02d:%02d:%02d", hh, mm, ss);
277 return std::string(buf);
280static std::string formatDecSd(
double decDeg)
283 const double d = clampDec(decDeg);
284 const char sign = (d < 0.0) ?
'-' :
'+';
285 const double ad = std::fabs(d);
286 int dd =
static_cast<int>(std::floor(ad));
287 const double m = (ad - dd) * 60.0;
288 int mm =
static_cast<int>(std::floor(m));
289 int ss =
static_cast<int>(std::round((m - mm) * 60.0));
290 normalizeSexagesimal(dd, mm, ss);
298 std::snprintf(buf,
sizeof(buf),
"%c%02d*%02d:%02d", sign, dd, mm, ss);
299 return std::string(buf);
306 uint32_t capabilities =
328 return "MX-HD Telescope";
347 IUFillNumber(&GuideNSN[0],
"TIMED_GUIDE_N",
"North (ms)",
"%.0f", 0, 600000, 0, 0);
348 IUFillNumber(&GuideNSN[1],
"TIMED_GUIDE_S",
"South (ms)",
"%.0f", 0, 600000, 0, 0);
352 IUFillNumber(&GuideWEN[0],
"TIMED_GUIDE_W",
"West (ms)",
"%.0f", 0, 600000, 0, 0);
353 IUFillNumber(&GuideWEN[1],
"TIMED_GUIDE_E",
"East (ms)",
"%.0f", 0, 600000, 0, 0);
384 for (
int attempt = 0; attempt < 3; ++attempt)
386 uint8_t b1 = 0, b2 = 0, b3 = 0;
387 if (mxhdReadStatus(b1, b2, b3))
389 decodeStatus(b1, b2, b3);
392 std::this_thread::sleep_for(std::chrono::milliseconds(200));
394 LOG_WARN(
"Handshake status read failed; continuing with connection so the client can retry.");
406 std::lock_guard<std::mutex> lock(m_ioMutex);
412 m_trackingEnabled =
true;
417 if (!mxhdApplyCachedSiteTime())
419 LOG_ERROR(
"Failed to apply cached site/time information after connect.");
420 (void)mxhdSend(
":Q#");
432 std::lock_guard<std::mutex> guideLock(m_guideMutex);
434 m_guideActive =
false;
435 m_guideDirection = 0;
439 (void)mxhdSend(
":Q#");
442 std::lock_guard<std::mutex> lock(m_ioMutex);
451 bool shouldSend =
false;
453 std::lock_guard<std::mutex> lock(m_siteTimeMutex);
456 if (!m_hasCachedSiteCoordinates &&
457 std::fabs(latitude) <= 1e-9 &&
458 std::fabs(longitude) <= 1e-9)
460 m_cachedSiteLatitudeDeg = latitude;
461 m_cachedSiteLongitudeDeg = longitude;
465 m_cachedSiteLatitudeDeg = latitude;
466 m_cachedSiteLongitudeDeg = longitude;
467 m_hasCachedSiteCoordinates =
true;
475 if (!mxhdApplySiteCoordinates(latitude, longitude))
486 bool shouldSend =
false;
488 std::lock_guard<std::mutex> lock(m_siteTimeMutex);
489 m_cachedUtcDate = *utc;
490 m_cachedUtcOffsetHoursEast = utc_offset;
491 m_hasCachedTime =
true;
498 if (!mxhdApplyTime(utc, utc_offset))
504bool MXHDTelescope::mxhdSend(
const std::string &
cmd)
506 std::lock_guard<std::mutex> lock(m_ioMutex);
513 LOGF_ERROR(
"I/O send failed (%s): %s",
cmd.c_str(), err.c_str());
519bool MXHDTelescope::mxhdQueryHash(
const std::string &
cmd, std::string &resp)
521 std::lock_guard<std::mutex> lock(m_ioMutex);
528 LOGF_ERROR(
"I/O query failed (%s): %s",
cmd.c_str(), err.c_str());
534bool MXHDTelescope::mxhdQueryAck(
const std::string &
cmd,
char &ack,
int timeoutMs)
536 std::lock_guard<std::mutex> lock(m_ioMutex);
543 LOGF_ERROR(
"I/O query failed (%s): %s",
cmd.c_str(), err.c_str());
549bool MXHDTelescope::mxhdApplySiteCoordinates(
double latitudeDeg,
double longitudeDeg)
551 const std::string cmdSt =
":St" + formatLatDmm(latitudeDeg) +
"#";
552 const std::string cmdSg =
":Sg" + formatLonDmmMountWestPositive(longitudeDeg) +
"#";
555 if (!mxhdQueryAck(cmdSt, ack, 10000))
559 LOGF_WARN(
"Unexpected :St response: '%c'", ack);
563 if (!mxhdQueryAck(cmdSg, ack, 10000))
567 LOGF_WARN(
"Unexpected :Sg response: '%c'", ack);
574bool MXHDTelescope::mxhdApplyTime(ln_date *utc,
double utcOffsetHoursEast)
576 if (!mxhdSendUtcOffset(utcOffsetHoursEast))
578 return mxhdSendLocalDateTime(utc, utcOffsetHoursEast);
581bool MXHDTelescope::mxhdSendUtcOffset(
double utcOffsetHoursEast,
int timeoutMs)
583 const int utcOffsetHoursRounded = roundUtcOffsetHours(utcOffsetHoursEast);
584 if (std::fabs(utcOffsetHoursEast - utcOffsetHoursRounded) > 0.01)
586 LOGF_WARN(
"UTC offset is fractional (%.3fh); rounded to %dh for MX-HD :SG.",
587 utcOffsetHoursEast, utcOffsetHoursRounded);
590 const std::string cmdSg =
":SG" + formatUtcOffsetForMount(utcOffsetHoursRounded) +
"#";
592 if (!mxhdQueryAck(cmdSg, ack, timeoutMs))
596 LOGF_WARN(
"Unexpected :SG response: '%c'", ack);
602bool MXHDTelescope::mxhdSendLocalDateTime(ln_date *utc,
double utcOffsetHoursEast,
int timeoutMs)
607 ln_date localDate {};
608 if (!makeLocalDateFromUtc(utc, utcOffsetHoursEast, localDate))
611 ln_date normalizedLocalDate {};
612 if (!normalizeLocalDateTime(localDate, normalizedLocalDate))
614 LOG_ERROR(
"Failed to normalize local date/time for :SC/:SL.");
618 const std::string date = formatDateMdy(normalizedLocalDate);
619 const std::string time = formatTimeHms(normalizedLocalDate);
622 LOG_ERROR(
"Failed to format local date for :SC.");
627 LOG_ERROR(
"Failed to format local time for :SL.");
631 const std::string cmdSc =
":SC" + date +
"#";
632 const std::string cmdSl =
":SL" + time +
"#";
635 if (!mxhdQueryAck(cmdSc, ack, timeoutMs))
639 LOGF_WARN(
"Unexpected :SC response: '%c'", ack);
643 if (!mxhdQueryAck(cmdSl, ack, timeoutMs))
647 LOGF_WARN(
"Unexpected :SL response: '%c'", ack);
654void MXHDTelescope::mxhdUpdatePierSide(
double raHours)
656 double longitudeDegEast = 0.0;
658 std::lock_guard<std::mutex> lock(m_siteTimeMutex);
659 if (!m_hasCachedSiteCoordinates)
661 longitudeDegEast = m_cachedSiteLongitudeDeg;
664 const double lstHours = normalizeHours(greenwichMeanSiderealHours(currentUtcJulianDay()) + (longitudeDegEast / 15.0));
665 const double haHours = normalizeSignedHours(lstHours - raHours);
668 constexpr double kMeridianDeadbandHours = 1.0 / 60.0;
669 if (std::fabs(haHours) < kMeridianDeadbandHours)
671 if (!m_hasPierSideCache)
681 m_pierSideEast = (haHours > 0.0);
682 m_hasPierSideCache =
true;
686bool MXHDTelescope::mxhdApplyCachedSiteTime()
688 bool haveSite =
false;
689 bool haveTime =
false;
690 double latitudeDeg = 0.0;
691 double longitudeDeg = 0.0;
693 double utcOffsetHoursEast = 0.0;
696 std::lock_guard<std::mutex> lock(m_siteTimeMutex);
697 haveSite = m_hasCachedSiteCoordinates;
698 haveTime = m_hasCachedTime;
699 latitudeDeg = m_cachedSiteLatitudeDeg;
700 longitudeDeg = m_cachedSiteLongitudeDeg;
701 utc = m_cachedUtcDate;
702 utcOffsetHoursEast = m_cachedUtcOffsetHoursEast;
711 if (!mxhdSendUtcOffset(utcOffsetHoursEast, 10000))
717 if (!mxhdApplySiteCoordinates(latitudeDeg, longitudeDeg))
723 if (!mxhdSendLocalDateTime(&utc, utcOffsetHoursEast, 10000))
727 if (!haveSite && !haveTime)
729 LOG_WARN(
"No cached INDI site/time values available at connect; skipping automatic :SG/:St/:Sg/:SC/:SL sync.");
735bool MXHDTelescope::mxhdReadStatus(uint8_t &b1, uint8_t &b2, uint8_t &b3)
737 std::lock_guard<std::mutex> lock(m_ioMutex);
744 LOGF_WARN(
"Status read failed (@ST#): %s", err.c_str());
750void MXHDTelescope::decodeStatus(uint8_t b1, uint8_t b2, uint8_t b3)
756 m_isHoming = (b3 & 0x80) != 0;
757 const bool slewBit = (b3 & 0x40) != 0;
758 m_isSlewing = slewBit || m_isHoming;
760 m_voltageV =
static_cast<double>(b2) / 10.0;
762 if (m_isHoming || m_isSlewing)
770bool MXHDTelescope::mxhdQueryRaDec(
double &raHours,
double &decDeg)
772 std::string raRaw, decRaw;
773 if (!mxhdQueryHash(
":GR#", raRaw))
775 if (!mxhdQueryHash(
":GD#", decRaw))
778 if (!parseRaHours(raRaw, raHours))
780 LOGF_ERROR(
"Invalid RA response: '%s'", raRaw.c_str());
783 if (!parseDecDeg(decRaw, decDeg))
785 LOGF_ERROR(
"Invalid Dec response: '%s'", decRaw.c_str());
791bool MXHDTelescope::mxhdSetTargetRaDec(
double raHours,
double decDeg)
794 const std::string
ra =
":Sr" + formatRaSr(raHours) +
"#";
795 const std::string
dec =
":Sd" + formatDecSd(decDeg) +
"#";
798 if (!mxhdQueryAck(
ra, ack))
802 LOGF_WARN(
"Unexpected :Sr response: '%c'", ack);
806 if (!mxhdQueryAck(
dec, ack))
810 LOGF_WARN(
"Unexpected :Sd response: '%c'", ack);
819 if (!mxhdSetTargetRaDec(
ra,
dec))
823 if (!mxhdQueryAck(
":MS#", ack))
828 LOGF_WARN(
"Unexpected :MS# response: '%c'", ack);
837 if (!mxhdSetTargetRaDec(
ra,
dec))
841 if (!mxhdQueryHash(
":CM#", resp))
852 if (!mxhdSend(
"@Hm#"))
856 m_parkCommandActive =
true;
857 m_unparkCommandActive =
false;
858 m_homeCommandActive =
false;
868 LOG_INFO(
"Mount is already unparked.");
873 if (!mxhdSend(
"@OG#"))
876 m_parkCommandActive =
false;
877 m_unparkCommandActive =
true;
878 m_homeCommandActive =
false;
890 return mxhdSend(
":RG#");
893 return mxhdSend(
":RC#");
896 return mxhdSend(
":RM#");
899 return mxhdSend(
":RS#");
902 LOGF_WARN(
"Unsupported slew rate index %d requested by client.", index);
910 if (!mxhdSend(enabled ?
"@FD1#" :
"@FD0#"))
912 m_trackingEnabled = enabled;
918 TrackRateMode selectedMode = TrackRateMode::Sidereal;
923 selectedMode = TrackRateMode::Sidereal;
927 selectedMode = TrackRateMode::Solar;
931 selectedMode = TrackRateMode::Lunar;
935 LOGF_WARN(
"Unsupported track mode index %u requested by client.", mode);
939 if (!mxhdSetTrackRate(selectedMode))
942 m_trackRateMode = selectedMode;
946bool MXHDTelescope::mxhdSetTrackRate(TrackRateMode mode)
950 case TrackRateMode::Sidereal:
951 return mxhdSend(
"@CE0#");
952 case TrackRateMode::Solar:
953 return mxhdSend(
"@LP1#");
954 case TrackRateMode::Lunar:
955 return mxhdSend(
"@LP4#");
966 uint8_t b1 = 0, b2 = 0, b3 = 0;
967 if (mxhdReadStatus(b1, b2, b3))
968 decodeStatus(b1, b2, b3);
970 m_abortRequestedAt = ::time(
nullptr);
971 m_abortSawHomeCleared =
false;
972 m_abortHomeClearedAt = 0;
977 if (!mxhdSend(
"@ME0#"))
984 m_unparkCommandActive =
false;
985 m_homeCommandActive =
false;
986 startAbortFinalize(180);
993 if (!mxhdQueryHash(
":Q#", resp))
995 m_unparkCommandActive =
false;
996 m_homeCommandActive =
false;
1001 if (!mxhdSend(
"@ME0#"))
1003 m_unparkCommandActive =
false;
1004 m_homeCommandActive =
false;
1005 startAbortFinalize(90);
1009void MXHDTelescope::startAbortFinalize(uint32_t delaySeconds)
1011 m_abortFinalizeActive =
true;
1012 m_abortDelaySeconds = delaySeconds;
1017void MXHDTelescope::maybeAdvanceAbortFinalize()
1019 if (!m_abortFinalizeActive)
1023 uint8_t b1 = 0, b2 = 0, b3 = 0;
1024 if (!mxhdReadStatus(b1, b2, b3))
1026 decodeStatus(b1, b2, b3);
1028 const time_t now = ::time(
nullptr);
1030 if (!m_abortSawHomeCleared)
1034 m_abortSawHomeCleared =
true;
1035 m_abortHomeClearedAt = now;
1041 if (now < (m_abortRequestedAt +
static_cast<time_t
>(m_abortDelaySeconds)))
1045 (void)mxhdSend(
"@FD0#");
1047 usleep(1000 * 1000);
1048 (void)mxhdSend(
"@ME1#");
1050 m_abortFinalizeActive =
false;
1081IPState MXHDTelescope::startTimedGuidePulse(GuideAxis axis,
int direction, uint32_t ms,
const char *moveCmd)
1090 bool needStopFirst =
false;
1092 std::lock_guard<std::mutex> lock(m_guideMutex);
1093 needStopFirst = m_guideActive;
1094 token = ++m_guideTokenGen;
1095 m_guideActive =
true;
1097 m_guideDirection = direction;
1100 if (needStopFirst && !mxhdSend(
":Q#"))
1102 std::lock_guard<std::mutex> lock(m_guideMutex);
1103 if (token == m_guideTokenGen.load())
1105 m_guideActive =
false;
1106 m_guideDirection = 0;
1111 if (!mxhdSend(moveCmd))
1113 std::lock_guard<std::mutex> lock(m_guideMutex);
1114 if (token == m_guideTokenGen.load())
1116 m_guideActive =
false;
1117 m_guideDirection = 0;
1122 std::thread([
this, axis, token, ms]()
1124 std::this_thread::sleep_for(std::chrono::milliseconds(ms));
1125 completeTimedGuidePulse(axis, token,
true);
1131void MXHDTelescope::setGuidePropertyIdle(GuideAxis axis,
IPState state)
1133 if (axis == GuideAxis::RA)
1135 GuideWEN[0].value = 0;
1136 GuideWEN[1].value = 0;
1137 GuideWENP.
s = state;
1142 GuideNSN[0].value = 0;
1143 GuideNSN[1].value = 0;
1144 GuideNSNP.
s = state;
1149void MXHDTelescope::completeTimedGuidePulse(GuideAxis axis, uint64_t token,
bool sendStop)
1151 bool shouldStop =
false;
1153 std::lock_guard<std::mutex> lock(m_guideMutex);
1154 if (token != m_guideTokenGen.load() || !m_guideActive || axis != m_guideAxis)
1157 m_guideActive =
false;
1158 m_guideDirection = 0;
1164 (void)mxhdSend(
":Q#");
1166 setGuidePropertyIdle(axis,
IPS_OK);
1171 return startTimedGuidePulse(GuideAxis::DEC, +1, ms,
"@mN#");
1176 return startTimedGuidePulse(GuideAxis::DEC, -1, ms,
"@mS#");
1181 return startTimedGuidePulse(GuideAxis::RA, +1, ms,
"@mE#");
1186 return startTimedGuidePulse(GuideAxis::RA, -1, ms,
"@mW#");
1194 if (std::strcmp(name, HomeSP.
name) == 0)
1197 if (HomeS[0].s ==
ISS_ON)
1199 if (!mxhdSend(
"@OG#"))
1206 m_homeCommandActive =
true;
1207 m_unparkCommandActive =
false;
1218 if (std::strcmp(name,
"SIMULATE_PIER_SIDE") == 0)
1221 LOG_INFO(
"MX-HD uses HA-based pier side, so SIMULATE_PIER_SIDE is forced to enabled.");
1233 if (std::strcmp(name, GuideNSNP.
name) == 0)
1236 const uint32_t northMs =
static_cast<uint32_t
>(GuideNSN[0].value < 0 ? 0 : GuideNSN[0].value);
1237 const uint32_t southMs =
static_cast<uint32_t
>(GuideNSN[1].value < 0 ? 0 : GuideNSN[1].value);
1239 if (northMs > 0 && southMs > 0)
1241 setGuidePropertyIdle(GuideAxis::DEC,
IPS_ALERT);
1247 else if (southMs > 0)
1253 setGuidePropertyIdle(GuideAxis::DEC, GuideNSNP.
s);
1259 if (std::strcmp(name, GuideWENP.
name) == 0)
1262 const uint32_t westMs =
static_cast<uint32_t
>(GuideWEN[0].value < 0 ? 0 : GuideWEN[0].value);
1263 const uint32_t eastMs =
static_cast<uint32_t
>(GuideWEN[1].value < 0 ? 0 : GuideWEN[1].value);
1265 if (westMs > 0 && eastMs > 0)
1267 setGuidePropertyIdle(GuideAxis::RA,
IPS_ALERT);
1273 else if (eastMs > 0)
1279 setGuidePropertyIdle(GuideAxis::RA, GuideWENP.
s);
1291 uint8_t b1 = 0, b2 = 0, b3 = 0;
1292 if (mxhdReadStatus(b1, b2, b3))
1293 decodeStatus(b1, b2, b3);
1296 maybeAdvanceAbortFinalize();
1298 if (m_abortFinalizeActive)
1300 else if (m_isHoming || m_isSlewing)
1309 if (!m_abortFinalizeActive && !m_isHoming && !m_isSlewing)
1311 double ra = 0.0,
dec = 0.0;
1312 if (mxhdQueryRaDec(
ra,
dec))
1316 mxhdUpdatePierSide(
ra);
1321 if (m_parkCommandActive && !m_isSlewing && !m_isHoming)
1324 m_parkCommandActive =
false;
1329 if (m_unparkCommandActive && !m_isSlewing && !m_isHoming)
1332 m_unparkCommandActive =
false;
1337 if (m_homeCommandActive && HomeSP.
s ==
IPS_BUSY && !m_isHoming && !m_isSlewing)
1339 m_homeCommandActive =
false;
1357static std::unique_ptr<MXHDTelescope> s_telescope;
1363 s_telescope->ISGetProperties(dev);
1369 s_telescope->ISNewSwitch(dev, name, states, names, n);
1372void ISNewNumber(
const char *dev,
const char *name,
double values[],
char *names[],
int n)
1375 s_telescope->ISNewNumber(dev, name, values, names, n);
1378void ISNewText(
const char *dev,
const char *name,
char *texts[],
char *names[],
int n)
1381 s_telescope->ISNewText(dev, name, texts, names, n);
1384void ISNewBLOB(
const char *dev,
const char *name,
int sizes[],
int blobsizes[],
char *blobs[],
char *formats[],
char *names[],
int n)
1387 s_telescope->ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
1393 s_telescope->ISSnoopDevice(root);
void detach()
indicates that the device is being removed
virtual bool Disconnect()
Disconnect from device.
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
TelescopeStatus TrackState
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
bool isParked()
isParked is mount currently parked?
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
@ TELESCOPE_HAS_PIER_SIDE
@ TELESCOPE_HAS_TRACK_MODE
@ TELESCOPE_HAS_PIER_SIDE_SIMULATION
@ TELESCOPE_CAN_CONTROL_TRACK
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
void setPierSide(TelescopePierSide side)
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
bool queryStatus3(const std::string &cmd, uint8_t &b1, uint8_t &b2, uint8_t &b3, int timeoutMs, std::string &err)
bool queryHash(const std::string &cmd, std::string &response, int timeoutMs, std::string &err)
bool writeString(const std::string &s, std::string &err)
bool queryAck(const std::string &cmd, char &ack, int timeoutMs, std::string &err)
void TimerHit() override
Called when setTimer() time is up.
IPState GuideWest(uint32_t ms)
bool Disconnect() override
Disconnect from device.
bool ReadScopeStatus() override
Read telescope status.
IPState GuideNorth(uint32_t ms)
bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
IPState GuideSouth(uint32_t ms)
bool Abort() override
Abort any telescope motion including tracking if possible.
bool Handshake() override
perform handshake with device to check communication
bool initProperties() override
Called to initialize basic properties required all the time.
bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
bool Park() override
Park the telescope to its home position.
const char * getDefaultName() override
bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
IPState GuideEast(uint32_t ms)
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
bool updateProperties() override
Called when connected state changes, to add/remove properties.
bool Goto(double ra, double dec) override
Move the scope to the supplied RA and DEC coordinates.
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
bool UnPark() override
Unpark the telescope if already parked.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
void setSimulatePierSide(bool value)
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
#define LOGF_WARN(fmt,...)
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)
void ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
Update data of an existing blob vector property.
void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Update the value of an existing switch vector property.
void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Update the value of an existing text vector property.
void ISGetProperties(const char *dev)
Get Device Properties.
void ISSnoopDevice(XMLEle *root)
Function defined by Drivers that is called when another Driver it is snooping (by having previously c...
void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)