34static std::unique_ptr<MLAstroRPA> rpa(
new MLAstroRPA());
78 StatusTP[0].
fill(
"STATUS",
"State",
"Unknown");
92 AzSoftLimitsNP[AZ_LIMIT_MIN].
fill(
"AZ_LIMIT_MIN",
"Min (deg)",
"%.2f", -90, 0, 0.5, -9.0);
93 AzSoftLimitsNP[AZ_LIMIT_MAX].
fill(
"AZ_LIMIT_MAX",
"Max (deg)",
"%.2f", 0, 90, 0.5, 9.0);
98 AltSoftLimitsNP[ALT_LIMIT_MIN].
fill(
"ALT_LIMIT_MIN",
"Min (deg)",
"%.2f", -90, 0, 0.5, -9.0);
99 AltSoftLimitsNP[ALT_LIMIT_MAX].
fill(
"ALT_LIMIT_MAX",
"Max (deg)",
"%.2f", 0, 90, 0.5, 9.0);
106 AltOvershootSP[ALT_OVERSHOOT_ENABLED].
fill(
"INDI_ENABLED",
"Enable Overshoot on Firmware",
ISS_OFF);
107 AltOvershootSP[ALT_OVERSHOOT_DISABLED].
fill(
"INDI_DISABLED",
"Disable",
ISS_ON);
111 AltOvershootDirSP[ALT_OVERSHOOT_UP].
fill(
"OVERSHOOT_UP",
"On Move Up",
ISS_OFF);
112 AltOvershootDirSP[ALT_OVERSHOOT_DOWN].
fill(
"OVERSHOOT_DOWN",
"On Move Down",
ISS_ON);
113 AltOvershootDirSP.
fill(
getDeviceName(),
"ALT_OVERSHOOT_DIR",
"Overshoot Direction",
116 AltOvershootAmountNP[0].
fill(
"AMOUNT",
"Amount (deg)",
"%.4f", 0, 10.9997, 0.1, 2.0);
117 AltOvershootAmountNP.
fill(
getDeviceName(),
"ALT_OVERSHOOT_AMOUNT",
"Overshoot Amount",
131 CorrectionPercentNP[0].
fill(
"CORRECTION_PERCENT",
"Correction (%)",
"%.0f", 1, 100, 1, 75);
132 CorrectionPercentNP.
fill(
getDeviceName(),
"RPA_CORRECTION_PERCENT",
"Correction Percentage",
135 CorrectionOvershootSP[CORR_OVERSHOOT_ENABLED].
fill(
"INDI_ENABLED",
"Enable overshoot",
ISS_OFF);
136 CorrectionOvershootSP[CORR_OVERSHOOT_DISABLED].
fill(
"INDI_DISABLED",
"Disable",
ISS_ON);
137 CorrectionOvershootSP.
fill(
getDeviceName(),
"RPA_CORRECTION_OVERSHOOT",
"Software Overshoot (ALT)",
140 CorrectionOvershootDirSP[CORR_OVERSHOOT_UP].
fill(
"OVERSHOOT_UP",
"Run overshoot for moving Up",
ISS_OFF);
141 CorrectionOvershootDirSP[CORR_OVERSHOOT_DOWN].
fill(
"OVERSHOOT_DOWN",
"Run overshoot for moving Down",
143 CorrectionOvershootDirSP.
fill(
getDeviceName(),
"RPA_CORRECTION_OVERSHOOT_DIR",
"Run Overshoot Direction",
146 CorrectionOvershootAmountNP[0].
fill(
"AMOUNT",
"Amount (arcmin)",
"%.1f", 0, 240, 1, 30);
147 CorrectionOvershootAmountNP.
fill(
getDeviceName(),
"RPA_CORRECTION_OVERSHOOT_AMOUNT",
148 "Overshoot Amount (arcmin)", CORRECTION_TAB,
IP_RW, 60,
IPS_IDLE);
153 AzMotorNP[AZ_RUN_CURRENT].
fill(
"AZ_RUN_CURRENT",
"Run Current (mA)",
"%.0f", 100, 3000, 50, 800);
154 AzMotorNP[AZ_HOLD_CURRENT].
fill(
"AZ_HOLD_CURRENT",
"Hold Current (mA)",
"%.0f", 0, 2000, 50, 200);
155 AzMotorNP[AZ_MICROSTEPS].
fill(
"AZ_MICROSTEPS",
"Microsteps",
"%.0f", 1, 256, 1, 64);
156 AzMotorNP[AZ_ACCEL].
fill(
"AZ_ACCEL",
"Acceleration",
"%.0f", 100, 50000, 100, 5000);
157 AzMotorNP[AZ_DECEL].
fill(
"AZ_DECEL",
"Deceleration",
"%.0f", 100, 50000, 100, 5000);
158 AzMotorNP[AZ_STEPS_PER_DEG].
fill(
"AZ_STEPS_PER_DEG",
"Steps/Degree",
"%.0f", 1, 100000, 1, 3200);
159 AzMotorNP[AZ_START_BOOST].
fill(
"AZ_START_BOOST",
"Start Boost (%)",
"%.0f", 0, 100, 1, 10);
160 AzMotorNP[AZ_COOL_STEP].
fill(
"AZ_COOL_STEP",
"CoolStep (%)",
"%.0f", 0, 100, 1, 0);
161 AzMotorNP[AZ_RUN_MODE].
fill(
"AZ_RUN_MODE",
"Mode (0=SC 1=SpC)",
"%.0f", 0, 1, 1, 0);
166 AltMotorNP[ALT_RUN_CURRENT].
fill(
"ALT_RUN_CURRENT",
"Run Current (mA)",
"%.0f", 100, 3000, 50, 800);
167 AltMotorNP[ALT_HOLD_CURRENT].
fill(
"ALT_HOLD_CURRENT",
"Hold Current (mA)",
"%.0f", 0, 2000, 50, 200);
168 AltMotorNP[ALT_MICROSTEPS].
fill(
"ALT_MICROSTEPS",
"Microsteps",
"%.0f", 1, 256, 1, 64);
169 AltMotorNP[ALT_ACCEL].
fill(
"ALT_ACCEL",
"Acceleration",
"%.0f", 100, 50000, 100, 5000);
170 AltMotorNP[ALT_DECEL].
fill(
"ALT_DECEL",
"Deceleration",
"%.0f", 100, 50000, 100, 5000);
171 AltMotorNP[ALT_STEPS_PER_DEG].
fill(
"ALT_STEPS_PER_DEG",
"Steps/Degree",
"%.0f", 1, 100000, 1, 3200);
172 AltMotorNP[ALT_START_BOOST].
fill(
"ALT_START_BOOST",
"Start Boost (%)",
"%.0f", 0, 100, 1, 10);
173 AltMotorNP[ALT_COOL_STEP].
fill(
"ALT_COOL_STEP",
"CoolStep (%)",
"%.0f", 0, 100, 1, 0);
174 AltMotorNP[ALT_RUN_MODE].
fill(
"ALT_RUN_MODE",
"Mode (0=SC 1=SpC)",
"%.0f", 0, 1, 1, 0);
179 SaveRebootSP[0].
fill(
"SAVE_REBOOT",
"Save & Reboot",
ISS_OFF);
184 StationSSIDTP[0].
fill(
"STATION_SSID",
"Station SSID",
"");
189 StationPasswordTP[0].
fill(
"STATION_PASSWORD",
"Station Password",
"");
190 StationPasswordTP.
fill(
getDeviceName(),
"RPA_STATION_PASSWORD",
"Station Password",
193 APConfigTP[AP_SSID].
fill(
"AP_SSID",
"AP SSID",
"");
194 APConfigTP[AP_IP].
fill(
"AP_IP",
"AP IP Address",
"");
199 APPasswordTP[0].
fill(
"AP_PASSWORD",
"AP Password",
"");
204 FirmwareInfoTP[FIRMWARE_VERSION].
fill(
"FIRMWARE_VERSION",
"Firmware",
"");
205 FirmwareInfoTP[FIRMWARE_SERIAL].
fill(
"FIRMWARE_SERIAL",
"Serial Number",
"");
208 WiFiInfoTP[WIFI_INFO_AP_SSID].
fill(
"AP_SSID",
"AP SSID",
"");
209 WiFiInfoTP[WIFI_INFO_AP_IP].
fill(
"AP_IP",
"AP IP",
"");
210 WiFiInfoTP[WIFI_INFO_AP_MAC].
fill(
"AP_MAC",
"AP MAC",
"");
211 WiFiInfoTP[WIFI_INFO_STA_SSID].
fill(
"STA_SSID",
"Station SSID",
"");
212 WiFiInfoTP[WIFI_INFO_STA_IP].
fill(
"STA_IP",
"Station IP",
"");
213 WiFiInfoTP[WIFI_INFO_STA_MAC].
fill(
"STA_MAC",
"Station MAC",
"");
303 AzSoftLimitsNP.
save(fp);
304 AltSoftLimitsNP.
save(fp);
305 AltOvershootSP.
save(fp);
306 AltOvershootDirSP.
save(fp);
307 AltOvershootAmountNP.
save(fp);
310 CorrectionPercentNP.
save(fp);
311 CorrectionOvershootSP.
save(fp);
312 CorrectionOvershootDirSP.
save(fp);
313 CorrectionOvershootAmountNP.
save(fp);
323 m_BacklashLogged =
false;
326 tcflush(PortFD, TCIOFLUSH);
332 char res[DRIVER_LEN] = {0};
333 if (!sendCommand(
"[MLAstroRPA-TC]", res))
336 if (strncmp(res,
"ok", 2) != 0)
338 LOGF_ERROR(
"Unexpected handshake response: %s", res);
344 const char *fwStart = strchr(res,
',');
345 const char *snField = strstr(res,
"SN:");
346 if (fwStart && snField && snField > fwStart)
349 size_t fwLen =
static_cast<size_t>(snField - fwStart);
350 while (fwLen > 0 && (fwStart[fwLen - 1] ==
',' || fwStart[fwLen - 1] ==
' '))
353 char fwVersion[64] = {0};
354 if (fwLen >=
sizeof(fwVersion))
355 fwLen =
sizeof(fwVersion) - 1;
356 strncpy(fwVersion, fwStart, fwLen);
358 FirmwareInfoTP[FIRMWARE_VERSION].setText(fwVersion);
359 FirmwareInfoTP[FIRMWARE_SERIAL].setText(snField + 3);
361 FirmwareInfoTP.
apply();
363 LOGF_INFO(
"Connected to %s (Serial: %s).", fwVersion, snField + 3);
367 if (!sendCommand(
"JoRe:1", res))
369 LOG_WARN(
"Could not set relative mode – proceeding anyway.");
373 char telem[DRIVER_LEN] = {0};
374 if (sendCommand(
"?", telem))
375 parseTelemetry(telem);
377 LOG_INFO(
"MLAstro RPA connected and ready.");
389 char res[DRIVER_LEN] = {0};
390 if (sendCommand(
"?", res))
393 checkMotionComplete();
402bool MLAstroRPA::parseTelemetry(
const char *response)
404 if (!response || *response ==
'\0')
408 const char *lt = strchr(response,
'<');
409 const char *pipe1 = strchr(response,
'|');
410 if (lt && pipe1 && pipe1 > lt)
412 char status[64] = {0};
413 int len =
static_cast<int>(pipe1 - lt - 1);
414 if (len > 0 && len <
static_cast<int>(
sizeof(status)))
416 strncpy(status, lt + 1, len);
418 if (strcmp(StatusTP[0].
getText(), status) != 0)
420 StatusTP[0].setText(status);
424 m_IsMoving = (strstr(status,
"MOVING") !=
nullptr ||
425 strstr(status,
"ALIGNING") !=
nullptr);
426 m_IsHoming = (strstr(status,
"HOMING") !=
nullptr);
431 const char *mpos = strstr(response,
"Mpos:");
434 double azDeg = 0.0, altDeg = 0.0;
435 if (sscanf(mpos + 5,
"%lf,%lf", &azDeg, &altDeg) == 2)
445 const char *dataStart = strstr(response,
"|>");
451 char data[DRIVER_LEN] = {0};
452 strncpy(data, dataStart, DRIVER_LEN - 1);
454 bool sawBacklash =
false;
455 char *token = strtok(data,
",");
461 if (sscanf(token,
"%31[^:]:%63s", key, val) == 2)
463 double v = atof(val);
465 if (strcmp(key,
"SLvl") == 0)
471 else if (strcmp(key,
"Home") == 0)
473 bool homed = (v != 0.0);
474 if (homed != m_IsHomed)
481 else if (strcmp(key,
"WSta") == 0)
486 else if (strcmp(key,
"Back") == 0)
495 else if (strcmp(key,
"AzBl") == 0)
502 else if (strcmp(key,
"AlBl") == 0)
509 else if (strcmp(key,
"AzPH") == 0)
514 else if (strcmp(key,
"AlPH") == 0)
519 else if (strcmp(key,
"AzL1") == 0)
521 AzSoftLimitsNP[AZ_LIMIT_MIN].setValue(v);
523 AzSoftLimitsNP.
apply();
525 else if (strcmp(key,
"AzL2") == 0)
527 AzSoftLimitsNP[AZ_LIMIT_MAX].setValue(v);
529 AzSoftLimitsNP.
apply();
531 else if (strcmp(key,
"AlL1") == 0)
533 AltSoftLimitsNP[ALT_LIMIT_MIN].setValue(v);
535 AltSoftLimitsNP.
apply();
537 else if (strcmp(key,
"AlL2") == 0)
539 AltSoftLimitsNP[ALT_LIMIT_MAX].setValue(v);
541 AltSoftLimitsNP.
apply();
544 else if (strcmp(key,
"Over") == 0)
546 int idx = (v != 0.0) ? ALT_OVERSHOOT_ENABLED : ALT_OVERSHOOT_DISABLED;
547 AltOvershootSP.
reset();
550 AltOvershootSP.
apply();
552 else if (strcmp(key,
"OvUp") == 0)
556 AltOvershootDirSP.
apply();
558 else if (strcmp(key,
"OvDn") == 0)
562 AltOvershootDirSP.
apply();
564 else if (strcmp(key,
"OvD") == 0)
567 AltOvershootAmountNP[0].setValue(m_OvshD + m_OvshM / 60.0 + m_OvshS / 3600.0);
569 AltOvershootAmountNP.
apply();
571 else if (strcmp(key,
"OvM") == 0)
574 AltOvershootAmountNP[0].setValue(m_OvshD + m_OvshM / 60.0 + m_OvshS / 3600.0);
575 AltOvershootAmountNP.
apply();
577 else if (strcmp(key,
"OvS") == 0)
580 AltOvershootAmountNP[0].setValue(m_OvshD + m_OvshM / 60.0 + m_OvshS / 3600.0);
581 AltOvershootAmountNP.
apply();
584 else if (strcmp(key,
"AzIR") == 0)
586 AzMotorNP[AZ_RUN_CURRENT].setValue(v);
589 else if (strcmp(key,
"AzIH") == 0)
591 AzMotorNP[AZ_HOLD_CURRENT].setValue(v);
594 else if (strcmp(key,
"AzMS") == 0)
596 AzMotorNP[AZ_MICROSTEPS].setValue(v);
599 else if (strcmp(key,
"AzAc") == 0)
601 AzMotorNP[AZ_ACCEL].setValue(v);
604 else if (strcmp(key,
"AzDec") == 0)
606 AzMotorNP[AZ_DECEL].setValue(v);
609 else if (strcmp(key,
"AzSD") == 0)
611 AzMotorNP[AZ_STEPS_PER_DEG].setValue(v);
614 else if (strcmp(key,
"AzSB") == 0)
616 AzMotorNP[AZ_START_BOOST].setValue(v);
619 else if (strcmp(key,
"AzSC") == 0)
621 AzMotorNP[AZ_COOL_STEP].setValue(v);
624 else if (strcmp(key,
"AzRM") == 0)
626 AzMotorNP[AZ_RUN_MODE].setValue(v);
631 else if (strcmp(key,
"AzRD") == 0)
640 else if (strcmp(key,
"AlIR") == 0)
642 AltMotorNP[ALT_RUN_CURRENT].setValue(v);
645 else if (strcmp(key,
"AlIH") == 0)
647 AltMotorNP[ALT_HOLD_CURRENT].setValue(v);
650 else if (strcmp(key,
"AlMS") == 0)
652 AltMotorNP[ALT_MICROSTEPS].setValue(v);
655 else if (strcmp(key,
"AlAc") == 0)
657 AltMotorNP[ALT_ACCEL].setValue(v);
660 else if (strcmp(key,
"AlDe") == 0)
662 AltMotorNP[ALT_DECEL].setValue(v);
665 else if (strcmp(key,
"AlSD") == 0)
667 AltMotorNP[ALT_STEPS_PER_DEG].setValue(v);
670 else if (strcmp(key,
"AlSB") == 0)
672 AltMotorNP[ALT_START_BOOST].setValue(v);
675 else if (strcmp(key,
"AlSC") == 0)
677 AltMotorNP[ALT_COOL_STEP].setValue(v);
680 else if (strcmp(key,
"AlRM") == 0)
682 AltMotorNP[ALT_RUN_MODE].setValue(v);
687 else if (strcmp(key,
"AlRD") == 0)
696 else if (strcmp(key,
"APss") == 0)
698 WiFiInfoTP[WIFI_INFO_AP_SSID].setText(val);
701 else if (strcmp(key,
"APip") == 0)
703 WiFiInfoTP[WIFI_INFO_AP_IP].setText(val);
706 else if (strcmp(key,
"APma") == 0)
708 WiFiInfoTP[WIFI_INFO_AP_MAC].setText(val);
711 else if (strcmp(key,
"STAs") == 0)
713 WiFiInfoTP[WIFI_INFO_STA_SSID].setText(val);
716 else if (strcmp(key,
"STAi") == 0)
718 WiFiInfoTP[WIFI_INFO_STA_IP].setText(val);
722 else if (strcmp(key,
"STAm") == 0)
724 WiFiInfoTP[WIFI_INFO_STA_MAC].setText(val);
728 token = strtok(
nullptr,
",");
734 if (sawBacklash && !m_BacklashLogged)
736 m_BacklashLogged =
true;
738 LOGF_INFO(
"Backlash compensation %s (AZ %d, ALT %d steps).",
739 enabled ?
"enabled" :
"disabled",
750void MLAstroRPA::checkMotionComplete()
759 LOG_INFO(
"MLAstro RPA motion complete.");
767 LOG_INFO(
"MLAstro RPA homing complete.");
775void MLAstroRPA::degreesToDMS(
double degrees,
int &d,
int &m,
int &s,
bool &positive)
777 positive = (degrees >= 0.0);
778 double abs_deg = std::fabs(degrees);
779 d =
static_cast<int>(abs_deg);
780 double rem = (abs_deg - d) * 60.0;
781 m =
static_cast<int>(rem);
782 s =
static_cast<int>((rem - m) * 60.0 + 0.5);
799IPState MLAstroRPA::sendRelativeMove(
double degrees,
const char *cmdPos,
const char *cmdNeg)
806 degreesToDMS(degrees, d, m, s, positive);
809 char cmd[DRIVER_LEN] = {0};
810 snprintf(
cmd, DRIVER_LEN,
"ReDe:%d,ReAM:%d,ReAS:%d,%s", d, m, s,
811 positive ? cmdPos : cmdNeg);
813 char res[DRIVER_LEN] = {0};
814 if (!sendCommand(
cmd, res))
817 if (strncmp(res,
"ok", 2) != 0)
819 LOGF_ERROR(
"Unexpected move response: %s", res);
821 if (strstr(res,
"Soft Limit"))
822 LOG_ERROR(
"Motion rejected: soft limit reached.");
823 else if (strstr(res,
"Hard Limit"))
824 LOG_ERROR(
"Motion rejected: hard limit triggered.");
825 else if (strstr(res,
"Not homed"))
826 LOG_ERROR(
"Motion rejected: device is not homed.");
827 else if (strstr(res,
"System Locked"))
828 LOG_ERROR(
"Motion rejected: system is locked (error state).");
832 LOGF_INFO(
"Relative move: %.4f° → D=%d M=%d S=%d dir=%s",
833 degrees, d, m, s, positive ?
"positive" :
"negative");
844 return azDegrees * CorrectionPercentNP[0].getValue() / 100.0;
853 if (altDegrees == 0.0)
856 const bool master = CorrectionOvershootSP[CORR_OVERSHOOT_ENABLED].
getState() ==
ISS_ON;
857 const bool dirEnabled = (altDegrees > 0.0)
858 ? CorrectionOvershootDirSP[CORR_OVERSHOOT_UP].getState() ==
ISS_ON
859 : CorrectionOvershootDirSP[CORR_OVERSHOOT_DOWN].
getState() ==
ISS_ON;
861 if (master && dirEnabled)
864 const double overshoot = CorrectionOvershootAmountNP[0].getValue() / 60.0;
865 return altDegrees + (altDegrees > 0.0 ? overshoot : -overshoot);
868 return altDegrees * CorrectionPercentNP[0].getValue() / 100.0;
880 LOGF_INFO(
"MoveAZ: correction applied — requested %.4f° → %.4f° (%.0f%%).",
881 requested, degrees, CorrectionPercentNP[0].getValue());
884 return sendRelativeMove(degrees,
"MAzR:1",
"MAzL:1");
896 LOGF_INFO(
"MoveALT: correction applied — requested %.4f° → %.4f°.",
900 return sendRelativeMove(degrees,
"MAlU:1",
"MAlD:1");
912 const double requestedAz = azDegrees;
913 const double requestedAlt = altDegrees;
920 if (azDegrees == 0.0 && altDegrees == 0.0)
922 LOGF_INFO(
"MoveBoth: requested AZ %.4f° / ALT %.4f° scaled to zero — no move.",
923 requestedAz, requestedAlt);
927 if (azDegrees != requestedAz || altDegrees != requestedAlt)
928 LOGF_INFO(
"MoveBoth: correction applied — requested AZ %.4f° / ALT %.4f° → AZ %.4f° / ALT %.4f°.",
929 requestedAz, requestedAlt, azDegrees, altDegrees);
933 degreesToDMS(azDegrees, azD, azM, azS, azPos);
935 int altD, altM, altS;
937 degreesToDMS(altDegrees, altD, altM, altS, altPos);
942 char cmd[DRIVER_LEN] = {0};
943 snprintf(
cmd, DRIVER_LEN,
944 "AzED:%d,AzEM:%d,AzES:%d,AzDi:%d,AlED:%d,AlEM:%d,AlES:%d,AlDi:%d,AAll:1",
945 azD, azM, azS, azPos ? 1 : 0,
946 altD, altM, altS, altPos ? 1 : 0);
948 char res[DRIVER_LEN] = {0};
949 if (!sendCommand(
cmd, res))
952 if (strncmp(res,
"ok", 2) != 0)
954 LOGF_ERROR(
"MoveBoth: unexpected response: %s", res);
955 if (strstr(res,
"Soft Limit"))
956 LOG_ERROR(
"Motion rejected: soft limit reached.");
957 else if (strstr(res,
"Hard Limit"))
958 LOG_ERROR(
"Motion rejected: hard limit triggered.");
959 else if (strstr(res,
"Not homed"))
960 LOG_ERROR(
"Motion rejected: device is not homed.");
961 else if (strstr(res,
"System Locked"))
962 LOG_ERROR(
"Motion rejected: system is locked (error state).");
967 LOGF_INFO(
"MoveBoth: AZ %.4f° (D=%d M=%d S=%d %s), ALT %.4f° (D=%d M=%d S=%d %s) — device sequencing internally.",
968 azDegrees, azD, azM, azS, azPos ?
"R" :
"L",
969 altDegrees, altD, altM, altS, altPos ?
"U" :
"D");
978 char res[DRIVER_LEN] = {0};
979 if (!sendCommand(
"STOP:1", res))
984 LOG_INFO(
"MLAstro RPA motion stopped.");
994 snprintf(
cmd,
sizeof(
cmd),
"SLvl:%u", speed);
996 char res[DRIVER_LEN] = {0};
997 if (!sendCommand(
cmd, res))
1000 if (strncmp(res,
"ok", 2) != 0)
1002 LOGF_ERROR(
"SetPACSpeed: unexpected response: %s", res);
1005 LOGF_INFO(
"Speed set to level %u.", speed);
1015 snprintf(
cmd,
sizeof(
cmd),
"AzRD:%d", enabled ? 1 : 0);
1017 char res[DRIVER_LEN] = {0};
1018 if (!sendCommand(
cmd, res))
1021 if (strncmp(res,
"ok", 2) != 0)
1023 LOGF_ERROR(
"ReverseAZ: unexpected response: %s", res);
1026 LOGF_INFO(
"AZ direction %s.", enabled ?
"reversed" :
"normal");
1036 snprintf(
cmd,
sizeof(
cmd),
"AlRD:%d", enabled ? 1 : 0);
1038 char res[DRIVER_LEN] = {0};
1039 if (!sendCommand(
cmd, res))
1042 if (strncmp(res,
"ok", 2) != 0)
1044 LOGF_ERROR(
"ReverseALT: unexpected response: %s", res);
1047 LOGF_INFO(
"ALT direction %s.", enabled ?
"reversed" :
"normal");
1056 char res[DRIVER_LEN] = {0};
1057 if (!sendCommand(
"SetH:1", res))
1060 if (strncmp(res,
"ok", 2) != 0)
1062 LOGF_ERROR(
"SetHome: unexpected response: %s", res);
1076 LOG_ERROR(
"No home position set. Please set home first.");
1080 char res[DRIVER_LEN] = {0};
1081 if (!sendCommand(
"RetH:1", res))
1084 if (strncmp(res,
"ok", 2) != 0)
1086 LOGF_ERROR(
"GoHome: unexpected response: %s", res);
1087 if (strstr(res,
"Not homed"))
1088 LOG_ERROR(
"Device reports: not homed.");
1102 char res[DRIVER_LEN] = {0};
1103 if (!sendCommand(
"RstH:1", res))
1106 if (strncmp(res,
"ok", 2) != 0)
1108 LOGF_ERROR(
"ResetHome: unexpected response: %s", res);
1126 char res[DRIVER_LEN] = {0};
1127 if (!sendCommand(
"SetH:1", res))
1130 if (strncmp(res,
"ok", 2) != 0)
1132 LOGF_ERROR(
"SyncAZ: unexpected response: %s", res);
1136 LOG_INFO(
"AZ synced: current position set as reference (0).");
1146 char res[DRIVER_LEN] = {0};
1147 if (!sendCommand(
"SetH:1", res))
1150 if (strncmp(res,
"ok", 2) != 0)
1152 LOGF_ERROR(
"SyncALT: unexpected response: %s", res);
1156 LOG_INFO(
"ALT synced: current position set as reference (0).");
1166 snprintf(
cmd,
sizeof(
cmd),
"Back:%d", enabled ? 1 : 0);
1168 char res[DRIVER_LEN] = {0};
1169 if (!sendCommand(
cmd, res))
1172 if (strncmp(res,
"ok", 2) != 0)
1174 LOGF_ERROR(
"SetBacklashEnabled: unexpected response: %s", res);
1177 LOGF_INFO(
"Backlash compensation %s.", enabled ?
"enabled" :
"disabled");
1187 snprintf(
cmd,
sizeof(
cmd),
"AzBl:%d", steps);
1189 char res[DRIVER_LEN] = {0};
1190 if (!sendCommand(
cmd, res))
1193 if (strncmp(res,
"ok", 2) != 0)
1195 LOGF_ERROR(
"SetBacklashAZ: unexpected response: %s", res);
1198 LOGF_INFO(
"AZ backlash set to %d steps.", steps);
1208 snprintf(
cmd,
sizeof(
cmd),
"AlBl:%d", steps);
1210 char res[DRIVER_LEN] = {0};
1211 if (!sendCommand(
cmd, res))
1214 if (strncmp(res,
"ok", 2) != 0)
1216 LOGF_ERROR(
"SetBacklashALT: unexpected response: %s", res);
1219 LOGF_INFO(
"ALT backlash set to %d steps.", steps);
1233 AzSoftLimitsNP.
update(values, names, n);
1234 char cmd[DRIVER_LEN] = {0};
1235 snprintf(
cmd, DRIVER_LEN,
"AzL1:%.2f,AzL2:%.2f",
1236 AzSoftLimitsNP[AZ_LIMIT_MIN].getValue(),
1237 AzSoftLimitsNP[AZ_LIMIT_MAX].getValue());
1238 char res[DRIVER_LEN] = {0};
1239 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1246 AzSoftLimitsNP.
apply();
1252 AltSoftLimitsNP.
update(values, names, n);
1253 char cmd[DRIVER_LEN] = {0};
1254 snprintf(
cmd, DRIVER_LEN,
"AlL1:%.2f,AlL2:%.2f",
1255 AltSoftLimitsNP[ALT_LIMIT_MIN].getValue(),
1256 AltSoftLimitsNP[ALT_LIMIT_MAX].getValue());
1257 char res[DRIVER_LEN] = {0};
1258 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1265 AltSoftLimitsNP.
apply();
1272 AltOvershootAmountNP.
update(values, names, n);
1275 degreesToDMS(AltOvershootAmountNP[0].getValue(), d, m, s, positive);
1277 snprintf(
cmd,
sizeof(
cmd),
"OvD:%d,OvM:%d,OvS:%d", d, m, s);
1278 char res[DRIVER_LEN] = {0};
1279 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1286 AltOvershootAmountNP.
apply();
1293 AzMotorNP.
update(values, names, n);
1294 char cmd[DRIVER_LEN] = {0};
1295 snprintf(
cmd, DRIVER_LEN,
1296 "AzIR:%.0f,AzIH:%.0f,AzMS:%.0f,AzAc:%.0f,AzDec:%.0f,"
1297 "AzSD:%.0f,AzSB:%.0f,AzSC:%.0f,AzRM:%.0f",
1298 AzMotorNP[AZ_RUN_CURRENT].getValue(),
1299 AzMotorNP[AZ_HOLD_CURRENT].getValue(),
1300 AzMotorNP[AZ_MICROSTEPS].getValue(),
1301 AzMotorNP[AZ_ACCEL].getValue(),
1302 AzMotorNP[AZ_DECEL].getValue(),
1303 AzMotorNP[AZ_STEPS_PER_DEG].getValue(),
1304 AzMotorNP[AZ_START_BOOST].getValue(),
1305 AzMotorNP[AZ_COOL_STEP].getValue(),
1306 AzMotorNP[AZ_RUN_MODE].getValue());
1307 char res[DRIVER_LEN] = {0};
1308 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1322 AltMotorNP.
update(values, names, n);
1323 char cmd[DRIVER_LEN] = {0};
1324 snprintf(
cmd, DRIVER_LEN,
1325 "AlIR:%.0f,AlIH:%.0f,AlMS:%.0f,AlAc:%.0f,AlDe:%.0f,"
1326 "AlSD:%.0f,AlSB:%.0f,AlSC:%.0f,AlRM:%.0f",
1327 AltMotorNP[ALT_RUN_CURRENT].getValue(),
1328 AltMotorNP[ALT_HOLD_CURRENT].getValue(),
1329 AltMotorNP[ALT_MICROSTEPS].getValue(),
1330 AltMotorNP[ALT_ACCEL].getValue(),
1331 AltMotorNP[ALT_DECEL].getValue(),
1332 AltMotorNP[ALT_STEPS_PER_DEG].getValue(),
1333 AltMotorNP[ALT_START_BOOST].getValue(),
1334 AltMotorNP[ALT_COOL_STEP].getValue(),
1335 AltMotorNP[ALT_RUN_MODE].getValue());
1336 char res[DRIVER_LEN] = {0};
1337 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1351 CorrectionPercentNP.
update(values, names, n);
1353 CorrectionPercentNP.
apply();
1359 if (CorrectionOvershootAmountNP.
isNameMatch(name))
1361 CorrectionOvershootAmountNP.
update(values, names, n);
1363 CorrectionOvershootAmountNP.
apply();
1386 AltOvershootSP.
update(states, names, n);
1387 bool enabled = AltOvershootSP[ALT_OVERSHOOT_ENABLED].
getState() ==
ISS_ON;
1389 snprintf(
cmd,
sizeof(
cmd),
"Over:%d", enabled ? 1 : 0);
1390 char res[DRIVER_LEN] = {0};
1391 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1398 AltOvershootSP.
apply();
1405 AltOvershootDirSP.
update(states, names, n);
1406 bool up = AltOvershootDirSP[ALT_OVERSHOOT_UP].
getState() ==
ISS_ON;
1407 bool down = AltOvershootDirSP[ALT_OVERSHOOT_DOWN].
getState() ==
ISS_ON;
1409 snprintf(
cmd,
sizeof(
cmd),
"OvUp:%d,OvDn:%d", up ? 1 : 0, down ? 1 : 0);
1410 char res[DRIVER_LEN] = {0};
1411 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1418 AltOvershootDirSP.
apply();
1425 CorrectionOvershootSP.
update(states, names, n);
1427 CorrectionOvershootSP.
apply();
1435 CorrectionOvershootDirSP.
update(states, names, n);
1437 CorrectionOvershootDirSP.
apply();
1445 SaveRebootSP.
reset();
1446 char res[DRIVER_LEN] = {0};
1447 if (sendCommand(
"Save&Reboot:1", res) && strncmp(res,
"ok", 2) == 0)
1450 LOG_INFO(
"Settings saved. Controller is rebooting…");
1455 LOG_ERROR(
"Save & Reboot command failed.");
1457 SaveRebootSP.
apply();
1479 StationSSIDTP.
update(texts, names, n);
1480 char cmd[DRIVER_LEN] = {0};
1481 snprintf(
cmd, DRIVER_LEN,
"STAs:%s", StationSSIDTP[0].
getText());
1482 char res[DRIVER_LEN] = {0};
1483 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1486 LOG_INFO(
"Station SSID updated. Use Save & Reboot to persist.");
1490 StationSSIDTP.
apply();
1497 StationPasswordTP.
update(texts, names, n);
1498 char cmd[DRIVER_LEN] = {0};
1499 snprintf(
cmd, DRIVER_LEN,
"STAp:%s", StationPasswordTP[0].
getText());
1500 char res[DRIVER_LEN] = {0};
1501 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1504 LOG_INFO(
"Station password updated. Use Save & Reboot to persist.");
1508 StationPasswordTP.
apply();
1515 APConfigTP.
update(texts, names, n);
1516 char cmd[DRIVER_LEN] = {0};
1517 snprintf(
cmd, DRIVER_LEN,
"APss:%s,APip:%s",
1518 APConfigTP[AP_SSID].
getText(),
1520 char res[DRIVER_LEN] = {0};
1521 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1524 LOG_INFO(
"Access Point settings updated. Use Save & Reboot to persist.");
1535 APPasswordTP.
update(texts, names, n);
1536 char cmd[DRIVER_LEN] = {0};
1537 snprintf(
cmd, DRIVER_LEN,
"APpa:%s", APPasswordTP[0].
getText());
1538 char res[DRIVER_LEN] = {0};
1539 if (sendCommand(
cmd, res) && strncmp(res,
"ok", 2) == 0)
1542 LOG_INFO(
"Access Point password updated. Use Save & Reboot to persist.");
1546 APPasswordTP.
apply();
1557bool MLAstroRPA::sendCommand(
const char *
cmd,
char *res)
1559 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1561 tcflush(PortFD, TCIOFLUSH);
1564 char formatted[DRIVER_LEN] = {0};
1565 snprintf(formatted, DRIVER_LEN,
"%s\n",
cmd);
1574 LOGF_ERROR(
"Serial write error: %s.", errstr);
1581 rc =
tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
1586 LOGF_ERROR(
"Serial read error: %s.", errstr);
1591 if (nbytes_read > 0 && (res[nbytes_read - 1] ==
'\n' || res[nbytes_read - 1] ==
'\r'))
1592 res[--nbytes_read] =
'\0';
1593 if (nbytes_read > 0 && (res[nbytes_read - 1] ==
'\r'))
1594 res[--nbytes_read] =
'\0';
1598 tcflush(PortFD, TCIOFLUSH);
1602void MLAstroRPA::hexDump(
char *buf,
const char *data,
int size)
1604 for (
int i = 0; i < size; i++)
1605 sprintf(buf + 3 * i,
"%02X ",
static_cast<uint8_t
>(data[i]));
1607 buf[3 * size - 1] =
'\0';
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
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)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
INDI::PropertySwitch BacklashSP
bool saveConfigItems(FILE *fp)
saveConfigItems Save PAC interface properties to config file. Call this from your driver's saveConfig...
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process PAC number properties.
INDI::PropertyNumber SpeedNP
void initProperties(const char *group)
initProperties Initialize PAC properties. Call this from your driver's initProperties().
void SetCapability(uint32_t cap)
SetCapability sets the PAC device capabilities. Call this in the driver constructor.
INDI::PropertyNumber PositionNP
INDI::PropertyNumber ManualAdjustmentNP
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process PAC switch properties.
bool updateProperties()
updateProperties Define or delete properties based on connection state. Call this from your driver's ...
@ POSITION_ALT
Current altitude position in degrees.
@ POSITION_AZ
Current azimuth position in degrees.
INDI::PropertySwitch ALTReverseSP
INDI::PropertySwitch HomeSP
INDI::PropertySwitch AZReverseSP
INDI::PropertyNumber BacklashNP
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPState state)
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
The MLAstroRPA class drives the MLAstro Robotic Polar Alignment controller via its ESP32-based serial...
IPState GoHome() override
GoHome.
bool updateProperties() override
updateProperties
bool ReverseALT(bool enabled) override
ReverseALT.
bool SetBacklashALT(int32_t steps) override
SetBacklashALT.
bool AbortMotion() override
PACInterface – abort, speed and reverse.
bool initProperties() override
initProperties
void TimerHit() override
TimerHit.
IPState MoveAZ(double degrees) override
PACInterface – axis movement.
bool SyncALT(double degrees) override
SyncALT – same mechanism as SyncAZ.
IPState MoveBoth(double azDegrees, double altDegrees) override
bool saveConfigItems(FILE *fp) override
saveConfigItems
IPState MoveALT(double degrees) override
MoveALT.
bool SyncAZ(double degrees) override
PACInterface – sync.
bool SetHome() override
PACInterface – home management.
double applyCorrectionAZ(double azDegrees) const
Scale a requested azimuth step by the configured correction percentage.
double applyCorrectionALT(double altDegrees) const
applyCorrectionALT – correction percentage, or full error + overshoot
bool ReverseAZ(bool enabled) override
ReverseAZ.
bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
ISNewText.
bool Handshake()
Handshake.
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
ISNewSwitch.
const char * getDefaultName() override
Basic device info.
bool ResetHome() override
ResetHome.
bool SetBacklashAZ(int32_t steps) override
SetBacklashAZ.
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
ISNewNumber.
bool SetBacklashEnabled(bool enabled) override
PACInterface – backlash.
bool SetPACSpeed(uint16_t speed) override
SetPACSpeed (SLvl: 1–5)
Provides interface to implement automated polar alignment correction (PAC).
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Implementations for common driver routines.
#define LOGF_INFO(fmt,...)
#define LOGF_DEBUG(fmt,...)
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)