Instrument Neutral Distributed Interface INDI 2.2.5
indipacinterface.cpp
Go to the documentation of this file.
1/*
2 PAC Interface (Polar Alignment Correction)
3 Copyright (C) 2026 Joaquin Rodriguez ([email protected])
4 Copyright (C) 2026 Jasem Mutlaq ([email protected])
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20*/
21
22#include "indipacinterface.h"
23#include "defaultdevice.h"
24#include "indilogger.h"
25
26#include <cstring>
27
28namespace INDI
29{
30
32 : m_DefaultDevice(defaultDevice)
33{
34}
35
36void PACInterface::initProperties(const char *group)
37{
38 // Manual adjustment: write non-zero value to move that axis immediately.
39 // AZ step: positive = East, negative = West.
40 // ALT step: positive = North (increase altitude), negative = South (decrease altitude).
41 ManualAdjustmentNP[MANUAL_AZ].fill("MANUAL_AZ_STEP", "Azimuth Step (deg)", "%.4f", -10, 10, 0.1, 0);
42 ManualAdjustmentNP[MANUAL_ALT].fill("MANUAL_ALT_STEP", "Altitude Step (deg)", "%.4f", -10, 10, 0.1, 0);
43 ManualAdjustmentNP.fill(m_DefaultDevice->getDeviceName(), "PAC_MANUAL_ADJUSTMENT", "Manual Adjustment",
44 group, IP_WO, 0, IPS_IDLE);
45
46 // Abort motion
47 AbortSP[0].fill("ABORT", "Abort", ISS_OFF);
48 AbortSP.fill(m_DefaultDevice->getDeviceName(), "PAC_ABORT_MOTION", "Abort Motion",
49 group, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
50
51 // Current position (only shown when PAC_HAS_POSITION is set)
52 PositionNP[POSITION_AZ].fill("POSITION_AZ", "Azimuth (deg)", "%.4f", -360, 360, 0, 0);
53 PositionNP[POSITION_ALT].fill("POSITION_ALT", "Altitude (deg)", "%.4f", -90, 90, 0, 0);
54 PositionNP.fill(m_DefaultDevice->getDeviceName(), "PAC_POSITION", "Position",
55 group, IP_RO, 0, IPS_IDLE);
56
57 // Motor speed (only shown when PAC_HAS_SPEED is set)
58 SpeedNP[0].fill("PAC_SPEED_VALUE", "Speed", "%.0f", 1, 10, 1, 1);
59 SpeedNP.fill(m_DefaultDevice->getDeviceName(), "PAC_SPEED", "Speed", group, IP_RW, 60, IPS_OK);
60
61 // Azimuth reverse (only shown when PAC_CAN_REVERSE is set)
62 AZReverseSP[DefaultDevice::INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_OFF);
63 AZReverseSP[DefaultDevice::INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_ON);
64 AZReverseSP.fill(m_DefaultDevice->getDeviceName(), "PAC_AZ_REVERSE", "Azimuth Reverse", group, IP_RW,
66
67 // Altitude reverse (only shown when PAC_CAN_REVERSE is set)
68 ALTReverseSP[DefaultDevice::INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_OFF);
69 ALTReverseSP[DefaultDevice::INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_ON);
70 ALTReverseSP.fill(m_DefaultDevice->getDeviceName(), "PAC_ALT_REVERSE", "Altitude Reverse", group, IP_RW,
72
73 // Home management (only shown when PAC_CAN_HOME is set)
74 HomeSP[HOME_SET].fill("HOME_SET", "Set Home", ISS_OFF);
75 HomeSP[HOME_GO].fill("HOME_GO", "Return Home", ISS_OFF);
76 HomeSP[HOME_RESET].fill("HOME_RESET", "Reset Home", ISS_OFF);
77 HomeSP.fill(m_DefaultDevice->getDeviceName(), "PAC_HOME", "Home", group, IP_RW,
79
80 // Sync position (only shown when PAC_CAN_SYNC is set)
81 SyncNP[SYNC_AZ].fill("SYNC_AZ", "Azimuth (deg)", "%.4f", -360, 360, 0, 0);
82 SyncNP[SYNC_ALT].fill("SYNC_ALT", "Altitude (deg)", "%.4f", -90, 90, 0, 0);
83 SyncNP.fill(m_DefaultDevice->getDeviceName(), "PAC_SYNC", "Sync Position",
84 group, IP_RW, 60, IPS_IDLE);
85
86 // Backlash enable/disable (only shown when PAC_HAS_BACKLASH is set)
87 BacklashSP[DefaultDevice::INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_OFF);
88 BacklashSP[DefaultDevice::INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_ON);
89 BacklashSP.fill(m_DefaultDevice->getDeviceName(), "PAC_BACKLASH_ENABLED", "Backlash", group, IP_RW,
91
92 // Backlash steps (only shown when PAC_HAS_BACKLASH is set)
93 BacklashNP[BACKLASH_AZ].fill("BACKLASH_AZ", "Azimuth (steps)", "%.0f", 0, 10000, 1, 0);
94 BacklashNP[BACKLASH_ALT].fill("BACKLASH_ALT", "Altitude (steps)", "%.0f", 0, 10000, 1, 0);
95 BacklashNP.fill(m_DefaultDevice->getDeviceName(), "PAC_BACKLASH_STEPS", "Backlash Steps",
96 group, IP_RW, 60, IPS_IDLE);
97}
98
100{
101 if (m_DefaultDevice->isConnected())
102 {
103 m_DefaultDevice->defineProperty(ManualAdjustmentNP);
104 m_DefaultDevice->defineProperty(AbortSP);
105 if (HasPosition())
106 m_DefaultDevice->defineProperty(PositionNP);
107 if (HasSpeed())
108 m_DefaultDevice->defineProperty(SpeedNP);
109 if (CanReverse())
110 {
111 m_DefaultDevice->defineProperty(AZReverseSP);
112 m_DefaultDevice->defineProperty(ALTReverseSP);
113 }
114 if (CanHome())
115 m_DefaultDevice->defineProperty(HomeSP);
116 if (CanSync())
117 m_DefaultDevice->defineProperty(SyncNP);
118 if (HasBacklash())
119 {
120 m_DefaultDevice->defineProperty(BacklashSP);
121 m_DefaultDevice->defineProperty(BacklashNP);
122 }
123 }
124 else
125 {
126 m_DefaultDevice->deleteProperty(ManualAdjustmentNP);
127 m_DefaultDevice->deleteProperty(AbortSP);
128 if (HasPosition())
129 m_DefaultDevice->deleteProperty(PositionNP);
130 if (HasSpeed())
131 m_DefaultDevice->deleteProperty(SpeedNP);
132 if (CanReverse())
133 {
134 m_DefaultDevice->deleteProperty(AZReverseSP);
135 m_DefaultDevice->deleteProperty(ALTReverseSP);
136 }
137 if (CanHome())
138 m_DefaultDevice->deleteProperty(HomeSP);
139 if (CanSync())
140 m_DefaultDevice->deleteProperty(SyncNP);
141 if (HasBacklash())
142 {
143 m_DefaultDevice->deleteProperty(BacklashSP);
144 m_DefaultDevice->deleteProperty(BacklashNP);
145 }
146 }
147
148 return true;
149}
150
151bool PACInterface::processSwitch(const char *dev, const char *name,
152 ISState *states, char *names[], int n)
153{
154 INDI_UNUSED(dev);
155
156 if (AbortSP.isNameMatch(name))
157 {
158 AbortSP.reset();
159 if (AbortMotion())
160 {
163 {
166 }
168 {
171 }
172 }
173 else
174 {
176 }
177 AbortSP.apply();
178 return true;
179 }
180
181 if (AZReverseSP.isNameMatch(name))
182 {
183 m_DefaultDevice->updateProperty(AZReverseSP, states, names, n, [this, names]()
184 {
185 bool enabled = AZReverseSP[DefaultDevice::INDI_ENABLED].isNameMatch(names[0]) == ISS_ON;
186 return ReverseAZ(enabled);
187 }, true);
188 return true;
189 }
190
191 if (ALTReverseSP.isNameMatch(name))
192 {
193 m_DefaultDevice->updateProperty(ALTReverseSP, states, names, n, [this, names]()
194 {
195 bool enabled = ALTReverseSP[DefaultDevice::INDI_ENABLED].isNameMatch(names[0]) == ISS_ON;
196 return ReverseALT(enabled);
197 }, true);
198 return true;
199 }
200
201 if (CanHome() && HomeSP.isNameMatch(name))
202 {
203 HomeSP.update(states, names, n);
205 HomeSP.reset();
206
207 if (cmd == HOME_SET)
208 {
209 if (SetHome())
210 {
212 DEBUGDEVICE(m_DefaultDevice->getDeviceName(), INDI::Logger::DBG_SESSION, "Home position set.");
213 }
214 else
215 {
217 DEBUGDEVICE(m_DefaultDevice->getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to set home position.");
218 }
219 }
220 else if (cmd == HOME_GO)
221 {
222 IPState rc = GoHome();
223 HomeSP.setState(rc);
224 if (rc == IPS_BUSY)
225 DEBUGDEVICE(m_DefaultDevice->getDeviceName(), INDI::Logger::DBG_SESSION, "Returning to home position...");
226 else if (rc == IPS_OK)
227 DEBUGDEVICE(m_DefaultDevice->getDeviceName(), INDI::Logger::DBG_SESSION, "Returned to home position.");
228 else
229 DEBUGDEVICE(m_DefaultDevice->getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to return to home position.");
230 }
231 else if (cmd == HOME_RESET)
232 {
233 if (ResetHome())
234 {
236 DEBUGDEVICE(m_DefaultDevice->getDeviceName(), INDI::Logger::DBG_SESSION, "Home position reset.");
237 }
238 else
239 {
241 DEBUGDEVICE(m_DefaultDevice->getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to reset home position.");
242 }
243 }
244
245 HomeSP.apply();
246 return true;
247 }
248
249 if (HasBacklash() && BacklashSP.isNameMatch(name))
250 {
251 m_DefaultDevice->updateProperty(BacklashSP, states, names, n, [this, names]()
252 {
253 bool enabled = BacklashSP[DefaultDevice::INDI_ENABLED].isNameMatch(names[0]) == ISS_ON;
254 return SetBacklashEnabled(enabled);
255 }, true);
256 return true;
257 }
258
259 return false;
260}
261
262bool PACInterface::processNumber(const char *dev, const char *name,
263 double values[], char *names[], int n)
264{
265 INDI_UNUSED(dev);
266
267 if (SpeedNP.isNameMatch(name))
268 {
269 m_DefaultDevice->updateProperty(SpeedNP, values, names, n, [this, values]()
270 {
271 return SetPACSpeed(static_cast<uint16_t>(values[0]));
272 }, true);
273 return true;
274 }
275
277 {
278 ManualAdjustmentNP.update(values, names, n);
279
280 const double azStep = ManualAdjustmentNP[MANUAL_AZ].getValue();
281 const double altStep = ManualAdjustmentNP[MANUAL_ALT].getValue();
282
283 IPState azState = IPS_OK;
284 IPState altState = IPS_OK;
285
286 if (azStep != 0.0 && altStep != 0.0)
287 {
288 // Both axes requested — delegate to the driver's coordinated move
289 IPState state = MoveBoth(azStep, altStep);
290 azState = altState = state;
291 }
292 else
293 {
294 if (azStep != 0.0) azState = MoveAZ(azStep);
295 if (altStep != 0.0) altState = MoveALT(altStep);
296 }
297
298 // Overall state: worst of the two axes.
299 if (azState == IPS_ALERT || altState == IPS_ALERT)
301 else if (azState == IPS_BUSY || altState == IPS_BUSY)
303 else
305
307 return true;
308 }
309
310 if (CanSync() && SyncNP.isNameMatch(name))
311 {
312 m_DefaultDevice->updateProperty(SyncNP, values, names, n, [this, values, names, n]()
313 {
314 SyncNP.update(values, names, n);
315 bool azOk = SyncAZ(SyncNP[SYNC_AZ].getValue());
316 bool altOk = SyncALT(SyncNP[SYNC_ALT].getValue());
317 return (azOk && altOk);
318 }, true);
319 return true;
320 }
321
322 if (HasBacklash() && BacklashNP.isNameMatch(name))
323 {
324 m_DefaultDevice->updateProperty(BacklashNP, values, names, n, [this, values, names, n]()
325 {
326 BacklashNP.update(values, names, n);
327 bool azOk = SetBacklashAZ(static_cast<int32_t>(BacklashNP[BACKLASH_AZ].getValue()));
328 bool altOk = SetBacklashALT(static_cast<int32_t>(BacklashNP[BACKLASH_ALT].getValue()));
329 return (azOk && altOk);
330 }, true);
331 return true;
332 }
333
334 return false;
335}
336
338{
339 if (HasSpeed())
340 SpeedNP.save(fp);
341 if (CanReverse())
342 {
343 AZReverseSP.save(fp);
344 ALTReverseSP.save(fp);
345 }
346 if (CanHome())
347 HomeSP.save(fp);
348 if (CanSync())
349 SyncNP.save(fp);
350 if (HasBacklash())
351 {
352 BacklashSP.save(fp);
353 BacklashNP.save(fp);
354 }
355 return true;
356}
357
358// ---------------------------------------------------------------------------
359// Default virtual implementations
360// ---------------------------------------------------------------------------
361
363{
364 INDI_UNUSED(degrees);
365 return IPS_ALERT;
366}
367
369{
370 INDI_UNUSED(degrees);
371 return IPS_ALERT;
372}
373
374IPState PACInterface::MoveBoth(double azDegrees, double altDegrees)
375{
376 // Default: call individual axis moves in sequence.
377 // Drivers that support a single combined command should override this.
378 IPState azState = MoveAZ(azDegrees);
379 IPState altState = MoveALT(altDegrees);
380 if (azState == IPS_ALERT || altState == IPS_ALERT) return IPS_ALERT;
381 if (azState == IPS_BUSY || altState == IPS_BUSY) return IPS_BUSY;
382 return IPS_OK;
383}
384
386{
388 "PAC device does not support aborting motion.");
389 return false;
390}
391
392bool PACInterface::SetPACSpeed(uint16_t speed)
393{
394 INDI_UNUSED(speed);
396 "PAC device does not support variable speed.");
397 return false;
398}
399
400bool PACInterface::ReverseAZ(bool enabled)
401{
402 INDI_UNUSED(enabled);
404 "PAC device does not support reversing azimuth direction.");
405 return false;
406}
407
408bool PACInterface::ReverseALT(bool enabled)
409{
410 INDI_UNUSED(enabled);
412 "PAC device does not support reversing altitude direction.");
413 return false;
414}
415
417{
419 "PAC device does not support setting home.");
420 return false;
421}
422
424{
426 "PAC device does not support returning to home.");
427 return IPS_ALERT;
428}
429
431{
433 "PAC device does not support resetting home.");
434 return false;
435}
436
437bool PACInterface::SyncAZ(double degrees)
438{
439 INDI_UNUSED(degrees);
441 "PAC device does not support syncing azimuth position.");
442 return false;
443}
444
445bool PACInterface::SyncALT(double degrees)
446{
447 INDI_UNUSED(degrees);
449 "PAC device does not support syncing altitude position.");
450 return false;
451}
452
454{
455 INDI_UNUSED(enabled);
457 "PAC device does not support backlash compensation.");
458 return false;
459}
460
462{
463 INDI_UNUSED(steps);
465 "PAC device does not support setting azimuth backlash.");
466 return false;
467}
468
470{
471 INDI_UNUSED(steps);
473 "PAC device does not support setting altitude backlash.");
474 return false;
475}
476
477}
bool isConnected() const
const char * getDeviceName() const
Class to provide extended functionality for devices in addition to the functionality provided by INDI...
bool updateProperty(PropertyType &property, ValueType *values, char *names[], int n, std::function< bool()> updater, bool saveConfig=false)
Generic helper function to update an INDI property based on an external operation.
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 SetPACSpeed(uint16_t speed)
SetPACSpeed Set the motor speed used for axis movements.
INDI::PropertyNumber SyncNP
virtual bool ReverseALT(bool enabled)
ReverseALT Reverse the direction of altitude axis movement.
virtual bool ResetHome()
ResetHome Clear the stored home reference without moving.
virtual bool SetBacklashEnabled(bool enabled)
SetBacklashEnabled Enable or disable hardware backlash compensation.
INDI::PropertySwitch BacklashSP
virtual IPState GoHome()
GoHome Slew both axes back to the home (0, 0) position.
@ MANUAL_AZ
Azimuth step in degrees: positive = East, negative = West.
@ MANUAL_ALT
Altitude step in degrees: positive = North, negative = South.
PACInterface(DefaultDevice *device)
bool saveConfigItems(FILE *fp)
saveConfigItems Save PAC interface properties to config file. Call this from your driver's saveConfig...
virtual bool SetHome()
SetHome Mark the current position as the home (zero) reference.
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().
virtual bool SetBacklashALT(int32_t steps)
SetBacklashALT Set the altitude axis backlash compensation value in steps.
virtual bool SetBacklashAZ(int32_t steps)
SetBacklashAZ Set the azimuth axis backlash compensation value in steps.
virtual bool AbortMotion()
AbortMotion Abort all axis motion immediately.
virtual bool ReverseAZ(bool enabled)
ReverseAZ Reverse the direction of azimuth axis movement.
INDI::PropertyNumber PositionNP
INDI::PropertyNumber ManualAdjustmentNP
virtual IPState MoveAZ(double degrees)
Move the azimuth axis by the given number of degrees.
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 ...
virtual bool SyncALT(double degrees)
SyncALT Sync the altitude position reference to the given value without moving.
@ POSITION_ALT
Current altitude position in degrees.
@ POSITION_AZ
Current azimuth position in degrees.
INDI::PropertySwitch ALTReverseSP
virtual bool SyncAZ(double degrees)
SyncAZ Sync the azimuth position reference to the given value without moving.
virtual IPState MoveBoth(double azDegrees, double altDegrees)
MoveBoth Move both axes in a single coordinated operation.
virtual IPState MoveALT(double degrees)
MoveALT Move the altitude axis by the given number of degrees.
INDI::PropertySwitch HomeSP
INDI::PropertySwitch AZReverseSP
INDI::PropertySwitch AbortSP
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
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)
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
@ IP_WO
Definition indiapi.h:183
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
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_ATMOST1
Definition indiapi.h:172
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define DEBUGDEVICE(device, priority, msg)
Definition indilogger.h:60
Namespace to encapsulate INDI client, drivers, and mediator classes.
uint8_t cmd[4]
Definition pwc-ioctl.h:2