Instrument Neutral Distributed Interface INDI 2.2.5
oapa.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 OAPA - Open Automatic Polar Alignment
3
4 Copyright (C) 2026 Michele Bergo
5
6 OAPA wire protocol (GRBL-style text lines, 115200 8N1).
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21*******************************************************************************/
22
23#include "oapa.h"
24
25#include "indicom.h"
26#include "indilogger.h"
29
30#include <algorithm>
31#include <cmath>
32#include <cstdio>
33#include <cstdlib>
34#include <cstring>
35#include <memory>
36#include <termios.h>
37#include <unistd.h>
38
39static std::unique_ptr<OAPA> oapa(new OAPA());
40
41// Firmware axis letters, indexed by AXIS_AZ / AXIS_ALT: X drives azimuth, Y altitude.
42static constexpr char AXIS_LETTER[2] = {'X', 'Y'};
43
44static constexpr const char *MOTOR_TAB = "Motor";
45
52
54{
55 return "OAPA";
56}
57
59{
61
63
64 // Speed per axis, sent as the F feed of each jog (the firmware clamps it to 50-3000).
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);
67 AxisSpeedNP.fill(getDeviceName(), "OAPA_SPEED", "Speed", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
68
69 // Calibration: motor steps per arcminute of correction. No default can be right,
70 // since gear reductions range from ~15 to ~1000 steps/arcmin, so moves are refused
71 // until the user sets it.
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);
74 StepsPerArcminNP.fill(getDeviceName(), "OAPA_STEPS_PER_ARCMIN", "Calibration", MAIN_CONTROL_TAB, IP_RW, 60,
75 IPS_IDLE);
76
77 // Driver currents. Defaults match the firmware's own, so a controller that has just
78 // been powered on is left as it is until the user edits them.
79 MotorCurrentNP[AXIS_AZ].fill("X_CURRENT", "Azimuth run (mA)", "%.0f", MIN_RUN_CURRENT, MAX_RUN_CURRENT, 50,
80 DEFAULT_RUN_CURRENT);
81 MotorCurrentNP[AXIS_ALT].fill("Y_CURRENT", "Altitude run (mA)", "%.0f", MIN_RUN_CURRENT, MAX_RUN_CURRENT, 50,
82 DEFAULT_RUN_CURRENT);
83 MotorCurrentNP.fill(getDeviceName(), "OAPA_MOTOR_CURRENT", "Run Current", MOTOR_TAB, IP_RW, 60, IPS_IDLE);
84
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);
87 MotorHoldNP.fill(getDeviceName(), "OAPA_MOTOR_HOLD", "Hold Current", MOTOR_TAB, IP_RW, 60, IPS_IDLE);
88
89 FirmwareTP[0].fill("VERSION", "Version", "Unknown");
90 FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", INFO_TAB, IP_RO, 60, IPS_IDLE);
91
93
94 serialConnection = new Connection::Serial(this);
95 serialConnection->registerHandshake([&]()
96 {
97 return Handshake();
98 });
100 registerConnection(serialConnection);
101
102 // Firmware >= 1.3.0 also speaks the same protocol over TCP, for boards on WiFi.
103 tcpConnection = new Connection::TCP(this);
104 tcpConnection->registerHandshake([&]()
105 {
106 return Handshake();
107 });
108 // No default address: from firmware 1.3.6 each board answers to a name of its
109 // own, oapa-<mac>.local, so a fixed default would point at the wrong board or
110 // at nothing. The port is the same for every board.
111 tcpConnection->setDefaultPort(DEFAULT_TCP_PORT);
112 registerConnection(tcpConnection);
113
115 return true;
116}
117
119{
121
122 if (isConnected())
123 {
124 defineProperty(AxisSpeedNP);
125 defineProperty(StepsPerArcminNP);
126 defineProperty(MotorCurrentNP);
127 defineProperty(MotorHoldNP);
128 defineProperty(FirmwareTP);
129 }
130 else
131 {
132 deleteProperty(AxisSpeedNP);
133 deleteProperty(StepsPerArcminNP);
134 deleteProperty(MotorCurrentNP);
135 deleteProperty(MotorHoldNP);
136 deleteProperty(FirmwareTP);
137 }
138
140 return true;
141}
142
144{
146 AxisSpeedNP.save(fp);
147 StepsPerArcminNP.save(fp);
148 MotorCurrentNP.save(fp);
149 MotorHoldNP.save(fp);
151 return true;
152}
153
155{
156 PortFD = (getActiveConnection() == tcpConnection) ? tcpConnection->getPortFD() : serialConnection->getPortFD();
157
158 // Opening the serial port resets most ESP32 boards: the firmware prints a boot
159 // banner and then waits silently. Probe until a status frame answers.
160 Status status;
161 bool found = false;
162 for (int attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !found; attempt++)
163 {
164 found = queryStatus(status);
165 if (!found)
166 usleep(500000);
167 }
168
169 if (!found)
170 {
171 LOG_ERROR("No OAPA status frame received. Check the port and that the OAPA firmware is flashed.");
172 return false;
173 }
174
175 FirmwareTP[0].setText(status.version[0] ? status.version : "Unknown");
176
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");
182
183 m_MoveActive = false;
184 updatePosition(status);
185
186 if (!pushDriverConfig())
187 LOG_WARN("Some motor driver settings were not acknowledged by the controller.");
188
189 LOGF_INFO("OAPA connected (firmware %s).", FirmwareTP[0].getText());
190 return true;
191}
192
194{
195 if (!isConnected())
196 return;
197
198 Status status;
199 if (queryStatus(status))
200 {
201 updatePosition(status);
202 checkMove(status);
203 }
204
205 // The deadline also covers a link that stopped answering mid-move.
206 if (m_MoveActive && Clock::now() > m_Deadline)
207 {
208 char res[DRIVER_LEN] = {0};
209 sendCommand("!", res);
210 finishMove(IPS_ALERT, "Move timed out and was stopped.");
211 }
212
214}
215
219IPState OAPA::MoveAZ(double degrees)
220{
221 return startMove(degrees, 0);
222}
223
224IPState OAPA::MoveALT(double degrees)
225{
226 return startMove(0, degrees);
227}
228
229IPState OAPA::MoveBoth(double azDegrees, double altDegrees)
230{
231 return startMove(azDegrees, altDegrees);
232}
233
234IPState OAPA::startMove(double azDegrees, double altDegrees)
235{
236 if (m_MoveActive)
237 {
238 LOG_ERROR("A move is already in progress. Abort it before starting another.");
239 return IPS_ALERT;
240 }
241
242 const double azSteps = degreesToSteps(azDegrees, AXIS_AZ);
243 const double altSteps = degreesToSteps(altDegrees, AXIS_ALT);
244 if (std::isnan(azSteps) || std::isnan(altSteps))
245 return IPS_ALERT;
246
247 const long x = std::lround(azSteps);
248 const long y = std::lround(altSteps);
249 if (x == 0 && y == 0)
250 {
251 LOG_INFO("Requested correction is smaller than one motor step. Nothing to move.");
252 return IPS_OK;
253 }
254
255 // Read the starting position fresh, so the target is exact.
256 Status status;
257 if (!queryStatus(status))
258 {
259 LOG_ERROR("Cannot read platform status before moving.");
260 return IPS_ALERT;
261 }
262 if (status.moving)
263 {
264 LOG_ERROR("Platform is already moving. Abort it before starting a correction.");
265 return IPS_ALERT;
266 }
267
268 const int speedAz = static_cast<int>(AxisSpeedNP[AXIS_AZ].getValue());
269 const int speedAlt = static_cast<int>(AxisSpeedNP[AXIS_ALT].getValue());
270
271 // The firmware takes one feed per jog command. With equal speeds both axes share one
272 // command; otherwise each axis gets its own jog with its own feed.
273 char cmds[2][DRIVER_LEN] = {{0}};
274 int count = 0;
275 if (x != 0 && y != 0 && speedAz == speedAlt)
276 snprintf(cmds[count++], DRIVER_LEN, "$J=G91G21X%ldY%ldF%d", x, y, speedAz);
277 else
278 {
279 if (x != 0)
280 snprintf(cmds[count++], DRIVER_LEN, "$J=G91G21X%ldF%d", x, speedAz);
281 if (y != 0)
282 snprintf(cmds[count++], DRIVER_LEN, "$J=G91G21Y%ldF%d", y, speedAlt);
283 }
284
285 for (int i = 0; i < count; i++)
286 {
287 char res[DRIVER_LEN] = {0};
288 if (!sendCommand(cmds[i], res) || strncmp(res, "ok", 2) != 0)
289 {
290 LOGF_ERROR("Jog command %s not acknowledged: %s", cmds[i], res);
291 // An axis that already started must not keep moving on its own.
292 if (i > 0)
293 {
294 char stopRes[DRIVER_LEN] = {0};
295 sendCommand("!", stopRes);
296 }
297 return IPS_ALERT;
298 }
299 }
300
301 // Travel time of the slower axis plus its acceleration ramp, doubled, plus slack.
302 double travel = 0;
303 if (x != 0)
304 travel = std::max(travel, std::labs(x) / static_cast<double>(speedAz) + speedAz / ACCELERATION);
305 if (y != 0)
306 travel = std::max(travel, std::labs(y) / static_cast<double>(speedAlt) + speedAlt / ACCELERATION);
307
308 const auto now = Clock::now();
309 m_Deadline = now + std::chrono::milliseconds(static_cast<long>((2 * travel + 10) * 1000));
310 m_LastProgress = now;
311 m_LastX = status.x;
312 m_LastY = status.y;
313 m_TargetX = status.x + x;
314 m_TargetY = status.y + y;
315 m_MoveActive = true;
316
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);
319 return IPS_BUSY;
320}
321
322double OAPA::degreesToSteps(double degrees, int axis)
323{
324 if (degrees == 0)
325 return 0;
326
327 const double stepsPerArcmin = StepsPerArcminNP[axis].getValue();
328 if (stepsPerArcmin <= 0)
329 {
330 LOGF_ERROR("%s is not calibrated. Set Calibration (steps/arcmin) before correcting.",
331 axis == AXIS_AZ ? "Azimuth" : "Altitude");
332 return NAN;
333 }
334
335 return degrees * 60.0 * stepsPerArcmin * (isReversed(axis) ? -1 : 1);
336}
337
338bool OAPA::isReversed(int axis)
339{
340 // Read the switch itself rather than a cached flag, so a value restored from the
341 // config file is honoured however it was applied.
342 auto &sp = (axis == AXIS_AZ) ? AZReverseSP : ALTReverseSP;
343 return sp[INDI_ENABLED].getState() == ISS_ON;
344}
345
346void OAPA::updatePosition(const Status &status)
347{
348 const double azScale = StepsPerArcminNP[AXIS_AZ].getValue() * 60.0;
349 const double altScale = StepsPerArcminNP[AXIS_ALT].getValue() * 60.0;
350
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);
353 PositionNP.setState(status.moving ? IPS_BUSY : IPS_OK);
355}
356
357void OAPA::checkMove(const Status &status)
358{
359 if (!m_MoveActive)
360 return;
361
362 const auto now = Clock::now();
363
364 if (status.moving)
365 {
366 if (std::fabs(status.x - m_LastX) >= POSITION_TOLERANCE || std::fabs(status.y - m_LastY) >= POSITION_TOLERANCE)
367 {
368 m_LastX = status.x;
369 m_LastY = status.y;
370 m_LastProgress = now;
371 }
372 else if (std::chrono::duration<double>(now - m_LastProgress).count() > STALL_SECONDS)
373 {
374 char res[DRIVER_LEN] = {0};
375 sendCommand("!", res);
376 finishMove(IPS_ALERT, "Platform reports motion but its position is not changing. Stopped.");
377 }
378 return;
379 }
380
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.");
383 else
384 {
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.");
388 }
389}
390
391void OAPA::finishMove(IPState state, const char *message)
392{
393 m_MoveActive = false;
396 if (state == IPS_OK)
397 LOGF_INFO("%s", message);
398 else
399 LOGF_ERROR("%s", message);
400}
401
403{
404 // A stale move must not complete as OK after an abort.
405 m_MoveActive = false;
406
407 char res[DRIVER_LEN] = {0};
408 if (!sendCommand("!", res))
409 return false;
410
411 LOG_INFO("OAPA motion aborted.");
412 return true;
413}
414
415bool OAPA::ReverseAZ(bool enabled)
416{
417 LOGF_INFO("Azimuth direction reverse %s.", enabled ? "enabled" : "disabled");
418 return true;
419}
420
421bool OAPA::ReverseALT(bool enabled)
422{
423 LOGF_INFO("Altitude direction reverse %s.", enabled ? "enabled" : "disabled");
424 return true;
425}
426
427bool OAPA::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
428{
429 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
430 {
431 if (AxisSpeedNP.isNameMatch(name))
432 {
433 AxisSpeedNP.update(values, names, n);
434 AxisSpeedNP.setState(IPS_OK);
435 AxisSpeedNP.apply();
436 saveConfig(AxisSpeedNP);
437 return true;
438 }
439
440 if (StepsPerArcminNP.isNameMatch(name))
441 {
442 if (m_MoveActive)
443 {
444 LOG_ERROR("Cannot change calibration while a move is in progress.");
445 StepsPerArcminNP.setState(IPS_ALERT);
446 StepsPerArcminNP.apply();
447 return true;
448 }
449 StepsPerArcminNP.update(values, names, n);
450 StepsPerArcminNP.setState(IPS_OK);
451 StepsPerArcminNP.apply();
452 saveConfig(StepsPerArcminNP);
453 return true;
454 }
455
456 if (MotorCurrentNP.isNameMatch(name) || MotorHoldNP.isNameMatch(name))
457 {
458 const bool isRun = MotorCurrentNP.isNameMatch(name);
459 auto &np = isRun ? MotorCurrentNP : MotorHoldNP;
460 np.update(values, names, n);
461
462 bool ok = true;
463 if (isConnected())
464 {
465 for (int axis = AXIS_AZ; axis <= AXIS_ALT; axis++)
466 ok = sendDriverSetting(isRun ? 'C' : 'H', axis, np[axis].getValue()) && ok;
467 }
468 np.setState(ok ? IPS_OK : IPS_ALERT);
469 np.apply();
470 saveConfig(np);
471 return true;
472 }
473
474 if (PACI::processNumber(dev, name, values, names, n))
475 return true;
476 }
477
478 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
479}
480
481bool OAPA::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
482{
483 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
484 {
485 if (PACI::processSwitch(dev, name, states, names, n))
486 return true;
487 }
488
489 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
490}
491
495bool OAPA::pushDriverConfig()
496{
497 // The controller forgets driver settings at power-off, so they are sent on every
498 // connection: run then hold, azimuth first, as the N.I.N.A. plugin does.
499 bool ok = true;
500 for (int axis = AXIS_AZ; axis <= AXIS_ALT; axis++)
501 {
502 ok = sendDriverSetting('C', axis, MotorCurrentNP[axis].getValue()) && ok;
503 ok = sendDriverSetting('H', axis, MotorHoldNP[axis].getValue()) && ok;
504 }
505 return ok;
506}
507
508bool OAPA::sendDriverSetting(char type, int axis, double value)
509{
510 // Type letter first, then the axis: "CX600", "HY25". The firmware acknowledges any
511 // other shape (e.g. "XC600") with "ok" and ignores it, so the order matters.
512 char cmd[DRIVER_LEN] = {0};
513 snprintf(cmd, DRIVER_LEN, "%c%c%ld", type, AXIS_LETTER[axis], std::lround(value));
514
515 char res[DRIVER_LEN] = {0};
516 if (!sendCommand(cmd, res))
517 return false;
518 if (strncmp(res, "ok", 2) != 0)
519 {
520 LOGF_WARN("Unexpected reply to %s: %s", cmd, res);
521 return false;
522 }
523 return true;
524}
525
529void OAPA::drainInput()
530{
531 // tcflush only works on a terminal, so a socket is drained by reading what is
532 // already buffered; a zero timeout returns at once when nothing is waiting.
533 if (getActiveConnection() != tcpConnection)
534 {
535 tcflush(PortFD, TCIFLUSH);
536 return;
537 }
538
539 char discard[DRIVER_LEN] = {0};
540 int nbytes_read = 0;
541 while (tty_nread_section(PortFD, discard, DRIVER_LEN, DRIVER_STOP_CHAR, 0, &nbytes_read) == TTY_OK
542 && nbytes_read > 0)
543 ;
544}
545
546bool OAPA::sendCommand(const char *cmd, char *res)
547{
548 int nbytes_written = 0, nbytes_read = 0;
549
550 // Drop anything unsolicited (boot banner, homing chatter) so the reply we read
551 // belongs to this command.
552 drainInput();
553
554 LOGF_DEBUG("CMD <%s>", cmd);
555 char formatted[DRIVER_LEN] = {0};
556 snprintf(formatted, DRIVER_LEN, "%s\n", cmd);
557 int rc = tty_write_string(PortFD, formatted, &nbytes_written);
558 if (rc != TTY_OK)
559 {
560 char errstr[MAXRBUF] = {0};
561 tty_error_msg(rc, errstr, MAXRBUF);
562 LOGF_ERROR("Write error: %s.", errstr);
563 return false;
564 }
565
566 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
567 if (rc != TTY_OK)
568 {
569 char errstr[MAXRBUF] = {0};
570 tty_error_msg(rc, errstr, MAXRBUF);
571 LOGF_ERROR("Read error: %s.", errstr);
572 return false;
573 }
574
575 res[std::min(nbytes_read, DRIVER_LEN - 1)] = '\0';
576 res[strcspn(res, "\r\n")] = '\0';
577 LOGF_DEBUG("RES <%s>", res);
578 return true;
579}
580
581bool OAPA::queryStatus(Status &status)
582{
583 char line[DRIVER_LEN] = {0};
584 if (!sendCommand("?", line))
585 return false;
586
587 // The first line may still be a banner line printed just before the query.
588 int nbytes_read = 0;
589 for (int extra = 0; !parseStatus(line, status); extra++)
590 {
591 if (extra >= 4 || tty_nread_section(PortFD, line, DRIVER_LEN, DRIVER_STOP_CHAR, 1, &nbytes_read) != TTY_OK)
592 return false;
593 line[std::min(nbytes_read, DRIVER_LEN - 1)] = '\0';
594 line[strcspn(line, "\r\n")] = '\0';
595 }
596
597 // "?" always answers with two lines. Consume the "ok" so it cannot be read as the
598 // reply to the next command.
599 char ok[DRIVER_LEN] = {0};
600 if (tty_nread_section(PortFD, ok, DRIVER_LEN, DRIVER_STOP_CHAR, 1, &nbytes_read) != TTY_OK)
601 LOG_DEBUG("Status frame was not followed by ok.");
602
603 return true;
604}
605
606bool OAPA::parseStatus(const char *line, Status &status)
607{
608 const char *start = strchr(line, '<');
609 if (start == nullptr)
610 return false;
611
612 const char *mpos = strstr(start, "|MPos:");
613 double x = 0, y = 0;
614 if (mpos == nullptr || sscanf(mpos + 6, "%lf,%lf", &x, &y) != 2)
615 return false;
616
617 status.moving = strncmp(start + 1, "Idle", 4) != 0;
618 status.x = x;
619 status.y = y;
620 status.version[0] = '\0';
621
622 const char *version = strstr(start, "|V:");
623 if (version != nullptr)
624 sscanf(version + 3, "%15[0-9.]", status.version);
625
626 return true;
627}
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)
int getPortFD() const
bool isConnected() const
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 ...
Definition oapa.h:63
bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
Definition oapa.cpp:143
bool ReverseALT(bool enabled) override
ReverseALT Reverse the direction of altitude axis movement.
Definition oapa.cpp:421
bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition oapa.cpp:58
OAPA()
Definition oapa.cpp:46
IPState MoveAZ(double degrees) override
Motion.
Definition oapa.cpp:219
bool Handshake()
Definition oapa.cpp:154
IPState MoveALT(double degrees) override
MoveALT Move the altitude axis by the given number of degrees.
Definition oapa.cpp:224
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition oapa.cpp:481
bool AbortMotion() override
AbortMotion Abort all axis motion immediately.
Definition oapa.cpp:402
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition oapa.cpp:193
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition oapa.cpp:427
bool ReverseAZ(bool enabled) override
ReverseAZ Reverse the direction of azimuth axis movement.
Definition oapa.cpp:415
IPState MoveBoth(double azDegrees, double altDegrees) override
MoveBoth Move both axes in a single coordinated operation.
Definition oapa.cpp:229
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition oapa.cpp:118
const char * getDefaultName() override
Definition oapa.cpp:53
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.
ISState
Switch state.
Definition indiapi.h:148
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
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
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
uint16_t type
Definition pwc-ioctl.h:0
uint8_t cmd[4]
Definition pwc-ioctl.h:2