39static std::unique_ptr<OAPA> oapa(
new OAPA());
42static constexpr char AXIS_LETTER[2] = {
'X',
'Y'};
44static constexpr const char *MOTOR_TAB =
"Motor";
65 AxisSpeedNP[AXIS_AZ].
fill(
"X_SPEED",
"Azimuth (steps/s)",
"%.0f", MIN_SPEED, MAX_SPEED, 50, DEFAULT_SPEED);
66 AxisSpeedNP[AXIS_ALT].
fill(
"Y_SPEED",
"Altitude (steps/s)",
"%.0f", MIN_SPEED, MAX_SPEED, 50, DEFAULT_SPEED);
72 StepsPerArcminNP[AXIS_AZ].
fill(
"STEPS_AZ",
"Azimuth (steps/arcmin)",
"%.2f", 0, 100000, 1, 0);
73 StepsPerArcminNP[AXIS_ALT].
fill(
"STEPS_ALT",
"Altitude (steps/arcmin)",
"%.2f", 0, 100000, 1, 0);
79 MotorCurrentNP[AXIS_AZ].
fill(
"X_CURRENT",
"Azimuth run (mA)",
"%.0f", MIN_RUN_CURRENT, MAX_RUN_CURRENT, 50,
81 MotorCurrentNP[AXIS_ALT].
fill(
"Y_CURRENT",
"Altitude run (mA)",
"%.0f", MIN_RUN_CURRENT, MAX_RUN_CURRENT, 50,
85 MotorHoldNP[AXIS_AZ].
fill(
"X_HOLD",
"Azimuth hold (% of run)",
"%.0f", 0, 100, 5, DEFAULT_HOLD_PERCENT);
86 MotorHoldNP[AXIS_ALT].
fill(
"Y_HOLD",
"Altitude hold (% of run)",
"%.0f", 0, 100, 5, DEFAULT_HOLD_PERCENT);
89 FirmwareTP[0].
fill(
"VERSION",
"Version",
"Unknown");
146 AxisSpeedNP.
save(fp);
147 StepsPerArcminNP.
save(fp);
148 MotorCurrentNP.
save(fp);
149 MotorHoldNP.
save(fp);
162 for (
int attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !found; attempt++)
164 found = queryStatus(status);
171 LOG_ERROR(
"No OAPA status frame received. Check the port and that the OAPA firmware is flashed.");
175 FirmwareTP[0].setText(status.version[0] ? status.version :
"Unknown");
177 int major = 0, minor = 0, patch = 0;
178 const bool versioned = sscanf(status.version,
"%d.%d.%d", &major, &minor, &patch) >= 2;
179 if (!versioned || major < 1 || (major == 1 && (minor < 2 || (minor == 2 && patch < 1))))
180 LOGF_WARN(
"Firmware %s predates 1.2.1: speed setting and Abort are not supported. Please update the firmware.",
181 status.version[0] ? status.version :
"without version");
183 m_MoveActive =
false;
184 updatePosition(status);
186 if (!pushDriverConfig())
187 LOG_WARN(
"Some motor driver settings were not acknowledged by the controller.");
199 if (queryStatus(status))
201 updatePosition(status);
206 if (m_MoveActive && Clock::now() > m_Deadline)
208 char res[DRIVER_LEN] = {0};
209 sendCommand(
"!", res);
210 finishMove(
IPS_ALERT,
"Move timed out and was stopped.");
221 return startMove(degrees, 0);
226 return startMove(0, degrees);
231 return startMove(azDegrees, altDegrees);
234IPState OAPA::startMove(
double azDegrees,
double altDegrees)
238 LOG_ERROR(
"A move is already in progress. Abort it before starting another.");
242 const double azSteps = degreesToSteps(azDegrees, AXIS_AZ);
243 const double altSteps = degreesToSteps(altDegrees, AXIS_ALT);
244 if (std::isnan(azSteps) || std::isnan(altSteps))
247 const long x = std::lround(azSteps);
248 const long y = std::lround(altSteps);
249 if (x == 0 && y == 0)
251 LOG_INFO(
"Requested correction is smaller than one motor step. Nothing to move.");
257 if (!queryStatus(status))
259 LOG_ERROR(
"Cannot read platform status before moving.");
264 LOG_ERROR(
"Platform is already moving. Abort it before starting a correction.");
268 const int speedAz =
static_cast<int>(AxisSpeedNP[AXIS_AZ].getValue());
269 const int speedAlt =
static_cast<int>(AxisSpeedNP[AXIS_ALT].getValue());
273 char cmds[2][DRIVER_LEN] = {{0}};
275 if (x != 0 && y != 0 && speedAz == speedAlt)
276 snprintf(cmds[count++], DRIVER_LEN,
"$J=G91G21X%ldY%ldF%d", x, y, speedAz);
280 snprintf(cmds[count++], DRIVER_LEN,
"$J=G91G21X%ldF%d", x, speedAz);
282 snprintf(cmds[count++], DRIVER_LEN,
"$J=G91G21Y%ldF%d", y, speedAlt);
285 for (
int i = 0; i < count; i++)
287 char res[DRIVER_LEN] = {0};
288 if (!sendCommand(cmds[i], res) || strncmp(res,
"ok", 2) != 0)
290 LOGF_ERROR(
"Jog command %s not acknowledged: %s", cmds[i], res);
294 char stopRes[DRIVER_LEN] = {0};
295 sendCommand(
"!", stopRes);
304 travel = std::max(travel, std::labs(x) /
static_cast<double>(speedAz) + speedAz / ACCELERATION);
306 travel = std::max(travel, std::labs(y) /
static_cast<double>(speedAlt) + speedAlt / ACCELERATION);
308 const auto now = Clock::now();
309 m_Deadline = now + std::chrono::milliseconds(
static_cast<long>((2 * travel + 10) * 1000));
310 m_LastProgress = now;
313 m_TargetX = status.x + x;
314 m_TargetY = status.y + y;
317 LOGF_INFO(
"Correction: AZ %+.4f deg (%ld steps at %d steps/s), ALT %+.4f deg (%ld steps at %d steps/s).",
318 azDegrees, x, speedAz, altDegrees, y, speedAlt);
322double OAPA::degreesToSteps(
double degrees,
int axis)
327 const double stepsPerArcmin = StepsPerArcminNP[axis].getValue();
328 if (stepsPerArcmin <= 0)
330 LOGF_ERROR(
"%s is not calibrated. Set Calibration (steps/arcmin) before correcting.",
331 axis == AXIS_AZ ?
"Azimuth" :
"Altitude");
335 return degrees * 60.0 * stepsPerArcmin * (isReversed(axis) ? -1 : 1);
338bool OAPA::isReversed(
int axis)
346void OAPA::updatePosition(
const Status &status)
348 const double azScale = StepsPerArcminNP[AXIS_AZ].getValue() * 60.0;
349 const double altScale = StepsPerArcminNP[AXIS_ALT].getValue() * 60.0;
351 PositionNP[
POSITION_AZ].setValue(azScale > 0 ? status.x / azScale * (isReversed(AXIS_AZ) ? -1 : 1) : 0);
352 PositionNP[
POSITION_ALT].setValue(altScale > 0 ? status.y / altScale * (isReversed(AXIS_ALT) ? -1 : 1) : 0);
357void OAPA::checkMove(
const Status &status)
362 const auto now = Clock::now();
366 if (std::fabs(status.x - m_LastX) >= POSITION_TOLERANCE || std::fabs(status.y - m_LastY) >= POSITION_TOLERANCE)
370 m_LastProgress = now;
372 else if (std::chrono::duration<double>(now - m_LastProgress).count() > STALL_SECONDS)
374 char res[DRIVER_LEN] = {0};
375 sendCommand(
"!", res);
376 finishMove(
IPS_ALERT,
"Platform reports motion but its position is not changing. Stopped.");
381 if (std::fabs(status.x - m_TargetX) <= POSITION_TOLERANCE && std::fabs(status.y - m_TargetY) <= POSITION_TOLERANCE)
382 finishMove(
IPS_OK,
"Correction move complete.");
385 LOGF_WARN(
"Platform stopped at (%.0f, %.0f) steps, target was (%.0f, %.0f).",
386 status.x, status.y, m_TargetX, m_TargetY);
387 finishMove(
IPS_ALERT,
"Move ended before reaching the target.");
391void OAPA::finishMove(
IPState state,
const char *message)
393 m_MoveActive =
false;
405 m_MoveActive =
false;
407 char res[DRIVER_LEN] = {0};
408 if (!sendCommand(
"!", res))
417 LOGF_INFO(
"Azimuth direction reverse %s.", enabled ?
"enabled" :
"disabled");
423 LOGF_INFO(
"Altitude direction reverse %s.", enabled ?
"enabled" :
"disabled");
433 AxisSpeedNP.
update(values, names, n);
444 LOG_ERROR(
"Cannot change calibration while a move is in progress.");
446 StepsPerArcminNP.
apply();
449 StepsPerArcminNP.
update(values, names, n);
451 StepsPerArcminNP.
apply();
458 const bool isRun = MotorCurrentNP.
isNameMatch(name);
459 auto &np = isRun ? MotorCurrentNP : MotorHoldNP;
460 np.
update(values, names, n);
465 for (
int axis = AXIS_AZ; axis <= AXIS_ALT; axis++)
466 ok = sendDriverSetting(isRun ?
'C' :
'H', axis, np[axis].getValue()) && ok;
495bool OAPA::pushDriverConfig()
500 for (
int axis = AXIS_AZ; axis <= AXIS_ALT; axis++)
502 ok = sendDriverSetting(
'C', axis, MotorCurrentNP[axis].getValue()) && ok;
503 ok = sendDriverSetting(
'H', axis, MotorHoldNP[axis].getValue()) && ok;
508bool OAPA::sendDriverSetting(
char type,
int axis,
double value)
512 char cmd[DRIVER_LEN] = {0};
513 snprintf(
cmd, DRIVER_LEN,
"%c%c%ld",
type, AXIS_LETTER[axis], std::lround(value));
515 char res[DRIVER_LEN] = {0};
516 if (!sendCommand(
cmd, res))
518 if (strncmp(res,
"ok", 2) != 0)
529void OAPA::drainInput()
535 tcflush(PortFD, TCIFLUSH);
539 char discard[DRIVER_LEN] = {0};
546bool OAPA::sendCommand(
const char *
cmd,
char *res)
548 int nbytes_written = 0, nbytes_read = 0;
555 char formatted[DRIVER_LEN] = {0};
556 snprintf(formatted, DRIVER_LEN,
"%s\n",
cmd);
566 rc =
tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
575 res[std::min(nbytes_read, DRIVER_LEN - 1)] =
'\0';
576 res[strcspn(res,
"\r\n")] =
'\0';
581bool OAPA::queryStatus(Status &status)
583 char line[DRIVER_LEN] = {0};
584 if (!sendCommand(
"?", line))
589 for (
int extra = 0; !parseStatus(line, status); extra++)
593 line[std::min(nbytes_read, DRIVER_LEN - 1)] =
'\0';
594 line[strcspn(line,
"\r\n")] =
'\0';
599 char ok[DRIVER_LEN] = {0};
601 LOG_DEBUG(
"Status frame was not followed by ok.");
606bool OAPA::parseStatus(
const char *line, Status &status)
608 const char *start = strchr(line,
'<');
609 if (start ==
nullptr)
612 const char *mpos = strstr(start,
"|MPos:");
614 if (mpos ==
nullptr || sscanf(mpos + 6,
"%lf,%lf", &x, &y) != 2)
617 status.moving = strncmp(start + 1,
"Idle", 4) != 0;
620 status.version[0] =
'\0';
622 const char *version = strstr(start,
"|V:");
623 if (version !=
nullptr)
624 sscanf(version + 3,
"%15[0-9.]", status.version);
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...
The TCP class manages connection with devices over the network via TCP/IP. Upon successful connection...
void setDefaultPort(uint32_t addressPort)
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.
Connection::Interface * getActiveConnection()
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
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.
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 AZReverseSP
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
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)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
The OAPA class drives an Open Automatic Polar Alignment platform running the OAPA reference firmware ...
bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
bool ReverseALT(bool enabled) override
ReverseALT Reverse the direction of altitude axis movement.
bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
IPState MoveAZ(double degrees) override
Motion.
IPState MoveALT(double degrees) override
MoveALT Move the altitude axis by the given number of degrees.
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
bool AbortMotion() override
AbortMotion Abort all axis motion immediately.
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
bool ReverseAZ(bool enabled) override
ReverseAZ Reverse the direction of azimuth axis movement.
IPState MoveBoth(double azDegrees, double altDegrees) override
MoveBoth Move both axes in a single coordinated operation.
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
const char * getDefaultName() override
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 * INFO_TAB
INFO_TAB Where all the properties for general information are located.
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_WARN(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,...)