Instrument Neutral Distributed Interface INDI 2.2.5
lx200_10micron.cpp
Go to the documentation of this file.
1/*
2 10micron INDI driver
3 GM1000HPS GM2000QCI GM2000HPS GM3000HPS GM4000QCI GM4000HPS AZ2000
4 Mount Command Protocol 2.14.11
5
6 Copyright (C) 2017-2025 Hans Lambermont
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
30#include "lx200_10micron.h"
31#include "indicom.h"
32#include "lx200driver.h"
33
34#include <cstring>
35#include <string>
36#include <strings.h>
37#include <termios.h>
38#include <math.h>
39#include <libnova/libnova.h>
40
41#define PRODUCT_TAB "Product"
42#define ALIGNMENT_TAB "Alignment"
43#define LX200_TIMEOUT 5 /* FD timeout in seconds */
44
45// INDI Number and Text names
46#define REFRACTION_MODEL_TEMPERATURE "REFRACTION_MODEL_TEMPERATURE"
47#define REFRACTION_MODEL_PRESSURE "REFRACTION_MODEL_PRESSURE"
48#define MODEL_COUNT "MODEL_COUNT"
49#define ALIGNMENT_POINTS "ALIGNMENT_POINTS"
50#define ALIGNMENT_STATE "Alignment"
51#define MINIMAL_NEW_ALIGNMENT_POINT_RO "MINIMAL_NEW_ALIGNMENT_POINT_RO"
52#define MINIMAL_NEW_ALIGNMENT_POINT "MINIMAL_NEW_ALIGNMENT_POINT"
53#define NEW_ALIGNMENT_POINT "NEW_ALIGNMENT_POINT"
54#define NEW_ALIGNMENT_POINTS "NEW_ALIGNMENT_POINTS"
55#define NEW_MODEL_NAME "NEW_MODEL_NAME"
56#define PRODUCT_INFO "PRODUCT_INFO"
57#define TLE_TEXT "TLE_TEXT"
58#define TLE_NUMBER "TLE_NUMBER"
59#define TRAJECTORY_TIME "TRAJECTORY_TIME"
60#define SAT_TRACKING_STAT "SAT_TRACKING_STAT"
61#define UNATTENDED_FLIP "UNATTENDED_FLIP"
62#define WAKE_ON_LAN_MAC "WAKE_ON_LAN_MAC"
63#define WAKE_ON_LAN_SEND "WAKE_ON_LAN_SEND"
64#define MOUNT_SHUTDOWN "MOUNT_SHUTDOWN"
65
88
89// Called by INDI::DefaultDevice::ISGetProperties
90// Note that getDriverName calls ::getDefaultName which returns LX200 Generic
92{
93 return "10micron";
94}
95
96// Called by INDI::Telescope::callHandshake, either TCP Connect or Serial Port Connect
98{
99 fd = PortFD;
100
101 if (isSimulation() == true)
102 {
103 LOG_INFO("Simulate Connect.");
104 return true;
105 }
106
107 // Set Ultra Precision Mode #:U2# , replies like 15:58:19.49 instead of 15:21.2
108 LOG_INFO("Setting Ultra Precision Mode.");
109 // #:U2#
110 // Set ultra precision mode. In ultra precision mode, extra decimal digits are returned for
111 // some commands, and there is no more difference between different emulation modes.
112 // Returns: nothing
113 // Available from version 2.10.
114 if (setCommandInt(fd, 2, "#:U") < 0)
115 {
116 LOG_ERROR("Failed to set Ultra Precision Mode.");
117 return false;
118 }
119
120 return true;
121}
122
123// Called only once by DefaultDevice::ISGetProperties
124// Initialize basic properties that are required all the time
126{
127 const bool result = LX200Generic::initProperties();
128 if (result)
129 {
130 // TODO initialize properties additional to INDI::Telescope
134 UNATTENDED_FLIP, "Unattended Flip", MOTION_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
135
136 IUFillNumber(&RefractionModelTemperatureN[0], "TEMPERATURE", "Celsius", "%+6.1f", -999.9, 999.9, 0, 0.);
139
140 IUFillNumber(&RefractionModelPressureN[0], "PRESSURE", "hPa", "%6.1f", 0.0, 9999.9, 0, 0.);
143
144 IUFillNumber(&ModelCountN[0], "COUNT", "#", "%.0f", 0, 999, 0, 0);
146 MODEL_COUNT, "Models", ALIGNMENT_TAB, IP_RO, 60, IPS_IDLE);
147
148 IUFillNumber(&AlignmentPointsN[0], "COUNT", "#", "%.0f", 0, 100, 0, 0);
151
152 IUFillSwitch(&AlignmentStateS[ALIGN_IDLE], "Idle", "Idle", ISS_ON);
153 IUFillSwitch(&AlignmentStateS[ALIGN_START], "Start", "Start new model", ISS_OFF);
154 IUFillSwitch(&AlignmentStateS[ALIGN_END], "End", "End new model", ISS_OFF);
155 IUFillSwitch(&AlignmentStateS[ALIGN_DELETE_CURRENT], "Del", "Delete current model", ISS_OFF);
158
159 IUFillNumber(&MiniNewAlpRON[MALPRO_MRA], "MRA", "Mount RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
160 IUFillNumber(&MiniNewAlpRON[MALPRO_MDEC], "MDEC", "Mount DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
161 IUFillNumber(&MiniNewAlpRON[MALPRO_MSIDE], "MSIDE", "Pier Side (0=E 1=W)", "%.0f", 0, 1, 0, 0);
162 IUFillNumber(&MiniNewAlpRON[MALPRO_SIDTIME], "SIDTIME", "Sidereal Time (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
165
166 IUFillNumber(&MiniNewAlpN[MALP_PRA], "PRA", "Solved RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
167 IUFillNumber(&MiniNewAlpN[MALP_PDEC], "PDEC", "Solved DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
170
171 IUFillNumber(&NewAlpN[ALP_MRA], "MRA", "Mount RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
172 IUFillNumber(&NewAlpN[ALP_MDEC], "MDEC", "Mount DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
173 IUFillNumber(&NewAlpN[ALP_MSIDE], "MSIDE", "Pier Side (0=E 1=W)", "%.0f", 0, 1, 0, 0);
174 IUFillNumber(&NewAlpN[ALP_SIDTIME], "SIDTIME", "Sidereal Time (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
175 IUFillNumber(&NewAlpN[ALP_PRA], "PRA", "Solved RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
176 IUFillNumber(&NewAlpN[ALP_PDEC], "PDEC", "Solved DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
178 NEW_ALIGNMENT_POINT, "New Point", ALIGNMENT_TAB, IP_RW, 60, IPS_IDLE);
179
180 IUFillNumber(&NewAlignmentPointsN[0], "COUNT", "#", "%.0f", 0, 100, 1, 0);
182 NEW_ALIGNMENT_POINTS, "New Points", ALIGNMENT_TAB, IP_RO, 60, IPS_IDLE);
183
184 IUFillText(&NewModelNameT[0], "NAME", "Model Name", "newmodel");
186 NEW_MODEL_NAME, "New Name", ALIGNMENT_TAB, IP_RW, 60, IPS_IDLE);
187
188 IUFillNumber(&TLEfromDatabaseN[0], "NUMBER", "#", "%.0f", 1, 999, 1, 1);
190 "TLE_NUMBER", "Database TLE ", SATELLITE_TAB, IP_RW, 60, IPS_IDLE);
191
192
193 IUFillText(&WoLMacT[0], "MAC", "MAC Address (XX:XX:XX:XX:XX:XX)", "");
195 char wolMacBuf[128] = {};
196 if (IUGetConfigText(getDeviceName(), WoLMacTP.name, WoLMacT[0].name, wolMacBuf, sizeof(wolMacBuf)) == 0)
197 IUSaveText(&WoLMacT[0], wolMacBuf);
198
199 IUFillSwitch(&WoLSendS[0], "SEND", "Wake Mount", ISS_OFF);
201
202 MountShutdownSP[0].fill("SHUTDOWN", "Shutdown Mount", ISS_OFF);
204 }
205 return result;
206}
207
215
223
224// Called by INDI::Telescope when connected state changes to add/remove properties
226{
227 // Define MountShutdownSP before the parent call so it appears immediately on connect,
228 // not after the slow getBasicData() mount queries inside LX200Generic::updateProperties().
229 if (isConnected())
231
232 bool result = LX200Generic::updateProperties();
233
234 if (isConnected())
235 {
237 // getMountInfo defines ProductTP
249
250 // read UnAttendedFlip setting from config and apply if available
251 int readit = 0;
252 for (int i = 0; readit == 0 && i < UnattendedFlipSP.nsp; i++)
253 {
255 }
256 if (readit == 0)
257 {
259 {
260 LOGF_INFO("Unattended Flip from config and mount are %s",
261 (UnattendedFlipS[UNATTENDED_FLIP_ENABLED].s == ISS_ON) ? "enabled" : "disabled");
262 }
263 else
264 {
265 LOGF_INFO("Read Unattended Flip %s from config while mount has %s, updating mount",
266 (UnattendedFlipS[UNATTENDED_FLIP_ENABLED].s == ISS_ON) ? "enabled" : "disabled",
267 (UnattendedFlip == UNATTENDED_FLIP_ENABLED) ? "enabled" : "disabled");
269 }
270 }
271 else
272 {
273 LOG_INFO("Did not find an Unattended Flip setting in the config file. Specify desired behaviour in Motion Control tab and save config in Options tab.");
274 }
275 }
276 else
277 {
292 }
293
294 return result;
295}
296
297// Called by LX200Generic::updateProperties
299{
300 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
301
302 // cannot call LX200Generic::getBasicData(); as getTimeFormat :Gc# (and getSiteName :GM#) are not implemented on 10Micron
303 if (!isSimulation())
304 {
305 getMountInfo();
306
307 getAlignment();
310
311 if (getTrackFreq(PortFD, &TrackFreqN[0].value) < 0)
312 {
313 LOG_WARN("Failed to get tracking frequency from device.");
314 }
315 else
316 {
317 LOGF_INFO("Tracking frequency is %.1f Hz", TrackFreqN[0].value);
318 IDSetNumber(&TrackFreqNP, nullptr);
319 }
320
321 char RefractionModelTemperature[80];
322 getCommandString(PortFD, RefractionModelTemperature, "#:GRTMP#");
323 float rmtemp;
324 sscanf(RefractionModelTemperature, "%f#", &rmtemp);
325 RefractionModelTemperatureN[0].value = rmtemp;
326 LOGF_INFO("RefractionModelTemperature is %0+6.1f degrees C", RefractionModelTemperatureN[0].value);
328
329 char RefractionModelPressure[80];
330 getCommandString(PortFD, RefractionModelPressure, "#:GRPRS#");
331 float rmpres;
332 sscanf(RefractionModelPressure, "%f#", &rmpres);
333 RefractionModelPressureN[0].value = rmpres;
334 LOGF_INFO("RefractionModelPressure is %06.1f hPa", RefractionModelPressureN[0].value);
336
337 int ModelCount;
338 getCommandInt(PortFD, &ModelCount, "#:modelcnt#");
339 ModelCountN[0].value = (double) ModelCount;
340 LOGF_INFO("%d Alignment Models", static_cast<int>(ModelCountN[0].value));
341 IDSetNumber(&ModelCountNP, nullptr);
342
343 int AlignmentPoints;
344 getCommandInt(PortFD, &AlignmentPoints, "#:getalst#");
345 AlignmentPointsN[0].value = AlignmentPoints;
346 LOGF_INFO("%d Alignment Stars in active model", static_cast<int>(AlignmentPointsN[0].value));
348
349 if (false == getUnattendedFlipSetting())
350 {
353 LOG_INFO("Unattended Flip is disabled.");
354 }
355 else
356 {
359 LOG_INFO("Unattended Flip is enabled.");
360 }
362 IDSetSwitch(&UnattendedFlipSP, nullptr);
363 }
364
366 {
367 LOG_INFO("sendLocationOnStartup is enabled, call sendScopeLocation.");
369 }
370 else
371 {
372 LOG_INFO("sendLocationOnStartup is disabled, do not call sendScopeLocation.");
373 }
375 {
376 LOG_INFO("sendTimeOnStartup is enabled, call sendScopeTime.");
378 }
379 else
380 {
381 LOG_INFO("sendTimeOnStartup is disabled, do not call sendScopeTime.");
382 }
383}
384
385// Called by our getBasicData
386bool LX200_10MICRON::getMountInfo()
387{
388 char ProductName[80];
389 getCommandString(PortFD, ProductName, "#:GVP#");
390 char ControlBox[80];
391 getCommandString(PortFD, ControlBox, "#:GVZ#");
392 char FirmwareVersion[80];
393 getCommandString(PortFD, FirmwareVersion, "#:GVN#");
394 char FirmwareDate1[80];
395 getCommandString(PortFD, FirmwareDate1, "#:GVD#");
396 char FirmwareDate2[80];
397 char mon[4];
398 int dd, yyyy;
399 sscanf(FirmwareDate1, "%s %02d %04d", mon, &dd, &yyyy);
400 getCommandString(PortFD, FirmwareDate2, "#:GVT#");
401 char FirmwareDate[80];
402 snprintf(FirmwareDate, 80, "%04d-%02d-%02dT%s", yyyy, monthToNumber(mon), dd, FirmwareDate2);
403
404 LOGF_INFO("Product:%s Control box:%s Firmware:%s of %s", ProductName, ControlBox, FirmwareVersion, FirmwareDate);
405
406 IUFillText(&ProductT[PRODUCT_NAME], "NAME", "Product Name", ProductName);
407 IUFillText(&ProductT[PRODUCT_CONTROL_BOX], "CONTROL_BOX", "Control Box", ControlBox);
408 IUFillText(&ProductT[PRODUCT_FIRMWARE_VERSION], "FIRMWARE_VERSION", "Firmware Version", FirmwareVersion);
409 IUFillText(&ProductT[PRODUCT_FIRMWARE_DATE], "FIRMWARE_DATE", "Firmware Date", FirmwareDate);
411 PRODUCT_INFO, "Product", PRODUCT_TAB, IP_RO, 60, IPS_IDLE);
412
414
415 return true;
416}
417
418// INDI::Telescope calls ReadScopeStatus() every POLLMS to check the link to the telescope and update its state and position.
419// The child class should call newRaDec() whenever a new value is read from the telescope.
421{
422 if (!isConnected())
423 {
424 return false;
425 }
426 if (isSimulation())
427 {
428 mountSim();
429 return true;
430 }
431
432 // Read scope status, based loosely on LX200_GENERIC::getCommandString
433 // #:Ginfo#
434 // Get multiple information. Returns a string where multiple data are encoded, separated
435 // by commas ',', and terminated by '#'. Data are recognized by their position in the string.
436 // The data are the following:
437 // Position Datum
438 // 1 The telescope right ascension in hours and decimals (from 000.00000 to 23.99999),
439 // true equinox and equator of date of observation (i.e. Jnow).
440 // 2 The telescope declination in degrees and decimals (from –90.0000 to +90.0000),
441 // true equinox and equator of date of observation (i.e. Jnow).
442 // 3 A flag indicating the side of the pier on which the telescope is currently positioned
443 // ("E" or "W").
444 // 4 The telescope azimuth in degrees and decimals (from 000.0000 to 359.9999).
445 // 5 The telescope altitude in degrees and decimals (from -90.0000 to +90.0000).
446 // 6 The julian date (JJJJJJJ.JJJJJJJJ), UTC, with leap second flag (see command
447 // :GJD2# for the description of this datum).
448 // 7 A number encoding the status of the mount as in the :Gstat command.
449 // 8 A number returning the slew status (0 if :D# would return no slew, 1 otherwise).
450 // The string is terminated by '#'. Other parameters may be added in future at the end of
451 // the string: do not assume that the number of parameters will stay the same.
452 // Available from version 2.14.9 (previous versions may have this command but it was
453 // experimental and possibly with a different format).
454 char cmd[] = "#:Ginfo#";
455 char data[80];
456 char *term;
457 int error_type;
458 int nbytes_write = 0, nbytes_read = 0;
459 // DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s>", cmd);
460 if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
461 {
462 return false;
463 }
464 error_type = tty_read_section(fd, data, '#', LX200_TIMEOUT, &nbytes_read);
465 tcflush(fd, TCIFLUSH);
466 if (error_type != TTY_OK)
467 {
468 return false;
469 }
470 term = strchr(data, '#');
471 if (term)
472 {
473 *(term + 1) = '\0';
474 }
475 else
476 {
477 return false;
478 }
479 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s> RES <%s>", cmd, data);
480
481 // TODO: check if this needs changing when satellite tracking
482 // Now parse the data. This format may consist of more parts some day
483 nbytes_read = sscanf(data, "%g,%g,%c,%g,%g,%g,%d,%d#", &Ginfo.RA_JNOW, &Ginfo.DEC_JNOW, &Ginfo.SideOfPier,
484 &Ginfo.AZ, &Ginfo.ALT, &Ginfo.Jdate, &Ginfo.Gstat, &Ginfo.SlewStatus);
485 if (nbytes_read < 0)
486 {
487 return false;
488 }
489
490 if (Ginfo.Gstat != OldGstat)
491 {
492 if (OldGstat != GSTAT_UNSET)
493 {
494 LOGF_INFO("Gstat changed from %d to %d", OldGstat, Ginfo.Gstat);
495 }
496 else
497 {
498 LOGF_INFO("Gstat initialized at %d", Ginfo.Gstat);
499 }
500 }
501 switch (Ginfo.Gstat)
502 {
503 case GSTAT_TRACKING:
505 break;
506 case GSTAT_STOPPED:
508 break;
509 case GSTAT_PARKING:
511 break;
512 case GSTAT_UNPARKING:
514 break;
517 break;
518 case GSTAT_PARKED:
520 if (!isParked())
521 SetParked(true);
522 break;
525 break;
528 break;
531 break;
534 break;
537 break;
540 break;
543 break;
544 case GSTAT_ERROR:
546 break;
547 default:
548 return false;
549 }
550 setPierSide((toupper(Ginfo.SideOfPier) == 'E') ? INDI::Telescope::PIER_EAST : INDI::Telescope::PIER_WEST);
551
552 OldGstat = Ginfo.Gstat;
553 NewRaDec(Ginfo.RA_JNOW, Ginfo.DEC_JNOW);
554
555 // Update alignment Mini new alignment point Read-Only fields
556 char LocalSiderealTimeS[80];
557 getCommandString(fd, LocalSiderealTimeS, "#:GS#");
558 f_scansexa(LocalSiderealTimeS, &Ginfo.SiderealTime);
559 MiniNewAlpRON[MALPRO_MRA].value = Ginfo.RA_JNOW;
560 MiniNewAlpRON[MALPRO_MDEC].value = Ginfo.DEC_JNOW;
561 MiniNewAlpRON[MALPRO_MSIDE].value = (toupper(Ginfo.SideOfPier) == 'E') ? 0 : 1;
562 MiniNewAlpRON[MALPRO_SIDTIME].value = Ginfo.SiderealTime;
563 IDSetNumber(&MiniNewAlpRONP, nullptr);
564
565 return true;
566}
567
569{
570 // #:KA#
571 // Slew to park position
572 // Returns: nothing
573 LOG_INFO("Parking.");
574 if (setStandardProcedureWithoutRead(fd, "#:KA#") < 0)
575 {
577 LOG_ERROR("Park command failed.");
578 ParkSP.apply();
579 return false;
580 }
581
584 ParkSP.apply();
585 // postpone SetParked(true) for ReadScopeStatus so that we know it is actually correct
586 return true;
587}
588
590{
591 // #:PO#
592 // Unpark
593 // Returns:nothing
594 LOG_INFO("Unparking.");
595 if (setStandardProcedureWithoutRead(fd, "#:PO#") < 0)
596 {
598 LOG_ERROR("Unpark command failed.");
599 ParkSP.apply();
600 return false;
601 }
602
605 SetParked(false);
606 ParkSP.apply();
607 return true;
608}
609
611{
612 // :AL#
613 // Stops tracking.
614 // Returns: nothing
615 // :AP#
616 // Starts tracking.
617 // Returns: nothing
618 if (enabled)
619 {
620 LOG_INFO("Start tracking.");
621 if (setStandardProcedureWithoutRead(fd, "#:AP#") < 0)
622 {
623 LOG_ERROR("Start tracking command failed");
624 return false;
625 }
626 }
627 else
628 {
629 LOG_INFO("Stop tracking.");
630 if (setStandardProcedureWithoutRead(fd, "#:AL#") < 0)
631 {
632 LOG_ERROR("Stop tracking command failed");
633 return false;
634 }
635 }
636 return true;
637}
638
640{
641 // #:Guaf#
642 // Returns the unattended flip setting.
643 // Returns:
644 // 0 disabled
645 // 1 enabled
646 // Available from version 2.11.
647 // Note: unattended flip didn't work properly in firmware versions up to 2.13.8 included.
648 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
649 char guaf[80];
650 getCommandString(PortFD, guaf, "#:Guaf#");
651 if ('1' == guaf[0])
652 {
654 }
655 else
656 {
658 }
659 return '1' == guaf[0];
660}
661
663{
664 // #:SuafN#
665 // Enables or disables the unattended flip. Use N=1 to enable, N=0 to disable. This is set always to 0 after power up.
666 // Returns: nothing
667 // Available from version 2.11.
668 // unattended flip didn't work properly in firmware versions up to 2.13.8 included.
669 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
670 char data[64];
671 snprintf(data, sizeof(data), "#:Suaf%d#", (setting == false) ? 0 : 1);
672 if (0 == setStandardProcedureWithoutRead(fd, data))
673 {
674 if (setting == false)
675 {
677 }
678 else
679 {
681 }
682 return true;
683 }
684 return false;
685}
686
687bool LX200_10MICRON::Flip(double ra, double dec)
688{
691 return flip();
692}
693
694bool LX200_10MICRON::flip()
695{
696 // #:FLIP#
697 // This command acts in different ways on the AZ2000 and german equatorial (GM1000 – GM4000) mounts.
698 // On an AZ2000 mount: When observing an object near the lowest culmination, requests to make a 360° turn of the azimuth axis and point the object again.
699 // On a german equatorial mount: When observing an object near the meridian, requests to make a 180° turn of the RA axis and move the declination axis in order to
700 // point the object with the telescope on the other side of the mount.
701 // Returns:
702 // 1 if successful
703 // 0 if the movement cannot be done
704 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
705 char data[64];
706 snprintf(data, sizeof(data), "#:FLIP#");
707 return 0 == setStandardProcedureAndExpectChar(fd, data, "1");
708}
709
711{
712 // #:CMCFGn#
713 // Configures the behaviour of the :CM# and :CMR# commands depending on the value
714 // of n. If n=0, :the commands :CM# and :CMR# work in the default mode, i.e. they
715 // synchronize the position to the mount with the coordinates of the currently selected
716 // target by correcting the axis offset values. If n=1, the commands :CM# and :CMR#
717 // work by using the synchronization position as an additional alignment star for refining
718 // the alignment model.
719 // Returns:
720 // the string "0#" if the value 0 has been passed
721 // the string "1#" if the value 1 has been passed
722 // Available from version 2.8.15.
723 LOG_INFO("SyncConfig.");
724 if (setCommandInt(fd, cmcfg, "#:CMCFG") < 0)
725 {
726 return false;
727 }
728 return true;
729}
730
731bool LX200_10MICRON::setLocalDate(uint8_t days, uint8_t months, uint16_t years)
732{
733 // #:SCYYYY-MM-DD#
734 // Set date to YYYY-MM-DD (year, month, day). The date is expressed in local time.
735 // Returns:
736 // 0 if the date is invalid
737 // The character "1" without additional strings in ultra-precision mode (regardless of
738 // emulation).
739 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
740 char data[64];
741 snprintf(data, sizeof(data), ":SC%04d-%02d-%02d#", years, months, days);
742 return 0 == setStandardProcedureAndExpectChar(fd, data, "1");
743}
744
746{
747 // #:TLEL0<two line element>#
748 // Loads satellite orbital elements in two-line format directly from the command protocol.
749 // <two line element> is a string containing the two line elements. Each lines can be
750 // terminated by escaped newline (ASCII code 10), carriage return (ASCII code 13) or a
751 // combination of both. The first line may contain the satellite name. The entire string is
752 // escaped with the mechanism described in the "escaped strings" section below.
753 // The TLE format is described here:
754 // https://www.celestrak.com/NORAD/documentation/tle-fmt.asp
755 // For example, loading the NOAA 14 element set of that page can be accomplished with:
756 // :TLEL0NOAA·14·················$0a
757 // 1·23455U·94089A···97320.90946019··.00000140··00000-0··10191-3·0··2621$0a
758 // 2·23455··99.0090·272.6745·0008546·223.1686·136.8816·14.11711747148495#
759 // Returns:
760 // E# invalid format
761 // V# valid format
762 // Available from version 2.13.20.
763 LOGF_INFO("The function is called with TLE %s", tle);
764 if (strlen(tle) > 230)
765 {
766 LOG_WARN("TLE is too long");
767 }
768
769 std::string tle_str;
770 std::string sep = "$0a";
771 std::string search = "\n";
772 tle_str = (std::string) tle;
773 for( size_t pos = 0; ; pos += sep.length() )
774 {
775 // Locate the substring to replace
776 pos = tle_str.find( search, pos );
777 if( pos == std::string::npos ) break;
778 // Replace by erasing and inserting
779 tle_str.erase( pos, search.length() );
780 tle_str.insert( pos, sep );
781 }
782 char command[250];
783 snprintf(command, sizeof(command), ":TLEL0%s#", tle_str.c_str());
784
785 if ( !isSimulation() )
786 {
787 LOG_INFO(command);
788 char response[2];
789 if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 2) )
790 {
791 LOG_ERROR("TLE set error");
792 return 1;
793 }
794 if (response[0] == 'E')
795 {
796 LOG_ERROR("Invalid formatting of TLE, trying to split:");
797 char *pch = strtok ((char*) tle, "\n");
798 while (pch != nullptr)
799 {
800 LOGF_INFO("%s\n", pch);
801 pch = strtok (nullptr, "\n");
802 }
803 return 1;
804 }
805 }
806 else
807 {
808 char *pch = strtok ((char*) tle, "\n");
809 while (pch != nullptr)
810 {
811 LOGF_INFO("%s\n", pch);
812 pch = strtok (nullptr, "\n");
813 }
814 }
815 return 0;
816}
817
819{
820 // #:TLEDLn#
821 // Loads orbital elements for a satellite from the TLE database in the mount. n is the index of the
822 // orbital elements in the database, starting from 1 to the number returned by the TLEDN command.
823 // Returns:
824 // E# the mount database doesn't contain a TLE with the given index.
825 // <two line elements># an escaped string containing the TLE data from the mount
826 // database which has been loaded. Lines are terminated by ASCII newline (ASCII code 10).
827 // Available from version 2.13.20.
828 char command[12];
829 snprintf(command, sizeof(command), ":TLEDL%d#", tleN);
830
831 LOG_INFO("Setting TLE from Database");
832 if ( !isSimulation() )
833 {
834 LOG_INFO(command);
835 char response[210];
836 if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 210) )
837 {
838 LOG_ERROR("TLE set error");
839 return 1;
840 }
841 if (response[0] == 'E')
842 {
843 LOG_ERROR("TLE number not in mount");
844 return 1;
845 }
846 }
847 return 0;
848}
849
850bool LX200_10MICRON::CalculateSatTrajectory(std::string start_pass_isodatetime, std::string end_pass_isodatetime)
851{
852 // #:TLEPJD,min#
853 // Precalculates the first transit of the satellite with the currently loaded orbital elements,
854 // starting from Julian Date JD and for a period of min minutes, where min is from 1 to 1440.
855 // Two-line elements have to be loaded with the :TLEL command.
856 // Returns:
857 // E# no TLE loaded or invalid command
858 // N# no passes in the given amount of time
859 // JDstart,JDend,flags# data for the first pass in the given interval. JDstart and JDend
860 // mark the beginning and the end of the given transit. Flags is a string which can be
861 // empty or contain the letter F – meaning that mount will flip during the transit.
862 // Available from version 2.13.20.
863 struct ln_date start_pass;
864 if (extractISOTime(start_pass_isodatetime.c_str(), &start_pass) == -1)
865 {
866 LOGF_ERROR("Date/Time is invalid: %s.", start_pass_isodatetime.c_str());
867 return 1;
868 }
869
870 struct ln_date end_pass;
871 if (extractISOTime(end_pass_isodatetime.c_str(), &end_pass) == -1)
872 {
873 LOGF_ERROR("Date/Time is invalid: %s.", end_pass_isodatetime.c_str());
874 return 1;
875 }
876
877 double JD_start;
878 double JD_end;
879 JD_start = ln_get_julian_day(&start_pass);
880 JD_end = ln_get_julian_day(&end_pass);
881 int nextPassInMinutes = static_cast<int>(ceil((JD_end - JD_start) * 24 * 60));
882 int nextPassinMinutesUpTo1440 = std::min(nextPassInMinutes, 1440);
883 int nextPassinMinutesBetween1and1440 = std::max(nextPassinMinutesUpTo1440, 1);
884
885 char command[28];
886 snprintf(command, sizeof(command), ":TLEP%7.8f,%01d#", JD_start, nextPassinMinutesBetween1and1440);
887 LOGF_INFO("Julian day %7.8f", JD_start);
888 LOGF_INFO("For the next %01d minutes", nextPassinMinutesBetween1and1440);
889 LOGF_INFO("Command: %s", command);
890 if ( !isSimulation() )
891 {
892 LOG_INFO(command);
893 char response[36];
894 if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 36) )
895 {
896 LOG_ERROR("TLE calculate error");
897 return 1;
898 }
899 if (response[0] == 'E')
900 {
901 LOG_ERROR("TLE not loaded or invalid command");
902 return 1;
903 }
904 if (response[0] == 'N')
905 {
906 LOG_ERROR("No passes loaded");
907 return 1;
908 }
909 }
910 return 0;
911}
912
914{
915 // #:TLES#
916 // Slews to the start of the satellite transit that has been precalculated with the :TLEP command.
917 // Returns:
918 // E# no transit has been precalculated
919 // F# slew failed due to mount parked or other status blocking slews
920 // V# slewing to the start of the transit, the mount will automatically start tracking the satellite.
921 // S# the transit has already started, slewing to catch the satellite
922 // Q# the transit has already ended, no slew occurs
923 // Available from version 2.13.20.
924 char command[7];
925 snprintf(command, sizeof(command), ":TLES#");
926 if ( !isSimulation() )
927 {
928 LOG_INFO(command);
929 char response[2];
930 if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 2) )
931 {
932 LOG_ERROR("TLE track error");
933 return 1;
934 }
935 if (response[0] == 'E')
936 {
937 LOG_ERROR("TLE transit not calculated");
938 return 2;
939 }
940 if (response[0] == 'F')
941 {
942 LOG_ERROR("Slew failed");
943 return 3;
944 }
945 if (response[0] == 'V')
946 {
947 LOG_INFO("Slewing to start of transit");
948 return 0;
949 }
950 if (response[0] == 'S')
951 {
952 LOG_INFO("Slewing to transiting satellite");
953 return 0;
954 }
955 if (response[0] == 'Q')
956 {
957 LOG_ERROR("Transit is already over");
958 return 4;
959 }
960 }
961 return 0;
962}
963
965{
966 // #:SRTMPsTTT.T#
967 // Sets the temperature used in the refraction model to sTTT.T degrees Celsius (°C).
968 // Returns:
969 // 0 invalid
970 // 1 valid
971 // Available from version 2.3.0.
972 char data[16];
973 snprintf(data, 16, "#:SRTMP%0+6.1f#", temperature);
974 return setStandardProcedure(fd, data);
975}
976
978{
979 // #:SRPRSPPPP.P#
980 // Sets the atmospheric pressure used in the refraction model to PPPP.P hPa. Note
981 // that this is the pressure at the location of the telescope, and not the pressure at sea level.
982 // Returns:
983 // 0 invalid
984 // 1 valid
985 // Available from version 2.3.0.
986 char data[16];
987 snprintf(data, 16, "#:SRPRS%06.1f#", pressure);
988 return setStandardProcedure(fd, data);
989}
990
991int LX200_10MICRON::AddSyncPoint(double MRa, double MDec, double MSide, double PRa, double PDec, double SidTime)
992{
993 // #:newalptMRA,MDEC,MSIDE,PRA,PDEC,SIDTIME#
994 // Add a new point to the alignment specification. The parameters are:
995 // MRA – the mount-reported right ascension, expressed as HH:MM:SS.S
996 // MDEC – the mount-reported declination, expressed as sDD:MM:SS
997 // MSIDE – the mount-reported pier side (the letter 'E' or 'W', as reported by the :pS# command)
998 // PRA – the plate-solved right ascension (i.e. the right ascension the telescope was
999 // effectively pointing to), expressed as HH:MM:SS.S
1000 // PDEC – the plate-solved declination (i.e. the declination the telescope was effectively
1001 // pointing to), expressed as sDD:MM:SS
1002 // SIDTIME – the local sidereal time at the time of the measurement of the point,
1003 // expressed as HH:MM:SS.S
1004 // Returns:
1005 // the string "nnn#" if the point is valid, where nnn is the current number of points in the
1006 // alignment specification (including this one)
1007 // the string "E#" if the point is not valid
1008 // See also the paragraph Entering an alignment model.
1009 // Available from version 2.8.15.
1010 char MRa_str[32], MDec_str[32];
1011 fs_sexa(MRa_str, MRa, 0, 36000);
1012 fs_sexa(MDec_str, MDec, 0, 3600);
1013
1014 char MSide_char;
1015 (static_cast<int>(MSide) == 0) ? MSide_char = 'E' : MSide_char = 'W';
1016
1017 char PRa_str[32], PDec_str[32];
1018 fs_sexa(PRa_str, PRa, 0, 36000);
1019 fs_sexa(PDec_str, PDec, 0, 3600);
1020
1021 char SidTime_str[32];
1022 fs_sexa(SidTime_str, SidTime, 0, 36000);
1023
1024 char command[80];
1025 snprintf(command, 80, "#:newalpt%s,%s,%c,%s,%s,%s#", MRa_str, MDec_str, MSide_char, PRa_str, PDec_str, SidTime_str);
1026 LOGF_INFO("AddSyncPoint %s", command);
1027
1028 char response[6];
1029 if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 5) || response[0] == 'E')
1030 {
1031 LOG_ERROR("AddSyncPoint error");
1032 return 1;
1033 }
1034 response[4] = 0;
1035 int points;
1036 int nbytes_read = sscanf(response, "%3d#", &points);
1037 if (nbytes_read < 0)
1038 {
1039 LOGF_ERROR("AddSyncPoint response error %d", nbytes_read);
1040 return 1;
1041 }
1042 LOGF_INFO("AddSyncPoint responded [%4s], there are now %d new alignment points", response, points);
1043 NewAlignmentPointsN[0].value = points;
1045
1046 return 0;
1047}
1048
1049int LX200_10MICRON::AddSyncPointHere(double PRa, double PDec)
1050{
1051 double MSide = (toupper(Ginfo.SideOfPier) == 'E') ? 0 : 1;
1052 return AddSyncPoint(Ginfo.RA_JNOW, Ginfo.DEC_JNOW, MSide, PRa, PDec, Ginfo.SiderealTime);
1053}
1054
1055bool LX200_10MICRON::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
1056{
1057 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1058 {
1059 if (strcmp(name, REFRACTION_MODEL_TEMPERATURE) == 0)
1060 {
1061 IUUpdateNumber(&RefractionModelTemperatureNP, values, names, n);
1063 {
1064 LOG_ERROR("SetRefractionModelTemperature error");
1067 return false;
1068 }
1071 LOGF_INFO("RefractionModelTemperature set to %0+6.1f degrees C", RefractionModelTemperatureN[0].value);
1072 return true;
1073 }
1074 if (strcmp(name, REFRACTION_MODEL_PRESSURE) == 0)
1075 {
1076 IUUpdateNumber(&RefractionModelPressureNP, values, names, n);
1078 {
1079 LOG_ERROR("SetRefractionModelPressure error");
1082 return false;
1083 }
1086 LOGF_INFO("RefractionModelPressure set to %06.1f hPa", RefractionModelPressureN[0].value);
1087 return true;
1088 }
1089 if (strcmp(name, MODEL_COUNT) == 0)
1090 {
1091 IUUpdateNumber(&ModelCountNP, values, names, n);
1093 IDSetNumber(&ModelCountNP, nullptr);
1094 LOGF_INFO("ModelCount %d", ModelCountN[0].value);
1095 return true;
1096 }
1097 if (strcmp(name, MINIMAL_NEW_ALIGNMENT_POINT_RO) == 0)
1098 {
1099 IUUpdateNumber(&MiniNewAlpNP, values, names, n);
1101 IDSetNumber(&MiniNewAlpRONP, nullptr);
1102 return true;
1103 }
1104 if (strcmp(name, MINIMAL_NEW_ALIGNMENT_POINT) == 0)
1105 {
1106 if (AlignmentState != ALIGN_START)
1107 {
1108 LOG_ERROR("Cannot add alignment points yet, need to start a new alignment first");
1109 return false;
1110 }
1111
1112 IUUpdateNumber(&MiniNewAlpNP, values, names, n);
1114 {
1115 LOG_ERROR("AddSyncPointHere error");
1117 IDSetNumber(&MiniNewAlpNP, nullptr);
1118 return false;
1119 }
1121 IDSetNumber(&MiniNewAlpNP, nullptr);
1122 return true;
1123 }
1124 if (strcmp(name, NEW_ALIGNMENT_POINT) == 0)
1125 {
1126 if (AlignmentState != ALIGN_START)
1127 {
1128 LOG_ERROR("Cannot add alignment points yet, need to start a new alignment first");
1129 return false;
1130 }
1131
1132 IUUpdateNumber(&NewAlpNP, values, names, n);
1133 if (0 != AddSyncPoint(NewAlpN[ALP_MRA].value, NewAlpN[ALP_MDEC].value, NewAlpN[ALP_MSIDE].value,
1134 NewAlpN[ALP_PRA].value, NewAlpN[ALP_PDEC].value, NewAlpN[ALP_SIDTIME].value))
1135 {
1136 LOG_ERROR("AddSyncPoint error");
1138 IDSetNumber(&NewAlpNP, nullptr);
1139 return false;
1140 }
1141 NewAlpNP.s = IPS_OK;
1142 IDSetNumber(&NewAlpNP, nullptr);
1143 return true;
1144 }
1145 if (strcmp(name, NEW_ALIGNMENT_POINTS) == 0)
1146 {
1147 IUUpdateNumber(&NewAlignmentPointsNP, values, names, n);
1150 LOGF_INFO("New unnamed Model now has %d alignment points", NewAlignmentPointsN[0].value);
1151 return true;
1152 }
1153 if (strcmp(name, "TLE_NUMBER") == 0)
1154 {
1155 LOG_INFO("I am trying to set from Database");
1156
1157 IUUpdateNumber(&TLEfromDatabaseNP, values, names, n);
1158 if ( 0 != SetTLEfromDatabase(TLEfromDatabaseN[0].value) )
1159 {
1161 IDSetNumber(&TLEfromDatabaseNP, nullptr);
1162 return false;
1163 }
1167 IDSetNumber(&TLEfromDatabaseNP, nullptr);
1168 LOGF_INFO("Selected TLE nr %.0f from database", TLEfromDatabaseN[0].value);
1169
1170 return true;
1171 }
1172 }
1173
1174 // Let INDI::LX200Generic handle any other number properties
1175 return LX200Generic::ISNewNumber(dev, name, values, names, n);
1176}
1177
1178bool LX200_10MICRON::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1179{
1180 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1181 {
1182 if (strcmp(AlignmentStateSP.name, name) == 0)
1183 {
1184 IUUpdateSwitch(&AlignmentStateSP, states, names, n);
1186
1187 switch (index)
1188 {
1189 case ALIGN_IDLE:
1190 AlignmentState = ALIGN_IDLE;
1191 LOG_INFO("Alignment state is IDLE");
1192 break;
1193
1194 case ALIGN_START:
1195 // #:newalig#
1196 // Start creating a new alignment specification, that will be entered with the :newalpt command.
1197 // Returns:
1198 // the string "V#" (this is always successful).
1199 // Available from version 2.8.15.
1200 if (0 != setStandardProcedureAndExpectChar(fd, "#:newalig#", "V"))
1201 {
1202 LOG_ERROR("New alignment start error");
1204 IDSetSwitch(&AlignmentStateSP, nullptr);
1205 return false;
1206 }
1207 LOG_INFO("New Alignment started");
1208 AlignmentState = ALIGN_START;
1209 break;
1210
1211 case ALIGN_END:
1212 // #:endalig#
1213 // Completes the alignment specification and computes a new alignment from the given
1214 // alignment points.
1215 // Returns:
1216 // the string "V#" if the alignment has been computed successfully
1217 // the string "E#" if the alignment couldn't be computed successfully with the current
1218 // alignment specification. In this case the previous alignment is retained.
1219 // Available from version 2.8.15.
1220 if (0 != setStandardProcedureAndExpectChar(fd, "#:endalig#", "V"))
1221 {
1222 LOG_ERROR("New alignment end error");
1224 IDSetSwitch(&AlignmentStateSP, nullptr);
1225 return false;
1226 }
1227 LOG_INFO("New Alignment ended");
1228 AlignmentState = ALIGN_END;
1229 break;
1230
1232 // #:delalig#
1233 // Deletes the current alignment model and stars.
1234 // Returns: an empty string terminated by '#'.
1235 // Available from version 2.8.15.
1236 if (0 != setStandardProcedureAndExpectChar(fd, "#:delalig#", "#"))
1237 {
1238 LOG_ERROR("Delete current alignment error");
1240 IDSetSwitch(&AlignmentStateSP, nullptr);
1241 return false;
1242 }
1243 LOG_INFO("Current Alignment deleted");
1244 AlignmentState = ALIGN_DELETE_CURRENT;
1245 break;
1246
1247 default:
1249 IDSetSwitch(&AlignmentStateSP, "Unknown alignment index %d", index);
1250 AlignmentState = ALIGN_IDLE;
1251 return false;
1252 }
1253
1255 IDSetSwitch(&AlignmentStateSP, nullptr);
1256 return true;
1257 }
1258 if (TrackSatSP.isNameMatch(name))
1259 {
1260
1261 TrackSatSP.update(states, names, n);
1262 int index = TrackSatSP.findOnSwitchIndex();
1263
1264 switch (index)
1265 {
1266 case SAT_TRACK:
1267 if ( 0 != TrackSat() )
1268 {
1270 TrackSatSP.apply();
1271 LOG_ERROR("Tracking failed");
1272 return false;
1273 }
1275 TrackSatSP.apply();
1276 LOG_INFO("Tracking satellite");
1277 return true;
1278 case SAT_HALT:
1279 if ( !Abort() )
1280 {
1282 TrackSatSP.apply();
1283 LOG_ERROR("Halt failed");
1284 return false;
1285 }
1287 TrackSatSP.apply();
1288 LOG_INFO("Halt tracking");
1289 return true;
1290 default:
1292 LOGF_ERROR("Unknown tracking modus %d", index);
1293 return false;
1294 }
1295 }
1296
1297 if (strcmp(UnattendedFlipSP.name, name) == 0)
1298 {
1299 IUUpdateSwitch(&UnattendedFlipSP, states, names, n);
1301 switch (index)
1302 {
1304 if (false == setUnattendedFlipSetting(false))
1305 {
1306 LOG_ERROR("Setting unattended flip failed");
1308 IDSetSwitch(&UnattendedFlipSP, nullptr);
1309 return false;
1310 }
1311 LOG_INFO("Unattended flip disabled");
1312 break;
1314 if (false == setUnattendedFlipSetting(true))
1315 {
1316 LOG_ERROR("Setting unattended flip failed");
1318 IDSetSwitch(&UnattendedFlipSP, nullptr);
1319 return false;
1320 }
1321 LOG_INFO("Unattended flip enabled");
1322 break;
1323 default:
1325 IDSetSwitch(&UnattendedFlipSP, "Unknown unattended flip setting %d", index);
1326 return false;
1327 }
1329 IDSetSwitch(&UnattendedFlipSP, nullptr);
1330 return true;
1331 }
1332 if (strcmp(WoLSendSP.name, name) == 0)
1333 {
1335 if (sendWakeOnLanPacket())
1336 {
1337 WoLSendSP.s = IPS_OK;
1338 LOG_INFO("Wake on LAN packet sent successfully.");
1339 }
1340 else
1341 {
1343 LOG_ERROR("Failed to send Wake on LAN packet.");
1344 }
1345 IDSetSwitch(&WoLSendSP, nullptr);
1346 return true;
1347 }
1348 if (MountShutdownSP.isNameMatch(name))
1349 {
1351 if (!isParked())
1352 {
1354 LOG_WARN("Mount must be parked before shutdown.");
1356 return false;
1357 }
1358 // :shutdown# — returns '1' on success, '0' on failure; ~20 s until power-off
1359 if (0 != setStandardProcedureAndExpectChar(fd, ":shutdown#", "1"))
1360 {
1362 LOG_ERROR("Mount shutdown command failed.");
1364 return false;
1365 }
1367 LOG_INFO("Mount shutdown initiated. Disconnecting before mount powers off.");
1369 // Defer disconnect so ISNewSwitch returns cleanly before we tear down the property list
1370 m_ShutdownPending = true;
1371 SetTimer(100);
1372 return true;
1373 }
1374 }
1375
1376 return LX200Generic::ISNewSwitch(dev, name, states, names, n);
1377}
1378
1379bool LX200_10MICRON::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
1380{
1381 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1382 {
1383 if (strcmp(name, NEW_MODEL_NAME) == 0)
1384 {
1385 IUUpdateText(&NewModelNameTP, texts, names, n);
1387 IDSetText(&NewModelNameTP, nullptr);
1388 LOGF_INFO("Model saved with name %s", NewModelNameT[0].text);
1389 return true;
1390 }
1391 if (strcmp(name, "SAT_TLE_TEXT") == 0)
1392 {
1393
1394 TLEtoTrackTP.update(texts, names, n);
1395 if (0 == SetTLEtoFollow(TLEtoTrackTP[0].getText()))
1396 {
1400 IDSetNumber(&TLEfromDatabaseNP, nullptr);
1401 LOGF_INFO("Selected TLE %s", TLEtoTrackTP[0].getText());
1402 return true;
1403 }
1404 else
1405 {
1409 IDSetNumber(&TLEfromDatabaseNP, nullptr);
1410 LOG_ERROR("TLE was not correctly uploaded");
1411 return false;
1412 }
1413 }
1414 if (strcmp(name, "SAT_PASS_WINDOW") == 0)
1415 {
1416 SatPassWindowTP.update(texts, names, n);
1418 {
1421 LOG_INFO("Trajectory set");
1422 return true;
1423 }
1424 else
1425 {
1428 LOG_ERROR("Trajectory could not be calculated");
1429 return false;
1430 }
1431 }
1432 if (strcmp(name, WAKE_ON_LAN_MAC) == 0)
1433 {
1434 IUUpdateText(&WoLMacTP, texts, names, n);
1435 WoLMacTP.s = IPS_OK;
1436 IDSetText(&WoLMacTP, nullptr);
1437 LOGF_INFO("WoL MAC address set to %s", WoLMacT[0].text);
1438 return true;
1439 }
1440 }
1441 return LX200Generic::ISNewText(dev, name, texts, names, n);
1442}
1443
1444// this should move to some generic library
1445int LX200_10MICRON::monthToNumber(const char *monthName)
1446{
1447 struct entry
1448 {
1449 const char *name;
1450 int id;
1451 };
1452 entry month_table[] = { { "Jan", 1 }, { "Feb", 2 }, { "Mar", 3 }, { "Apr", 4 }, { "May", 5 },
1453 { "Jun", 6 }, { "Jul", 7 }, { "Aug", 8 }, { "Sep", 9 }, { "Oct", 10 },
1454 { "Nov", 11 }, { "Dec", 12 }, { nullptr, 0 }
1455 };
1456 entry *p = month_table;
1457 while (p->name != nullptr)
1458 {
1459 if (strcasecmp(p->name, monthName) == 0)
1460 return p->id;
1461 ++p;
1462 }
1463 return 0;
1464}
1465
1467{
1468 int error_type;
1469 int nbytes_write = 0;
1470
1471 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s>", data);
1472 tcflush(fd, TCIFLUSH);
1473 if ((error_type = tty_write_string(fd, data, &nbytes_write)) != TTY_OK)
1474 {
1475 LOGF_ERROR("CMD <%s> write ERROR %d", data, error_type);
1476 return error_type;
1477 }
1478 tcflush(fd, TCIFLUSH);
1479 return 0;
1480}
1481
1482int LX200_10MICRON::setStandardProcedureAndExpectChar(int fd, const char *data, const char *expect)
1483{
1484 char bool_return[2];
1485 int error_type;
1486 int nbytes_write = 0, nbytes_read = 0;
1487
1488 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s>", data);
1489 tcflush(fd, TCIFLUSH);
1490 if ((error_type = tty_write_string(fd, data, &nbytes_write)) != TTY_OK)
1491 {
1492 LOGF_ERROR("CMD <%s> write ERROR %d", data, error_type);
1493 return error_type;
1494 }
1495 error_type = tty_read(fd, bool_return, 1, LX200_TIMEOUT, &nbytes_read);
1496 tcflush(fd, TCIFLUSH);
1497
1498 if (nbytes_read < 1)
1499 {
1500 LOGF_ERROR("CMD <%s> read ERROR %d", data, error_type);
1501 return error_type;
1502 }
1503
1504 if (bool_return[0] != expect[0])
1505 {
1506 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s> failed.", data);
1507 return -1;
1508 }
1509
1510 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s> successful.", data);
1511
1512 return 0;
1513}
1514
1515int LX200_10MICRON::setStandardProcedureAndReturnResponse(int fd, const char *data, char *response, int max_response_length)
1516{
1517 int error_type;
1518 int nbytes_write = 0, nbytes_read = 0;
1519
1520 DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s>", data);
1521 tcflush(fd, TCIFLUSH);
1522 if ((error_type = tty_write_string(fd, data, &nbytes_write)) != TTY_OK)
1523 {
1524 LOGF_ERROR("CMD <%s> write ERROR %d", data, error_type);
1525 return error_type;
1526 }
1527 error_type = tty_read(fd, response, max_response_length, LX200_TIMEOUT, &nbytes_read);
1528 tcflush(fd, TCIFLUSH);
1529
1530 if (nbytes_read < 1)
1531 {
1532 LOGF_ERROR("CMD <%s> read ERROR %d", data, error_type);
1533 return error_type;
1534 }
1535
1536 return 0;
1537}
1538
1539void LX200_10MICRON::TimerHit()
1540{
1541 if (m_ShutdownPending)
1542 {
1543 m_ShutdownPending = false;
1544 // Standard INDI disconnect sequence — same as clicking Disconnect in the UI
1545 Disconnect();
1546 setConnected(false, IPS_IDLE);
1548 return; // do not reschedule polling
1549 }
1551}
1552
1553bool LX200_10MICRON::sendWakeOnLanPacket()
1554{
1555 const char *macStr = WoLMacT[0].text;
1556 if (!macStr || macStr[0] == '\0')
1557 {
1558 LOG_ERROR("WoL: MAC address not configured.");
1559 return false;
1560 }
1561
1562 std::string cmd = "wakeonlan " + std::string(macStr);
1563 int ret = system(cmd.c_str());
1564 if (ret == 0)
1565 {
1566 LOGF_INFO("WoL: magic packet sent to %s", macStr);
1567 return true;
1568 }
1569 if (ret == 127)
1570 {
1571 LOG_ERROR("WoL: wakeonlan tool not found. Please install it on your system.");
1572 return false;
1573 }
1574 LOG_ERROR("WoL: wakeonlan failed to send the packet.");
1575 return false;
1576}
double temperature
bool isConnected() const
INDI::PropertyText getText(const char *name) const
virtual bool Disconnect()
Disconnect from device.
virtual void setConnected(bool status, IPState state=IPS_OK, const char *msg=nullptr)
Set connection switch status in the client.
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 void TimerHit()
Callback function to be called once SetTimer duration elapses.
bool isSimulation() const
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
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....
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
void setPierSide(TelescopePierSide side)
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool initProperties() override
Called to initialize basic properties required all the time.
INumber TrackFreqN[1]
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool sendScopeLocation()
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
INumberVectorProperty TrackFreqNP
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool sendScopeTime()
void setLX200Capability(uint32_t cap)
INumber RefractionModelTemperatureN[1]
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
void getBasicData() override
bool Handshake() override
perform handshake with device to check communication
bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
INumberVectorProperty ModelCountNP
INumberVectorProperty NewAlignmentPointsNP
ISwitchVectorProperty AlignmentStateSP
ITextVectorProperty NewModelNameTP
bool CalculateSatTrajectory(std::string start_pass_isodatetime, std::string end_pass_isodatetime)
INumberVectorProperty NewAlpNP
INumber NewAlignmentPointsN[1]
int setStandardProcedureAndExpectChar(int fd, const char *data, const char *expect)
ITextVectorProperty WoLMacTP
bool UnPark() override
Unpark the telescope if already parked.
int monthToNumber(const char *monthName)
INumberVectorProperty RefractionModelPressureNP
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
int setStandardProcedureWithoutRead(int fd, const char *data)
INumberVectorProperty RefractionModelTemperatureNP
bool SetTLEtoFollow(const char *tle)
ISwitchVectorProperty WoLSendSP
bool getUnattendedFlipSetting()
IText NewModelNameT[1]
ITextVectorProperty ProductTP
bool setLocalDate(uint8_t days, uint8_t months, uint16_t years) override
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
const char * getDefaultName() override
virtual int SetRefractionModelPressure(double pressure)
INumber MiniNewAlpN[MALP_COUNT]
bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
INumberVectorProperty TLEfromDatabaseNP
INumber NewAlpN[ALP_COUNT]
int setStandardProcedureAndReturnResponse(int fd, const char *data, char *response, int max_response_length)
@ GSTAT_NOT_TRACKING_AND_NOT_MOVING
bool ReadScopeStatus() override
Read telescope status.
ISwitch UnattendedFlipS[UNATTENDED_FLIP_COUNT]
bool initProperties() override
Called to initialize basic properties required all the time.
INDI::PropertySwitch MountShutdownSP
INumber MiniNewAlpRON[MALPRO_COUNT]
INumber ModelCountN[1]
bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual int SetRefractionModelTemperature(double temperature)
ISwitch WoLSendS[1]
bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
bool Park() override
Park the telescope to its home position.
bool SyncConfigBehaviour(bool cmcfg)
INumberVectorProperty MiniNewAlpRONP
ISwitchVectorProperty UnattendedFlipSP
bool Flip(double ra, double dec) override
Move and flip the scope to the supplied RA and DEC coordinates.
INumber TLEfromDatabaseN[1]
INumber AlignmentPointsN[1]
int AddSyncPoint(double MRa, double MDec, double MSide, double PRa, double PDec, double SidTime)
INumberVectorProperty MiniNewAlpNP
ISwitch AlignmentStateS[ALIGN_COUNT]
INumberVectorProperty AlignmentPointsNP
bool SetTLEfromDatabase(int tleN)
int AddSyncPointHere(double PRa, double PDec)
bool setUnattendedFlipSetting(bool setting)
INumber RefractionModelPressureN[1]
const char * CONNECTION_TAB
CONNECTION_TAB Where all device connection settings (serial, usb, ethernet) are defined and controlle...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * SATELLITE_TAB
SATELLITE_TAB.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
INDI::PropertySwitch ParkSP
INDI::PropertyText TLEtoTrackTP
Text Vector property defining the orbital elements of an artificial satellite (TLE)....
INDI::PropertyText SatPassWindowTP
Text Vector property defining the start and end of a satellite pass (window contains pass)....
INDI::PropertySwitch TrackSatSP
Switch Vector property defining the state of the satellite tracking of the mount. Example implementat...
@ SAT_PASS_WINDOW_END
Index for end of the window.
@ SAT_PASS_WINDOW_START
Index for start of the window.
@ SAT_TRACK
Track signal.
@ SAT_HALT
Halt signal (abort)
double ra
double dec
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
@ 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
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:653
int f_scansexa(const char *str0, double *dp)
convert sexagesimal string str AxBxC to double. x can be anything non-numeric. Any missing A,...
Definition indicom.c:194
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
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition indicom.c:130
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
int extractISOTime(const char *timestr, struct ln_date *iso_date)
Extract ISO 8601 time and store it in a tm struct.
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition indidevapi.c:33
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:490
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition indidevapi.c:337
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition indidevapi.c:366
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:509
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition indidevapi.c:44
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition indidevapi.c:376
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition indidevapi.c:416
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition indidevapi.c:398
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition indidevapi.c:28
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:453
#define INDI_UNUSED(x)
Definition indidevapi.h:132
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
Definition indidriver.c:908
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
int IUGetConfigSwitch(const char *dev, const char *property, const char *member, ISState *value)
IUGetConfigSwitch Opens configuration file and reads single switch property.
Definition indidriver.c:657
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_WARN(txt)
Definition indilogger.h:73
#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 DEBUGFDEVICE(device, priority, msg,...)
Definition indilogger.h:61
#define getDeviceName
int fd
#define WAKE_ON_LAN_SEND
#define ALIGNMENT_POINTS
#define WAKE_ON_LAN_MAC
#define NEW_ALIGNMENT_POINT
#define MINIMAL_NEW_ALIGNMENT_POINT
#define REFRACTION_MODEL_PRESSURE
#define ALIGNMENT_TAB
#define NEW_ALIGNMENT_POINTS
#define MOUNT_SHUTDOWN
#define PRODUCT_TAB
#define MINIMAL_NEW_ALIGNMENT_POINT_RO
#define MODEL_COUNT
#define NEW_MODEL_NAME
#define PRODUCT_INFO
#define REFRACTION_MODEL_TEMPERATURE
#define ALIGNMENT_STATE
#define UNATTENDED_FLIP
#define LX200_TIMEOUT
int getCommandInt(int fd, int *value, const char *cmd)
int setStandardProcedure(int fd, const char *data)
int checkLX200EquatorialFormat(int fd)
int setCommandInt(int fd, int data, const char *cmd)
int getTrackFreq(int fd, double *value)
int getCommandString(int fd, char *data, const char *cmd)
@ LX200_24
Definition lx200driver.h:64
uint8_t cmd[4]
Definition pwc-ioctl.h:2
char name[MAXINDINAME]
Definition indiapi.h:321
char name[MAXINDINAME]
Definition indiapi.h:369
char name[MAXINDINAME]
Definition indiapi.h:248