Instrument Neutral Distributed Interface INDI 2.2.5
lx200am5.cpp
Go to the documentation of this file.
1/*
2 ZWO Mount INDI driver
3
4 Copyright (C) 2022-2027 Jasem Mutlaq
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#include "lx200am5.h"
22
24#include "lx200driver.h"
25#include "indicom.h"
26
27#include <libnova/transform.h>
28
29#include <cmath>
30#include <cstring>
31#include <stdio.h>
32#include <termios.h>
33#include <unistd.h>
34#include <regex>
35
54
56{
58
61
62 tcpConnection->setDefaultHost("192.168.4.1");
65
66 if (strstr(getDeviceName(), "WiFi"))
68
72 MountTypeSP[Azimuth].fill("Azimuth", "Azimuth", ISS_OFF);
73 MountTypeSP[Equatorial].fill("Equatorial", "Equatorial", ISS_ON);
74 MountTypeSP.fill(getDeviceName(), "MOUNT_TYPE", "Mount Type", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
75 MountTypeSP.load();
76
77 if (MountTypeSP[Equatorial].getState() == ISS_ON)
79
80 // Slew Rates
81
82 SlewRateSP[0].setLabel("0.25x");
83 SlewRateSP[1].setLabel("0.5x");
84 SlewRateSP[2].setLabel("1x");
85 SlewRateSP[3].setLabel("2x");
86 SlewRateSP[4].setLabel("4x");
87 SlewRateSP[5].setLabel("8x");
88 SlewRateSP[6].setLabel("20x");
89 SlewRateSP[7].setLabel("60x");
90 SlewRateSP[8].setLabel("720x");
91 SlewRateSP[9].setLabel("1440x");
93 // 1440x is the default
95
96 // Home/Zero position
97 // HomeSP[0].fill("GO", "Go", ISS_OFF);
98 // HomeSP.fill(getDeviceName(), "TELESCOPE_HOME", "Home", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
99
100 // Guide Rate
101 GuideRateNP[0].fill("RATE", "Rate", "%.2f", 0.1, 0.9, 0.1, 0.5);
102 GuideRateNP.fill(getDeviceName(), "GUIDE_RATE", "Guiding Rate", MOTION_TAB, IP_RW, 60, IPS_IDLE);
103 GuideRateNP.load();
104
105 // Buzzer
106 BuzzerSP[Off].fill("OFF", "Off", ISS_OFF);
107 BuzzerSP[Low].fill("LOW", "Low", ISS_OFF);
108 BuzzerSP[High].fill("HIGH", "High", ISS_ON);
109 BuzzerSP.fill(getDeviceName(), "BUZZER", "Buzzer", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
110
111 // Heavy Duty Mode
112 HeavyDutyModeSP[INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_OFF);
113 HeavyDutyModeSP[INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_OFF);
114 HeavyDutyModeSP.fill(getDeviceName(), "HEAVY_DUTY_MODE", "Heavy Duty Mode", MOTION_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
115
116 // Meridian Flip Enable
117 MeridianFlipSP[INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_ON);
118 MeridianFlipSP[INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_OFF);
119 MeridianFlipSP.fill(getDeviceName(), "MERIDIAN_FLIP", "Meridian Flip", MERIDIAN_FLIP_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
120 MeridianFlipSP.load();
121
122 // Post Meridian Track
123 PostMeridianTrackSP[TRACK].fill("TRACK", "Track", ISS_ON);
124 PostMeridianTrackSP[STOP].fill("STOP", "Stop", ISS_OFF);
125 PostMeridianTrackSP.fill(getDeviceName(), "POST_MERIDIAN_TRACK", "After Meridian", MERIDIAN_FLIP_TAB, IP_RW, ISR_1OFMANY,
126 60, IPS_IDLE);
127 PostMeridianTrackSP.load();
128
129 // Meridian Flip Limit
130 MeridianLimitNP[0].fill("LIMIT", "Limit (deg)", "%.f", -15, 15, 1, 0);
131 MeridianLimitNP.fill(getDeviceName(), "MERIDIAN_LIMIT", "Meridian Limit", MERIDIAN_FLIP_TAB, IP_RW, 60, IPS_IDLE);
132 MeridianLimitNP.load();
133
134 // Altitude Limits
135 AltitudeLimitSP[INDI_ENABLED].fill("ENABLE", "Enable", ISS_OFF);
136 AltitudeLimitSP[INDI_DISABLED].fill("DISABLE", "Disable", ISS_ON);
137 AltitudeLimitSP.fill(getDeviceName(), "ALTITUDE_LIMIT_CONTROL", "Altitude Limit Control", ALTITUDE_LIMIT_TAB, IP_RW,
138 ISR_1OFMANY, 60, IPS_IDLE);
139
140 AltitudeLimitUpperNP[0].fill("UPPER_LIMIT", "Upper Limit (deg)", "%.f", 60, 90, 1, 90);
141 AltitudeLimitUpperNP.fill(getDeviceName(), "ALTITUDE_UPPER_LIMIT", "Altitude Upper Limit", ALTITUDE_LIMIT_TAB, IP_RW, 60,
142 IPS_IDLE);
143
144 AltitudeLimitLowerNP[0].fill("LOWER_LIMIT", "Lower Limit (deg)", "%.f", 0, 30, 1, 0);
145 AltitudeLimitLowerNP.fill(getDeviceName(), "ALTITUDE_LOWER_LIMIT", "Altitude Lower Limit", ALTITUDE_LIMIT_TAB, IP_RW, 60,
146 IPS_IDLE);
147
148 // Multi-Star Alignment
149 MultiStarAlignSP[CLEAR_ALIGNMENT_DATA].fill("CLEAR", "Clear Data", ISS_OFF);
150 MultiStarAlignSP.fill(getDeviceName(), "MULTI_STAR_ALIGNMENT", "Multi-Star Alignment", ALIGNMENT_TAB, IP_RW, ISR_ATMOST1,
151 60, IPS_IDLE);
152
153 // Variable Slew Speed
154 VariableSlewRateNP[0].fill("RATE", "Rate (x Sidereal)", "%.2f", 0.00, 1440.00, 0.01, 1440.00);
155 VariableSlewRateNP.fill(getDeviceName(), "VARIABLE_SLEW_RATE", "Variable Slew Rate", MOTION_TAB, IP_RW, 60, IPS_IDLE);
156
157 // Firmware
158 FirmwareTP[FIRMWARE_MODEL].fill("MODEL", "Model", "");
159 FirmwareTP[FIRMWARE_VERSION].fill("VERSION", "Version", "");
160 FirmwareTP.fill(getDeviceName(), "MOUNT_FIRMWARE", "Firmware", INFO_TAB, IP_RO, 60, IPS_IDLE);
161
162 // Custom park position (config only, never defined)
163 CustomParkSP[0].fill("SET", "Set", ISS_OFF);
164 CustomParkSP.fill(getDeviceName(), "CUSTOM_PARK_POSITION", "Custom Park", SITE_TAB, IP_RW, ISR_NOFMANY, 60, IPS_IDLE);
165 CustomParkSP.load();
166
167 return true;
168}
169
174{
176
177 if (isConnected())
178 {
179 setup();
180
181 //defineProperty(HomeSP);
182 defineProperty(GuideRateNP);
183 defineProperty(BuzzerSP);
184 defineProperty(HeavyDutyModeSP);
185
186 // Only define meridian flip properties for equatorial mount
187 if (MountTypeSP[Equatorial].getState() == ISS_ON)
188 {
189 defineProperty(MeridianFlipSP);
190 defineProperty(PostMeridianTrackSP);
191 defineProperty(MeridianLimitNP);
192 }
193
194 // Altitude Limits
195 defineProperty(AltitudeLimitSP);
196 defineProperty(AltitudeLimitUpperNP);
197 defineProperty(AltitudeLimitLowerNP);
198
199 // Multi-Star Alignment
200 defineProperty(MultiStarAlignSP);
201
202 // Variable Slew Speed
203 defineProperty(VariableSlewRateNP);
204
205 defineProperty(FirmwareTP);
206 }
207 else
208 {
209 //deleteProperty(HomeSP);
210 deleteProperty(GuideRateNP);
211 deleteProperty(BuzzerSP);
212 deleteProperty(HeavyDutyModeSP);
213
214 // Only delete meridian flip properties if they were defined
215 if (MountTypeSP[Equatorial].getState() == ISS_ON)
216 {
217 deleteProperty(MeridianFlipSP);
218 deleteProperty(PostMeridianTrackSP);
219 deleteProperty(MeridianLimitNP);
220 }
221
222 // Altitude Limits
223 deleteProperty(AltitudeLimitSP);
224 deleteProperty(AltitudeLimitUpperNP);
225 deleteProperty(AltitudeLimitLowerNP);
226
227 // Multi-Star Alignment
228 deleteProperty(MultiStarAlignSP);
229
230 // Variable Slew Speed
231 deleteProperty(VariableSlewRateNP);
232
233 deleteProperty(FirmwareTP);
234 }
235
236 return true;
237}
238
243{
244 return "ZWO AM5";
245}
246
251{
252 LOG_DEBUG("Checking AM5 connection...");
253
254 double targetRA = 0;
255 for (int i = 0; i < 2; i++)
256 {
257 if (getLX200RA(PortFD, &targetRA) == 0)
258 return true;
259
260 const struct timespec ms250_delay = {.tv_sec = 0, .tv_nsec = 250000000};
261 nanosleep(&ms250_delay, NULL);
262 }
263
264 return false;
265}
266
270void LX200AM5::setup()
271{
272 // Mount Type
273 // MountTypeSP.setState(setMountType(MountTypeSP.findOnSwitchIndex()) ? IPS_OK : IPS_ALERT);
274 // MountTypeSP.apply();
275
276 InitPark();
277
278 getFirmwareInfo();
279 getMountType();
280 getTrackMode();
281 getGuideRate();
282 getBuzzer();
283 getHeavyDutyMode();
284
285 // Only get meridian flip settings for equatorial mount
286 if (MountTypeSP[Equatorial].getState() == ISS_ON)
287 getMeridianFlipSettings();
288
289 // Get altitude limit settings
290 getAltitudeLimitStatus();
291 getAltitudeLimitUpper();
292 getAltitudeLimitLower();
293
294 // A park made outside the driver (e.g. by the vendor app) leaves the mount latched in the parked state.
295 checkMountParkState();
296}
297
301bool LX200AM5::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
302{
303 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
304 {
305 // Mount Type
306 // updateProperty only calls the lambda when the value actually changes.
307 // When disconnected the lambda returns true so the selection is still saved.
308 if (MountTypeSP.isNameMatch(name))
309 {
310 return updateProperty(MountTypeSP, states, names, n, [this, names]()
311 {
312 if (isConnected())
313 {
314 int previousType = MountTypeSP.findOnSwitchIndex();
315 int newType = MountTypeSP[Azimuth].isNameMatch(names[0]) ? Azimuth : Equatorial;
316 bool rc = setMountType(newType);
317 if (rc && previousType != newType)
318 LOG_WARN("You must restart mount for change to take effect.");
319 return rc;
320 }
321 return true;
322 }, true);
323 }
324
325 // Buzzer
326 if (BuzzerSP.isNameMatch(name))
327 {
328 return updateProperty(BuzzerSP, states, names, n, [this, names]()
329 {
330 if (BuzzerSP[Off].isNameMatch(names[0]))
331 return setBuzzer(Off);
332 if (BuzzerSP[Low].isNameMatch(names[0]))
333 return setBuzzer(Low);
334 if (BuzzerSP[High].isNameMatch(names[0]))
335 return setBuzzer(High);
336 return false;
337 }, true);
338 }
339
340 // Heavy Duty Mode
341 if (HeavyDutyModeSP.isNameMatch(name))
342 {
343 return updateProperty(HeavyDutyModeSP, states, names, n, [this, names]()
344 {
345 return setHeavyDutyMode(HeavyDutyModeSP[INDI_ENABLED].isNameMatch(names[0]));
346 }, true);
347 }
348
349 // Meridian Flip Enable
350 if (MeridianFlipSP.isNameMatch(name))
351 {
352 return updateProperty(MeridianFlipSP, states, names, n, [this, names]()
353 {
354 return setMeridianFlipSettings(MeridianFlipSP[INDI_ENABLED].isNameMatch(names[0]),
355 PostMeridianTrackSP[TRACK].getState() == ISS_ON,
356 MeridianLimitNP[0].getValue());
357 }, true);
358 }
359
360 // Post Meridian Track
361 if (PostMeridianTrackSP.isNameMatch(name))
362 {
363 return updateProperty(PostMeridianTrackSP, states, names, n, [this, names]()
364 {
365 return setMeridianFlipSettings(MeridianFlipSP[INDI_ENABLED].getState() == ISS_ON,
366 PostMeridianTrackSP[TRACK].isNameMatch(names[0]),
367 MeridianLimitNP[0].getValue());
368 }, true);
369 }
370
371 // Custom park position, only received when the config is loaded.
372 if (CustomParkSP.isNameMatch(name))
373 {
374 CustomParkSP.update(states, names, n);
375 return true;
376 }
377
378 // Altitude Limit Enable/Disable
379 if (AltitudeLimitSP.isNameMatch(name))
380 {
381 return updateProperty(AltitudeLimitSP, states, names, n, [this, names]()
382 {
383 return setAltitudeLimitEnabled(AltitudeLimitSP[INDI_ENABLED].isNameMatch(names[0]));
384 }, true);
385 }
386
387 // Multi-Star Alignment (momentary action — handled manually like AbortRotatorSP in rotator interface)
388 if (MultiStarAlignSP.isNameMatch(name))
389 {
390 MultiStarAlignSP.update(states, names, n);
391 if (MultiStarAlignSP[CLEAR_ALIGNMENT_DATA].getState() == ISS_ON)
392 {
393 MultiStarAlignSP.setState(clearMultiStarAlignmentData() ? IPS_OK : IPS_ALERT);
394
395 }
396 else
397 MultiStarAlignSP.setState(IPS_IDLE);
398
399 MultiStarAlignSP.apply();
400 return true;
401 }
402 }
403
404 return LX200Generic::ISNewSwitch(dev, name, states, names, n);
405}
406
410bool LX200AM5::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
411{
412 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
413 {
414 // Guide Rate
415 if (GuideRateNP.isNameMatch(name))
416 {
417 return updateProperty(GuideRateNP, values, names, n, [this, values]()
418 {
419 return setGuideRate(values[0]);
420 }, true);
421 }
422
423 // Meridian Flip Limit
424 if (MeridianLimitNP.isNameMatch(name))
425 {
426 return updateProperty(MeridianLimitNP, values, names, n, [this, values]()
427 {
428 return setMeridianFlipSettings(MeridianFlipSP[INDI_ENABLED].getState() == ISS_ON,
429 PostMeridianTrackSP[TRACK].getState() == ISS_ON,
430 values[0]);
431 }, true);
432 }
433
434 // Altitude Upper Limit
435 if (AltitudeLimitUpperNP.isNameMatch(name))
436 {
437 return updateProperty(AltitudeLimitUpperNP, values, names, n, [this, values]()
438 {
439 return setAltitudeLimitUpper(values[0]);
440 }, true);
441 }
442
443 // Altitude Lower Limit
444 if (AltitudeLimitLowerNP.isNameMatch(name))
445 {
446 return updateProperty(AltitudeLimitLowerNP, values, names, n, [this, values]()
447 {
448 return setAltitudeLimitLower(values[0]);
449 }, true);
450 }
451
452 // Variable Slew Rate
453 if (VariableSlewRateNP.isNameMatch(name))
454 {
455 return updateProperty(VariableSlewRateNP, values, names, n, [this, values]()
456 {
457 return setVariableSlewRate(values[0]);
458 });
459 }
460 }
461
462 return LX200Generic::ISNewNumber(dev, name, values, names, n);
463}
464
468bool LX200AM5::setMountType(int type)
469{
470 return sendCommand((type == Azimuth) ? ":AA#" : ":AP#");
471}
472
476bool LX200AM5::getMountType()
477{
478 char response[DRIVER_LEN] = {0};
479 if (sendCommand(":GU#", response))
480 {
481 MountTypeSP.reset();
482 MountTypeSP[Azimuth].setState(strchr(response, 'Z') ? ISS_ON : ISS_OFF);
483 MountTypeSP[Equatorial].setState(strchr(response, 'G') ? ISS_ON : ISS_OFF);
484 MountTypeSP.setState(IPS_OK);
485 return true;
486 }
487 else
488 {
489 MountTypeSP.setState(IPS_ALERT);
490 return true;
491 }
492}
493
498{
499 char command[DRIVER_LEN] = {0};
500 snprintf(command, DRIVER_LEN, ":R%d#", index);
501 return sendCommand(command);
502}
503
504
505
509bool LX200AM5::setGuideRate(double value)
510{
511 char command[DRIVER_LEN] = {0};
512 snprintf(command, DRIVER_LEN, ":Rg%.2f#", value);
513 return sendCommand(command);
514}
515
519bool LX200AM5::getGuideRate()
520{
521 char response[DRIVER_LEN] = {0};
522 if (sendCommand(":Ggr#", response))
523 {
524 float rate = 0;
525 int result = sscanf(response, "%f", &rate);
526 if (result == 1)
527 {
528 GuideRateNP[0].setValue(rate);
529 return true;
530 }
531 }
532
533 GuideRateNP.setState(IPS_ALERT);
534 return false;
535
536}
537
541bool LX200AM5::getTrackMode()
542{
543 char response[DRIVER_LEN] = {0};
544 if (sendCommand(":GT#", response))
545 {
547 auto onIndex = response[0] - 0x30;
548 if (onIndex >= 0 && onIndex < static_cast<int>(TrackModeSP.count()))
549 {
550 TrackModeSP[onIndex].setState(ISS_ON);
551 return true;
552 }
553 }
554
556 return false;
557
558}
559
563bool LX200AM5::setBuzzer(int value)
564{
565 char command[DRIVER_LEN] = {0};
566 snprintf(command, DRIVER_LEN, ":SBu%d#", value);
567 return sendCommand(command);
568}
569
573bool LX200AM5::getBuzzer()
574{
575 char response[DRIVER_LEN] = {0};
576 if (sendCommand(":GBu#", response))
577 {
578 BuzzerSP.reset();
579 auto onIndex = response[0] - 0x30;
580 if (onIndex >= 0 && onIndex < static_cast<int>(BuzzerSP.count()))
581 {
582 BuzzerSP[onIndex].setState(ISS_ON);
583 BuzzerSP.setState(IPS_OK);
584 return true;
585 }
586 }
587
588 BuzzerSP.setState(IPS_ALERT);
589 return true;
590
591}
592
596bool LX200AM5::getHeavyDutyMode()
597{
598 char response[DRIVER_LEN] = {0};
599 if (sendCommand(":GRl#", response))
600 {
601 HeavyDutyModeSP.reset();
602
603 if (strcmp(response, "1440#") == 0)
604 {
605 HeavyDutyModeSP[INDI_DISABLED].setState(ISS_ON);
606 }
607
608 if (strcmp(response, "720#") == 0)
609 {
610 HeavyDutyModeSP[INDI_ENABLED].setState(ISS_ON);
611 }
612
613 HeavyDutyModeSP.setState(IPS_OK);
614 return true;
615 }
616 else
617 {
618 HeavyDutyModeSP.setState(IPS_ALERT);
619 return true;
620 }
621}
622
626bool LX200AM5::setHeavyDutyMode(bool enable)
627{
628 // Reply must be read, otherwise it is left in the input stream for the next command.
629 char response[DRIVER_LEN] = {0};
630 return sendCommand(enable ? ":SRl720#" : ":SRl1440#", response, -1, 1) && response[0] == '1';
631}
632
636bool LX200AM5::setMeridianFlipSettings(bool enabled, bool track, double limit)
637{
638 char command[DRIVER_LEN] = {0};
639 snprintf(command, DRIVER_LEN, ":STa%d%d%c%02d#", enabled ? 1 : 0, track ? 1 : 0,
640 limit >= 0 ? '+' : '-', static_cast<int>(std::abs(limit)));
641 char response[2] = {0};
642 auto rc = sendCommand(command, response, -1, 1);
643 return rc && response[0] == '1';
644}
645
649bool LX200AM5::getMeridianFlipSettings()
650{
651 char response[DRIVER_LEN] = {0};
652 if (sendCommand(":GTa#", response))
653 {
654 if (strlen(response) >= 5)
655 {
656 // First digit: meridian flip enabled
657 MeridianFlipSP.reset();
658 MeridianFlipSP[response[0] == '1' ? INDI_ENABLED : INDI_DISABLED].setState(ISS_ON);
659 MeridianFlipSP.setState(IPS_OK);
660
661 // Second digit: track after meridian
662 PostMeridianTrackSP.reset();
663 PostMeridianTrackSP[response[1] == '1' ? TRACK : STOP].setState(ISS_ON);
664 PostMeridianTrackSP.setState(IPS_OK);
665
666 // Last three digits: limit angle
667 char *sign = &response[2];
668 char *limit = &response[3];
669 int limitValue = atoi(limit);
670 if (*sign == '-')
671 limitValue = -limitValue;
672 MeridianLimitNP[0].setValue(limitValue);
673 MeridianLimitNP.setState(IPS_OK);
674
675 return true;
676 }
677 }
678
679 MeridianFlipSP.setState(IPS_ALERT);
680 PostMeridianTrackSP.setState(IPS_ALERT);
681 MeridianLimitNP.setState(IPS_ALERT);
682 return false;
683}
684
688bool LX200AM5::setAltitudeLimitEnabled(bool enable)
689{
690 // Reply must be read, otherwise it is left in the input stream for the next command.
691 char response[DRIVER_LEN] = {0};
692 return sendCommand(enable ? ":SLE#" : ":SLD#", response, -1, 1) && response[0] == '1';
693}
694
698bool LX200AM5::getAltitudeLimitStatus()
699{
700 char response[DRIVER_LEN] = {0};
701 if (sendCommand(":GLC#", response))
702 {
703 AltitudeLimitSP.reset();
704 AltitudeLimitSP[response[0] == '1' ? INDI_ENABLED : INDI_DISABLED].setState(ISS_ON);
705 AltitudeLimitSP.setState(IPS_OK);
706 return true;
707 }
708 AltitudeLimitSP.setState(IPS_ALERT);
709 return false;
710}
711
715bool LX200AM5::setAltitudeLimitUpper(double limit)
716{
717 char command[DRIVER_LEN] = {0};
718 snprintf(command, DRIVER_LEN, ":SLH%02d#", static_cast<int>(limit));
719 char response[2] = {0};
720 auto rc = sendCommand(command, response, -1, 1);
721 return rc && response[0] == '1';
722}
723
727bool LX200AM5::getAltitudeLimitUpper()
728{
729 char response[DRIVER_LEN] = {0};
730 if (sendCommand(":GLH#", response))
731 {
732 int limit = 0;
733 if (sscanf(response, "%d#", &limit) == 1)
734 {
735 AltitudeLimitUpperNP[0].setValue(limit);
736 AltitudeLimitUpperNP.setState(IPS_OK);
737 return true;
738 }
739 }
740 AltitudeLimitUpperNP.setState(IPS_ALERT);
741 return false;
742}
743
747bool LX200AM5::setAltitudeLimitLower(double limit)
748{
749 char command[DRIVER_LEN] = {0};
750 snprintf(command, DRIVER_LEN, ":SLL%02d#", static_cast<int>(limit));
751 char response[2] = {0};
752 auto rc = sendCommand(command, response, -1, 1);
753 return rc && response[0] == '1';
754}
755
759bool LX200AM5::getAltitudeLimitLower()
760{
761 char response[DRIVER_LEN] = {0};
762 if (sendCommand(":GLL#", response))
763 {
764 int limit = 0;
765 if (sscanf(response, "%d#", &limit) == 1)
766 {
767 AltitudeLimitLowerNP[0].setValue(limit);
768 AltitudeLimitLowerNP.setState(IPS_OK);
769 return true;
770 }
771 }
772 AltitudeLimitLowerNP.setState(IPS_ALERT);
773 return false;
774}
775
779bool LX200AM5::clearMultiStarAlignmentData()
780{
781 char response[2] = {0};
782 auto rc = sendCommand(":NSC#", response, -1, 1);
783 return rc && response[0] == '1';
784}
785
789bool LX200AM5::setVariableSlewRate(double rate)
790{
791 char command[DRIVER_LEN] = {0};
792 snprintf(command, DRIVER_LEN, ":Rv%.2f#", rate);
793 return sendCommand(command);
794}
795
799bool LX200AM5::setUTCOffset(double offset)
800{
801 int h {0}, m {0}, s {0};
802 char command[DRIVER_LEN] = {0};
803 offset *= -1;
804 getSexComponents(offset, &h, &m, &s);
805
806 snprintf(command, DRIVER_LEN, ":SG%c%02d:%02d#", offset >= 0 ? '+' : '-', std::abs(h), m);
807 return setStandardProcedure(PortFD, command) == 0;
808}
809
810bool LX200AM5::setLocalDate(uint8_t days, uint8_t months, uint16_t years)
811{
812 char command[DRIVER_LEN] = {0};
813 snprintf(command, DRIVER_LEN, ":SC%02d/%02d/%02d#", months, days, years % 100);
814 return setStandardProcedure(PortFD, command) == 0;
815}
816
821{
822 char response[DRIVER_LEN] = {0};
823 bool rc = sendCommand(enabled ? ":Te#" : ":Td#", response, 4, 1);
824 return rc && response[0] == '1';
825}
826
830bool LX200AM5::isTracking()
831{
832 char response[DRIVER_LEN] = {0};
833 bool rc = sendCommand(":GAT#", response);
834 return rc && response[0] == '1';
835}
836
840bool LX200AM5::goHome()
841{
842 return sendCommand(":hC#");
843}
844
848bool LX200AM5::park()
849{
850 // JM 2025.11.08: Many users do not like default ZWO parking position
851 // which is horizontal and does not go back to expected home position
852 // with CW down and looking at celestial pole.
853 // For now this is reverted now back to go back to that position instead
854 // of parking until ZWO releases update for custom parking positions.
855 return goHome();
856 //return sendCommand(":hP#");
857}
858
862bool LX200AM5::setHome()
863{
864 const char cmd[] = ":SOa#";
865 char status = '0';
866 return sendCommand(cmd, &status, strlen(cmd), sizeof(status)) && status == '1';
867}
868
872bool LX200AM5::updateLocation(double latitude, double longitude, double elevation)
873{
874 INDI_UNUSED(elevation);
875 int d{0}, m{0}, s{0};
876
877 // JM 2021-04-10: MUST convert from INDI longitude to standard longitude.
878 // DO NOT REMOVE
879 if (longitude > 180)
880 longitude = longitude - 360;
881
882 char command[DRIVER_LEN] = {0};
883 // Reverse as per Meade way
884 longitude *= -1;
885 getSexComponents(longitude, &d, &m, &s);
886 snprintf(command, DRIVER_LEN, ":Sg%c%03d*%02d:%02d#", longitude >= 0 ? '+' : '-', std::abs(d), m, s);
887 if (setStandardProcedure(PortFD, command) < 0)
888 {
889 LOG_ERROR("Error setting site longitude coordinates");
890 return false;
891 }
892
893 getSexComponents(latitude, &d, &m, &s);
894 snprintf(command, DRIVER_LEN, ":St%c%02d*%02d:%02d#", latitude >= 0 ? '+' : '-', std::abs(d), m, s);
895 if (setStandardProcedure(PortFD, command) < 0)
896 {
897 LOG_ERROR("Error setting site latitude coordinates");
898 return false;
899 }
900
901 return true;
902}
903
908{
909 m_MountParking = false;
910
911 bool rc = false;
912 // Without a custom park position, the mount would go to its factory park position which is horizontal.
913 if (hasCustomParkPosition() && CustomParkSP[0].getState() == ISS_ON)
914 rc = parkToMountPosition();
915 else
916 rc = park();
917
918 if (rc)
920 return rc;
921}
922
927{
928 // :hP# latches the parked state in the mount and only :Spu# releases it.
929 if (hasMountPark())
930 {
931 char response[DRIVER_LEN] = {0};
932 if (!sendStatusCommand(":Spu#", response))
933 {
934 LOG_ERROR("Failed to unpark mount.");
935 return false;
936 }
937
938 if (response[0] != '1')
939 {
940 // A mount that is not latched as parked (e.g. parked by going home) refuses to unpark.
941 char state = 0;
942 if (!getMountParkState(state) || state != '0')
943 {
944 LOGF_ERROR("Mount refused to unpark (%s).", response);
945 return false;
946 }
947 }
948 }
949
951 SetParked(false);
952 return true;
953}
954
959{
960 if (!hasCustomParkPosition())
961 {
962 LOGF_WARN("Setting the park position requires mount firmware 1.3.0 or later (detected %s).",
963 FirmwareTP[FIRMWARE_VERSION].getText());
964 return false;
965 }
966
967 char response[DRIVER_LEN] = {0};
968 if (!sendStatusCommand(":Sp01#", response))
969 {
970 LOG_ERROR("Failed to set park position.");
971 return false;
972 }
973
974 switch (response[0])
975 {
976 case '1':
977 LOG_INFO("Current position is stored as the mount park position.");
978 break;
979 case '2':
980 LOG_INFO("Current position is already the mount park position.");
981 break;
982 case '3':
983 LOG_ERROR("Mount refused the park position: only one park position can be set.");
984 return false;
985 case '4':
986 LOG_ERROR("Mount refused the park position: only available in equatorial mode.");
987 return false;
988 case '5':
989 LOG_ERROR("Mount refused the park position: the mount has to be homed first.");
990 return false;
991 case '9':
992 LOG_ERROR("Mount refused the park position: the mount is moving.");
993 return false;
994 default:
995 LOGF_ERROR("Mount refused the park position (%s).", response);
996 return false;
997 }
998
999 // Park to the stored position from now on.
1000 CustomParkSP[0].setState(ISS_ON);
1001 saveConfig();
1002 return true;
1003}
1004
1010{
1011 CustomParkSP[0].setState(ISS_OFF);
1012 saveConfig();
1013
1014 LOG_INFO("Mount parks at the home position.");
1015 return true;
1016}
1017
1021bool LX200AM5::parkToMountPosition()
1022{
1023 char status[DRIVER_LEN] = {0};
1024 if (!sendCommand(":GU#", status))
1025 return false;
1026
1027 if (strchr(status, 'Z'))
1028 {
1029 LOG_ERROR("Parking to the mount park position is only available in equatorial mode.");
1030 return false;
1031 }
1032
1033 if (!SetTrackEnabled(false))
1034 {
1035 LOG_ERROR("Failed to stop tracking before parking.");
1036 return false;
1037 }
1038
1039 m_ParkMoved = false;
1040 m_ParkDeadline = std::chrono::steady_clock::now() + PARK_START_TIMEOUT;
1041
1042 LOG_INFO("Parking to the mount park position...");
1043 m_MountParking = sendCommand(":hP#");
1044 return m_MountParking;
1045}
1046
1050bool LX200AM5::getMountParkState(char &state)
1051{
1052 char response[DRIVER_LEN] = {0};
1053 if (!sendCommand(":Gps#", response))
1054 return false;
1055
1056 state = response[0];
1057 return true;
1058}
1059
1063void LX200AM5::checkMountParkState()
1064{
1065 m_MountParking = false;
1066
1067 char state = 0;
1068 if (!hasMountPark() || !getMountParkState(state))
1069 return;
1070
1071 if (state == '2' && !isParked())
1072 {
1073 LOG_INFO("Mount reports it is parked.");
1074 SetParked(true);
1075 }
1076 else if (state == '1')
1077 {
1078 LOG_INFO("Mount is parking...");
1080 m_MountParking = true;
1081 m_ParkMoved = true;
1083 ParkSP.apply();
1084 }
1085}
1086
1090void LX200AM5::updateMountPark(bool slewComplete)
1091{
1092 const bool slewing = !slewComplete;
1093 const bool timedOut = std::chrono::steady_clock::now() > m_ParkDeadline;
1094
1095 char state = 0;
1096 // Try again on next poll.
1097 if (!getMountParkState(state))
1098 return;
1099
1100 switch (state)
1101 {
1102 case '2':
1103 m_MountParking = false;
1104 SetParked(true);
1105 break;
1106
1107 case '1':
1108 m_ParkMoved = true;
1109 break;
1110
1111 case '0':
1112 if (slewing)
1113 m_ParkMoved = true;
1114 else if (!m_ParkMoved && timedOut)
1115 failMountPark("mount did not start moving to the park position");
1116 break;
1117
1118 default:
1119 failMountPark("mount reported a park error");
1120 break;
1121 }
1122}
1123
1127void LX200AM5::failMountPark(const char *reason)
1128{
1129 LOGF_ERROR("Park failed: %s.", reason);
1130 m_MountParking = false;
1132 ParkSP.reset();
1135 ParkSP.apply();
1136}
1137
1142{
1143 if (!isConnected())
1144 return false;
1145
1146 bool slewComplete = false, isHome = false;
1147 char status[DRIVER_LEN] = {0};
1148 if (sendCommand(":GU#", status))
1149 {
1150 slewComplete = strchr(status, 'N');
1151 isHome = strchr(status, 'H');
1152 }
1153
1154 if (HomeSP.getState() == IPS_BUSY && isHome)
1155 {
1156 HomeSP.reset();
1158 LOG_INFO("Arrived at home.");
1159 HomeSP.apply();
1161 }
1162 else if (TrackState == SCOPE_SLEWING)
1163 {
1164 // Check if LX200 is done slewing
1165 if (slewComplete)
1166 {
1168 LOG_INFO("Slew is complete. Tracking...");
1169 }
1170 }
1171 else if (TrackState == SCOPE_PARKING)
1172 {
1173 if (m_MountParking)
1174 updateMountPark(slewComplete);
1175 else if (slewComplete)
1176 SetParked(true);
1177 }
1178 else
1179 {
1180 // Tracking changed?
1181 auto wasTracking = TrackStateSP[INDI_ENABLED].getState() == ISS_ON;
1182 auto nowTracking = isTracking();
1183 if (wasTracking != nowTracking)
1184 TrackState = nowTracking ? SCOPE_TRACKING : SCOPE_IDLE;
1185 }
1186
1188 {
1190 LOG_ERROR("Error reading RA/DEC.");
1191 EqNP.apply();
1192 return false;
1193 }
1194
1195 if (HasPierSide())
1196 {
1197 char response[DRIVER_LEN] = {0};
1198 if (sendCommand(":Gm#", response))
1199 {
1200 if (response[0] == 'W')
1202 else if (response[0] == 'E')
1204 else
1206 }
1207 }
1208
1210
1211 return true;
1212}
1213
1217void LX200AM5::getFirmwareInfo()
1218{
1219 m_FirmwareVersion = 0;
1220
1221 char response[DRIVER_LEN] = {0};
1222 if (sendCommand(":GVP#", response))
1223 {
1224 char *term = strchr(response, DRIVER_STOP_CHAR);
1225 if (term)
1226 *term = '\0';
1227 FirmwareTP[FIRMWARE_MODEL].setText(response);
1228 }
1229
1230 memset(response, 0, sizeof(response));
1231 if (sendCommand(":GV#", response))
1232 {
1233 char *term = strchr(response, DRIVER_STOP_CHAR);
1234 if (term)
1235 *term = '\0';
1236 FirmwareTP[FIRMWARE_VERSION].setText(response);
1237
1238 int major = 0, minor = 0, patch = 0;
1239 if (sscanf(response, "%d.%d.%d", &major, &minor, &patch) == 3)
1240 m_FirmwareVersion = (major << 16) | (minor << 8) | patch;
1241 else
1242 LOGF_WARN("Unexpected firmware version format: %s", response);
1243 }
1244
1245 FirmwareTP.setState(m_FirmwareVersion > 0 ? IPS_OK : IPS_ALERT);
1246
1247 LOGF_INFO("Mount %s firmware %s.", FirmwareTP[FIRMWARE_MODEL].getText(), FirmwareTP[FIRMWARE_VERSION].getText());
1248 if (!hasCustomParkPosition())
1249 LOG_INFO("Mount parks at the home position. Custom park position requires firmware 1.3.0 or later.");
1250 else if (CustomParkSP[0].getState() == ISS_ON)
1251 LOG_INFO("Mount parks at the custom park position.");
1252 else
1253 LOG_INFO("Mount parks at the home position. To use a custom park position, slew to it and set it as current park position.");
1254}
1255
1259bool LX200AM5::sendStatusCommand(const char * cmd, char * res)
1260{
1261 int nbytes_written = 0, nbytes_read = 0;
1262
1263 tcflush(PortFD, TCIOFLUSH);
1264
1265 LOGF_DEBUG("CMD <%s>", cmd);
1266 int rc = tty_write_string(PortFD, cmd, &nbytes_written);
1267 if (rc == TTY_OK)
1268 rc = tty_read(PortFD, res, 1, DRIVER_TIMEOUT, &nbytes_read);
1269 // Errors are reported as e<code>#
1270 if (rc == TTY_OK && res[0] == 'e')
1271 rc = tty_nread_section(PortFD, res + 1, DRIVER_LEN - 2, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
1272
1273 if (rc != TTY_OK)
1274 {
1275 char errstr[MAXRBUF] = {0};
1276 tty_error_msg(rc, errstr, MAXRBUF);
1277 LOGF_ERROR("Serial error: %s.", errstr);
1278 return false;
1279 }
1280
1281 char *term = strchr(res, DRIVER_STOP_CHAR);
1282 if (term)
1283 *term = '\0';
1284 else if (res[0] != 'e')
1285 res[1] = '\0';
1286
1287 LOGF_DEBUG("RES <%s>", res);
1288
1289 tcflush(PortFD, TCIOFLUSH);
1290
1291 return true;
1292}
1293
1297bool LX200AM5::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
1298{
1299 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1300
1301 tcflush(PortFD, TCIOFLUSH);
1302
1303 if (cmd_len > 0)
1304 {
1305 char hex_cmd[DRIVER_LEN * 3] = {0};
1306 hexDump(hex_cmd, cmd, cmd_len);
1307 LOGF_DEBUG("CMD <%s>", hex_cmd);
1308 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
1309 }
1310 else
1311 {
1312 LOGF_DEBUG("CMD <%s>", cmd);
1313 rc = tty_write_string(PortFD, cmd, &nbytes_written);
1314 }
1315
1316 if (rc != TTY_OK)
1317 {
1318 char errstr[MAXRBUF] = {0};
1319 tty_error_msg(rc, errstr, MAXRBUF);
1320 LOGF_ERROR("Serial write error: %s.", errstr);
1321 return false;
1322 }
1323
1324 if (res == nullptr)
1325 {
1326 tcdrain(PortFD);
1327 return true;
1328 }
1329
1330 if (res_len > 0)
1331 rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
1332 else
1333 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
1334
1335 if (rc != TTY_OK)
1336 {
1337 char errstr[MAXRBUF] = {0};
1338 tty_error_msg(rc, errstr, MAXRBUF);
1339 LOGF_ERROR("Serial read error: %s.", errstr);
1340 return false;
1341 }
1342
1343 if (res_len > 0)
1344 {
1345 char hex_res[DRIVER_LEN * 3] = {0};
1346 hexDump(hex_res, res, res_len);
1347 LOGF_DEBUG("RES <%s>", hex_res);
1348 }
1349 else
1350 {
1351 LOGF_DEBUG("RES <%s>", res);
1352 }
1353
1354 tcflush(PortFD, TCIOFLUSH);
1355
1356 return true;
1357}
1358
1363{
1364 switch (action)
1365 {
1366 case HOME_GO:
1367 if (goHome())
1368 return IPS_BUSY;
1369 else
1370 return IPS_ALERT;
1371
1372 case HOME_SET:
1373 // The ZWO AM5 protocol does not have a specific "set home" command.
1374 // We can potentially use the current position as home, but for now,
1375 // we'll mark it as unsupported.
1376 LOG_WARN("Setting home position is not supported by the ZWO AM5 protocol.");
1377 return IPS_ALERT;
1378
1379 case HOME_FIND:
1380 // The ZWO AM5 protocol does not have a specific "find home" command.
1381 LOG_WARN("Finding home position is not supported by the ZWO AM5 protocol.");
1382 return IPS_ALERT;
1383
1384 default:
1385 return IPS_ALERT;
1386 }
1387}
1388
1392void LX200AM5::hexDump(char * buf, const char * data, int size)
1393{
1394 for (int i = 0; i < size; i++)
1395 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
1396
1397 if (size > 0)
1398 buf[3 * size - 1] = '\0';
1399}
1400
1404std::vector<std::string> LX200AM5::split(const std::string &input, const std::string &regex)
1405{
1406 // passing -1 as the submatch index parameter performs splitting
1407 std::regex re(regex);
1408 std::sregex_token_iterator
1409 first{input.begin(), input.end(), re, -1},
1410 last;
1411 return {first, last};
1412}
1413
1418{
1420
1421 MountTypeSP.save(fp);
1422 GuideRateNP.save(fp);
1423 BuzzerSP.save(fp);
1424 HeavyDutyModeSP.save(fp);
1425 MeridianFlipSP.save(fp);
1426 PostMeridianTrackSP.save(fp);
1427 MeridianLimitNP.save(fp);
1428 AltitudeLimitSP.save(fp);
1429 AltitudeLimitUpperNP.save(fp);
1430 AltitudeLimitLowerNP.save(fp);
1431 VariableSlewRateNP.save(fp);
1432 CustomParkSP.save(fp);
1433
1434 return true;
1435}
void setDefaultHost(const char *addressHost)
void setLANSearchEnabled(bool enabled)
void setDefaultPort(uint32_t addressPort)
bool isConnected() const
INDI::PropertyText getText(const char *name) const
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 saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void setActiveConnection(Connection::Interface *existingConnection)
setActiveConnection Switch the active connection to the passed connection plugin
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)
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
void setLabel(const char *label)
bool isNameMatch(const char *otherName) const
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)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
TelescopeStatus TrackState
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
bool isParked()
isParked is mount currently parked?
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
Connection::TCP * tcpConnection
uint32_t GetTelescopeCapability() const
GetTelescopeCapability returns the capability of the Telescope.
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
void setPierSide(TelescopePierSide side)
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
virtual bool ReadScopeStatus() override
Read telescope status.
virtual IPState ExecuteHomeAction(TelescopeHomeAction action) override
Execute a homing action.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
Definition lx200am5.cpp:173
virtual bool SetDefaultPark() override
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
Definition lx200am5.cpp:820
virtual bool initProperties() override
Called to initialize basic properties required all the time.
Definition lx200am5.cpp:55
virtual bool Park() override
Park the telescope to its home position.
Definition lx200am5.cpp:907
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition lx200am5.cpp:410
virtual const char * getDefaultName() override
Definition lx200am5.cpp:242
virtual bool UnPark() override
Unpark the telescope if already parked.
Definition lx200am5.cpp:926
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool checkConnection() override
Definition lx200am5.cpp:250
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition lx200am5.cpp:301
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
Definition lx200am5.cpp:872
virtual bool setUTCOffset(double offset) override
Definition lx200am5.cpp:799
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
Definition lx200am5.cpp:958
virtual bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
Definition lx200am5.cpp:497
virtual bool setLocalDate(uint8_t days, uint8_t months, uint16_t years) override
Definition lx200am5.cpp:810
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void setLX200Capability(uint32_t cap)
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.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
const char * SITE_TAB
SITE_TAB Where all site information setting are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
INDI::PropertySwitch ParkSP
INDI::PropertyNumber EqNP
INDI::PropertySwitch TrackStateSP
INDI::PropertySwitch HomeSP
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch TrackModeSP
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
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_NOFMANY
Definition indiapi.h:173
@ ISR_ATMOST1
Definition indiapi.h:172
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
void getSexComponents(double value, int *d, int *m, int *s)
Definition indicom.c:243
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
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 INDI_UNUSED(x)
Definition indidevapi.h:132
#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
#define getDeviceName
int setStandardProcedure(int fd, const char *data)
#define getLX200DEC(fd, x)
@ LX200_24
Definition lx200driver.h:64
#define getLX200RA(fd, x)
uint16_t type
Definition pwc-ioctl.h:0
uint8_t cmd[4]
Definition pwc-ioctl.h:2