Instrument Neutral Distributed Interface INDI 2.2.5
pmc8.cpp
Go to the documentation of this file.
1/*
2 INDI Explore Scientific PMC8 driver
3
4 Copyright (C) 2017 Michael Fulbright
5
6 Additional contributors:
7 Thomas Olson, Copyright (C) 2019
8 Karl Rees, Copyright (C) 2019-2023
9 Martin Ruiz, Copyright (C) 2023
10 Daniel Karnaukh, Copyright (C) 2026
11
12 Based on IEQPro driver.
13
14 This library is free software; you can redistribute it and/or
15 modify it under the terms of the GNU Lesser General Public
16 License as published by the Free Software Foundation; either
17 version 2.1 of the License, or (at your option) any later version.
18
19 This library is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 Lesser General Public License for more details.
23
24 You should have received a copy of the GNU Lesser General Public
25 License along with this library; if not, write to the Free Software
26 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27*/
28/* Experimental Mount selector switch G11 vs EXOS2 by Thomas Olson
29 *
30 */
31
32#include "pmc8.h"
33
34#include <indicom.h>
37
38#include <libnova/sidereal_time.h>
39
40#include <memory>
41
42#include <math.h>
43#include <string.h>
44
45/* Simulation Parameters */
46#define SLEWRATE 3 /* slew rate, degrees/s */
47
48#define MOUNTINFO_TAB "Mount Info"
49
50#define PMC8_DEFAULT_PORT 54372
51#define PMC8_DEFAULT_IP_ADDRESS "192.168.47.1"
52#define PMC8_TRACKING_AUTODETECT_INTERVAL 10
53#define PMC8_VERSION_MAJOR 0
54#define PMC8_VERSION_MINOR 5
55
56static std::unique_ptr<PMC8> scope(new PMC8());
57
58/* Constructor */
59PMC8::PMC8() : GI(this)
60{
61 currentRA = ln_get_apparent_sidereal_time(ln_get_julian_from_sys());
62 if (LocationNP[LOCATION_LATITUDE].getValue() < 0)
63 currentDEC = -90;
64 else
65 currentDEC = 90;
66
67 DBG_SCOPE = INDI::Logger::getInstance().addDebugLevel("Scope Verbose", "SCOPE");
68
72 9);
73
75}
76
78{
79}
80
82{
83 return "PMC8";
84}
85
87{
89
90 // Serial Cable Connection Type
91 // Letting them choose standard cable can speed up connection time significantly
92 IUFillSwitch(&SerialCableTypeS[0], "SERIAL_CABLE_AUTO", "Auto", ISS_ON);
93 IUFillSwitch(&SerialCableTypeS[1], "SERIAL_CABLE_INVERTED", "Inverted", ISS_OFF);
94 IUFillSwitch(&SerialCableTypeS[2], "SERIAL_CABLE_STANDARD", "Standard", ISS_OFF);
95 IUFillSwitchVector(&SerialCableTypeSP, SerialCableTypeS, 3, getDeviceName(), "SERIAL_CABLE_TYPE", "Serial Cable",
97
98 // Mount Type
99 IUFillSwitch(&MountTypeS[MOUNT_G11], "MOUNT_G11", "G11", ISS_OFF);
100 IUFillSwitch(&MountTypeS[MOUNT_EXOS2], "MOUNT_EXOS2", "EXOS2", ISS_OFF);
101 IUFillSwitch(&MountTypeS[MOUNT_iEXOS100], "MOUNT_iEXOS100", "iEXOS100", ISS_OFF);
102 IUFillSwitchVector(&MountTypeSP, MountTypeS, 3, getDeviceName(), "MOUNT_TYPE", "Mount Type", CONNECTION_TAB, IP_RW,
104
105
106 /* Tracking Mode */
107 // order is important, since driver assumes solar = 1, lunar = 2
108 AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
109 AddTrackMode("TRACK_SOLAR", "Solar");
110 AddTrackMode("TRACK_LUNAR", "Lunar");
111 //AddTrackMode("TRACK_KING", "King"); // King appears to be effectively the same as Solar, at least for EXOS-2, and a bit of pain to implement with auto-detection
112 AddTrackMode("TRACK_CUSTOM", "Custom");
113
114 // Set TrackRate limits
115 /*TrackRateN[AXIS_RA].min = -PMC8_MAX_TRACK_RATE;
116 TrackRateN[AXIS_RA].max = PMC8_MAX_TRACK_RATE;
117 TrackRateN[AXIS_DE].min = -0.01;
118 TrackRateN[AXIS_DE].max = 0.01;*/
119
120 // what to do after goto operation
121 IUFillSwitch(&PostGotoS[0], "GOTO_START_TRACKING", "Start / Resume Tracking", ISS_ON);
122 IUFillSwitch(&PostGotoS[1], "GOTO_RESUME_PREVIOUS", "Previous State", ISS_OFF);
123 IUFillSwitch(&PostGotoS[2], "GOTO_STOP_TRACKING", "No Tracking", ISS_OFF);
124 IUFillSwitchVector(&PostGotoSP, PostGotoS, 3, getDeviceName(), "POST_GOTO_SETTINGS", "Post Goto", MOTION_TAB, IP_RW,
126
127 // relabel move speeds
128 SlewRateSP[0].setLabel("4x");
129 SlewRateSP[1].setLabel("8x");
130 SlewRateSP[2].setLabel("16x");
131 SlewRateSP[3].setLabel("32x");
132 SlewRateSP[4].setLabel("64x");
133 SlewRateSP[5].setLabel("128x");
134 SlewRateSP[6].setLabel("256x");
135 SlewRateSP[7].setLabel("512x");
136 SlewRateSP[8].setLabel("833x");
137
138 // settings for ramping up/down when moving
139 IUFillNumber(&RampN[0], "RAMP_INTERVAL", "Interval (ms)", "%g", 20, 1000, 5, 200);
140 IUFillNumber(&RampN[1], "RAMP_BASESTEP", "Base Step", "%g", 1, 256, 1, 4);
141 IUFillNumber(&RampN[2], "RAMP_FACTOR", "Factor", "%g", 1.0, 2.0, 0.1, 1.4);
142 IUFillNumberVector(&RampNP, RampN, 3, getDeviceName(), "RAMP_SETTINGS", "Move Ramp", MOTION_TAB, IP_RW, 0, IPS_IDLE);
143
144 /* How fast do we guide compared to sidereal rate */
145 IUFillNumber(&GuideRateN[0], "GUIDE_RATE_RA", "RA (x Sidereal)", "%g", 0.1, 1.0, 0.1, 0.4);
146 IUFillNumber(&GuideRateN[1], "GUIDE_RATE_DE", "DEC (x Sidereal)", "%g", 0.1, 1.0, 0.1, 0.4);
147 IUFillNumberVector(&GuideRateNP, GuideRateN, 2, getDeviceName(), "GUIDE_RATE", "Guide Rate", GUIDE_TAB, IP_RW, 0, IPS_IDLE);
148 IUFillNumber(&LegacyGuideRateN[0], "LEGACY_GUIDE_RATE", "x Sidereal", "%g", 0.1, 1.0, 0.1, 0.4);
149 IUFillNumberVector(&LegacyGuideRateNP, LegacyGuideRateN, 1, getDeviceName(), "LEGACY_GUIDE_RATE", "Guide Rate", GUIDE_TAB,
150 IP_RW, 0, IPS_IDLE);
151
153
155
156 // Driver does not support custom parking yet.
158
160
162
163 IUFillText(&FirmwareT[0], "Version", "Version", "");
164 IUFillTextVector(&FirmwareTP, FirmwareT, 1, getDeviceName(), "Firmware", "Firmware", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
165
167
168 return true;
169}
170
172{
174
175 if (isConnected())
176 {
177 getStartupData();
178
179 defineProperty(&PostGotoSP);
180 loadConfig(true, PostGotoSP.name);
181
182 defineProperty(&RampNP);
183 loadConfig(true, RampNP.name);
184
185 if (firmwareInfo.IsRev2Compliant)
186 {
187 defineProperty(&GuideRateNP);
188 }
189 else
190 {
191 defineProperty(&LegacyGuideRateNP);
192 }
193
194 defineProperty(&FirmwareTP);
195
196 // do not support park position
199 }
200 else
201 {
202 deleteProperty(PostGotoSP.name);
203
204 if (firmwareInfo.IsRev2Compliant)
205 {
206 deleteProperty(GuideRateNP.name);
207 }
208 else
209 {
210 deleteProperty(LegacyGuideRateNP.name);
211 }
212
213 deleteProperty(FirmwareTP.name);
214
215 deleteProperty(RampNP.name);
216 }
217
219
220 return true;
221}
222
223void PMC8::getStartupData()
224{
225 LOG_DEBUG("Getting firmware data...");
226 if (get_pmc8_firmware(PortFD, &firmwareInfo))
227 {
228 const char *c;
229
230 FirmwareTP.s = IPS_OK;
231 c = firmwareInfo.MainBoardFirmware.c_str();
232 LOGF_INFO("firmware = %s.", c);
233
234 // not sure if there's really a point to the mount switch anymore if we know the mount from the firmware - perhaps remove as newer firmware becomes standard?
235 // populate mount type switch in interface from firmware if possible
236 if (firmwareInfo.MountType == MOUNT_EXOS2)
237 {
238 MountTypeS[MOUNT_EXOS2].s = ISS_ON;
239 LOG_INFO("Detected mount type as Exos2.");
240 }
241 else if (firmwareInfo.MountType == MOUNT_G11)
242 {
243 MountTypeS[MOUNT_G11].s = ISS_ON;
244 LOG_INFO("Detected mount type as G11.");
245 }
246 else if (firmwareInfo.MountType == MOUNT_iEXOS100)
247 {
248 MountTypeS[MOUNT_iEXOS100].s = ISS_ON;
249 LOG_INFO("Detected mount type as iExos100.");
250 }
251 else
252 {
253 LOG_INFO("Cannot detect mount type--perhaps this is older firmware?");
254 if (strstr(getDeviceName(), "EXOS2"))
255 {
256 MountTypeS[MOUNT_EXOS2].s = ISS_ON;
257 LOG_INFO("Guessing mount is EXOS2 from device name.");
258 }
259 else if (strstr(getDeviceName(), "iEXOS100"))
260 {
261 MountTypeS[MOUNT_iEXOS100].s = ISS_ON;
262 LOG_INFO("Guessing mount is iEXOS100 from device name.");
263 }
264 else
265 {
266 MountTypeS[MOUNT_G11].s = ISS_ON;
267 LOG_INFO("Guessing mount is G11.");
268 }
269 }
270 MountTypeSP.s = IPS_OK;
271 IDSetSwitch(&MountTypeSP, nullptr);
272
273 IUSaveText(&FirmwareT[0], c);
274 IDSetText(&FirmwareTP, nullptr);
275 }
276
277 // get SRF values
278 if (firmwareInfo.IsRev2Compliant)
279 {
280 double rate = 0.4;
282 {
283 GuideRateN[0].value = rate;
284 GuideRateNP.s = IPS_OK;
285 IDSetNumber(&GuideRateNP, nullptr);
286 }
288 {
289 GuideRateN[1].value = rate;
290 GuideRateNP.s = IPS_OK;
291 IDSetNumber(&GuideRateNP, nullptr);
292 }
293 }
294
295 // PMC8 doesn't store location permanently so read from config and set
296 // Convert to INDI standard longitude (0 to 360 Eastward)
297 double longitude = LocationNP[LOCATION_LONGITUDE].getValue();
298 double latitude = LocationNP[LOCATION_LATITUDE].getValue();
299 if (latitude < 0)
300 currentDEC = -90;
301 else
302 currentDEC = 90;
303
304
305 // must also keep "low level" aware of position to convert motor counts to RA/DEC
306 set_pmc8_location(latitude, longitude);
307
308 // seems like best place to put a warning that will be seen in log window of EKOS/etc
309 LOG_INFO("The PMC-Eight driver is in BETA development currently.");
310 LOG_INFO("Be prepared to intervene if something unexpected occurs.");
311
312#if 0
313 // Park position initialization
314 // Southern Hemisphere support: Use get_pmc8_east_dir() to determine proper DEC park position
315 // In northern hemisphere, park at DEC +90; in southern hemisphere, park at DEC -90
316 double HA = ln_get_apparent_sidereal_time(ln_get_julian_from_sys());
317 double DEC = get_pmc8_east_dir() ? 90.0 : -90.0; // +90 for north, -90 for south
318
319 // currently only park at motor position (0, 0)
320 if (InitPark())
321 {
322 // If loading parking data is successful, we just set the default parking values.
325 }
326 else
327 {
328 // Otherwise, we set all parking data to default in case no parking data is found.
329 SetAxis1Park(HA);
330 SetAxis2Park(DEC);
333 }
334#endif
335
336#if 0
337 // FIXME - Need to implement simulation functionality
338 if (isSimulation())
339 {
340 if (isParked())
342 else
344 }
345#endif
346}
347
348bool PMC8::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
349{
350 // Check guider interface
351 if (GI::processNumber(dev, name, values, names, n))
352 return true;
353
354 if (!strcmp(dev, getDeviceName()))
355 {
356 // Guiding Rate
357 if (!strcmp(name, RampNP.name))
358 {
359 IUUpdateNumber(&RampNP, values, names, n);
360 RampNP.s = IPS_OK;
361 IDSetNumber(&RampNP, nullptr);
362
363 return true;
364 }
365 if (!strcmp(name, LegacyGuideRateNP.name))
366 {
367 IUUpdateNumber(&GuideRateNP, values, names, n);
368
369 if (set_pmc8_guide_rate(PortFD, PMC8_AXIS_RA, LegacyGuideRateN[0].value))
370 LegacyGuideRateNP.s = IPS_OK;
371 else
372 LegacyGuideRateNP.s = IPS_ALERT;
373
374 IDSetNumber(&LegacyGuideRateNP, nullptr);
375
376 return true;
377 }
378 if (!strcmp(name, GuideRateNP.name))
379 {
380 IUUpdateNumber(&GuideRateNP, values, names, n);
381
382 if (set_pmc8_guide_rate(PortFD, PMC8_AXIS_RA, GuideRateN[0].value) &&
383 set_pmc8_guide_rate(PortFD, PMC8_AXIS_DEC, GuideRateN[1].value))
384 GuideRateNP.s = IPS_OK;
385 else
386 GuideRateNP.s = IPS_ALERT;
387
388 IDSetNumber(&GuideRateNP, nullptr);
389
390 return true;
391 }
392 }
393
394 return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
395}
396
397void PMC8::ISGetProperties(const char *dev)
398{
400 defineProperty(&MountTypeSP);
401 defineProperty(&SerialCableTypeSP);
402 loadConfig(true, SerialCableTypeSP.name);
403
404 // set default connection parameters
405 // unfortunately, the only way I've found to set these is after calling ISGetProperties on base class
409
410 // reload config here, even though it was already loaded in call to base class
411 // since defaults may have overridden saved properties
412 loadConfig(false, nullptr);
413}
414
415bool PMC8::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
416{
417 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
418 {
419 if (strcmp(name, MountTypeSP.name) == 0)
420 {
421 IUUpdateSwitch(&MountTypeSP, states, names, n);
422 int currentMountIndex = IUFindOnSwitchIndex(&MountTypeSP);
423 LOGF_INFO("Selected mount is %s", MountTypeS[currentMountIndex].label);
424
425 //right now, this lets the user override the parameters for the detected mount. Perhaps we should prevent the user from doing so?
426 set_pmc8_mountParameters(currentMountIndex);
427 MountTypeSP.s = IPS_OK;
428 IDSetSwitch(&MountTypeSP, nullptr);
429 return true;
430 }
431 if (strcmp(name, SerialCableTypeSP.name) == 0)
432 {
433 IUUpdateSwitch(&SerialCableTypeSP, states, names, n);
434 SerialCableTypeSP.s = IPS_OK;
435 IDSetSwitch(&SerialCableTypeSP, nullptr);
436 return true;
437 }
438 if (strcmp(name, PostGotoSP.name) == 0)
439 {
440 IUUpdateSwitch(&PostGotoSP, states, names, n);
441 // for v2 firmware, if halt after goto is selected, tell driver to use ESPt2
442 set_pmc8_goto_resume(!((IUFindOnSwitchIndex(&PostGotoSP) == 2) && firmwareInfo.IsRev2Compliant));
443 PostGotoSP.s = IPS_OK;
444 IDSetSwitch(&PostGotoSP, nullptr);
445 return true;
446 }
447 }
448
449 return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
450}
451
453{
454 bool rc = false;
455
456 // try to disconnect and reconnect if reconnect flag is set
457
459 {
460 int rc = Disconnect();
461 if (rc) setConnected(false);
462 rc = Connect();
463 if (rc) setConnected(true, IPS_OK);
464 return false;
465 }
466
467 if (isSimulation())
468 mountSim();
469
470 // avoid unnecessary status calls to mount while pulse guiding so we don't lock up the mount for 40+ ms right when it needs to start/stop
471 if (isPulsingNS || isPulsingWE) return true;
472
473 bool slewing = false;
474
475 switch (TrackState)
476 {
477 case SCOPE_SLEWING:
478 // are we done?
479 // check slew state
480 rc = get_pmc8_is_scope_slewing(PortFD, slewing);
481 if (!rc)
482 {
483 LOG_ERROR("PMC8::ReadScopeStatus() - unable to check slew state");
484 }
485 else
486 {
487 if (slewing == false)
488 {
489 if ((IUFindOnSwitchIndex(&PostGotoSP) == 0) ||
490 ((IUFindOnSwitchIndex(&PostGotoSP) == 1) && (RememberTrackState == SCOPE_TRACKING)))
491 {
492 LOG_INFO("Slew complete, tracking...");
495
496 // Don't want to restart tracking after goto with v2 firmware, since mount does automatically
497 // and we might detect that slewing has stopped before it fully settles
498 if (!firmwareInfo.IsRev2Compliant)
499 {
500 if (!SetTrackEnabled(true))
501 {
502 LOG_ERROR("slew complete - unable to enable tracking");
503 return false;
504 }
505 }
506 }
507 else
508 {
509 LOG_INFO("Slew complete.");
511 }
512 }
513 }
514
515 break;
516
517 case SCOPE_PARKING:
518 // are we done?
519 // check slew state
520 rc = get_pmc8_is_scope_slewing(PortFD, slewing);
521 if (!rc)
522 {
523 LOG_ERROR("PMC8::ReadScopeStatus() - unable to check slew state");
524 }
525 else
526 {
527 if (slewing == false)
528 {
530 LOG_DEBUG("Mount tracking is off.");
531
532 SetParked(true);
533
534 saveConfig(true);
535 }
536 }
537 break;
538
539 case SCOPE_IDLE:
540 //periodically check to see if we've entered tracking state (e.g. at startup or from other client)
541 if (!trackingPollCounter--)
542 {
543
544 trackingPollCounter = PMC8_TRACKING_AUTODETECT_INTERVAL;
545
546 // make sure we aren't moving manually to avoid false positives
547 if (moveInfoDEC.state == PMC8_MOVE_INACTIVE && moveInfoRA.state == PMC8_MOVE_INACTIVE)
548 {
549
550 double track_rate;
551 uint8_t track_mode;
552
553 rc = get_pmc8_tracking_data(PortFD, track_rate, track_mode);
554
555 // N.B. PMC8 rates are arcseconds per sidereal second
556 // INDI uses arcseconds per solar second
557 track_rate *= SOLAR_SECOND;
558
559 if (rc && ((int)track_rate > 0) && ((int)track_rate <= PMC8_MAX_TRACK_RATE))
560 {
562 TrackModeSP[convertFromPMC8TrackMode(track_mode)].setState(ISS_ON);
566 LOGF_INFO("Mount has started tracking at %f arcsec / sec", track_rate);
568 TrackRateNP[AXIS_RA].setValue(track_rate);
570 }
571 }
572 }
573 break;
574
575 case SCOPE_TRACKING:
576 //periodically check to see if we've stopped tracking or changed speed (e.g. from other client)
577 if (!trackingPollCounter--)
578 {
579 trackingPollCounter = PMC8_TRACKING_AUTODETECT_INTERVAL;
580
581 // make sure we aren't moving manually to avoid false positives
582 if (moveInfoDEC.state == PMC8_MOVE_INACTIVE && moveInfoRA.state == PMC8_MOVE_INACTIVE)
583 {
584
585 double track_rate;
586 uint8_t track_mode;
587
588 rc = get_pmc8_tracking_data(PortFD, track_rate, track_mode);
589
590 // N.B. PMC8 rates are arcseconds per sidereal second
591 // INDI uses arcseconds per solar second
592 track_rate *= SOLAR_SECOND;
593
594 if (rc && ((int)track_rate == 0))
595 {
596 LOG_INFO("Mount appears to have stopped tracking");
598 }
599 else if (rc && ((int)track_rate <= PMC8_MAX_TRACK_RATE))
600 {
601 if (TrackModeSP[convertFromPMC8TrackMode(track_mode)].getState() != ISS_ON)
602 {
604 TrackModeSP[convertFromPMC8TrackMode(track_mode)].setState(ISS_ON);
606 }
607 if (TrackRateNP[AXIS_RA].getValue() != track_rate)
608 {
611 TrackRateNP[AXIS_RA].setValue(track_rate);
613 LOGF_INFO("Mount now tracking at %f arcsec / sec", track_rate);
614 }
615 }
616 }
617 }
618
619 default:
620 break;
621 }
622
624 rc = get_pmc8_coords(PortFD, currentRA, currentDEC, sop);
625
626 if (rc)
627 {
628 NewRaDec(currentRA, currentDEC);
629 setPierSide(sop);
630 }
631
632 return rc;
633}
634
635bool PMC8::Goto(double r, double d)
636{
637 if (isPulsingNS ||
638 isPulsingWE ||
639 moveInfoDEC.state != PMC8_MOVE_INACTIVE ||
640 moveInfoRA.state != PMC8_MOVE_INACTIVE ||
641 (TrackState == SCOPE_SLEWING && !firmwareInfo.IsRev2Compliant))
642 {
643 LOG_ERROR("Cannot slew while moving or guiding. Please stop moving or guiding first");
644 return false;
645 }
646 else if (TrackState == SCOPE_SLEWING)
647 {
648 targetRA = r;
649 targetDEC = d;
651 //Supposedly the goto should abort in 2s, but we'll give it a little bit more time just in case
653 LOG_INFO("Goto called while already slewing. Stopping slew and will try goto again in 2.5 seconds");
654 return true;
655 }
656
657 // start tracking if we're idle, so mount will track at correct rate post-goto
659 if ((TrackState != SCOPE_TRACKING) && (IUFindOnSwitchIndex(&PostGotoSP) == 0) && firmwareInfo.IsRev2Compliant)
660 {
661 SetTrackEnabled(true);
662 }
663 else if (IUFindOnSwitchIndex(&PostGotoSP) == 2)
664 {
666 }
667
668 char RAStr[64] = {0}, DecStr[64] = {0};
669
670 targetRA = r;
671 targetDEC = d;
672
673 fs_sexa(RAStr, targetRA, 2, 3600);
674 fs_sexa(DecStr, targetDEC, 2, 3600);
675
676 LOGF_DEBUG("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
677
678 if (slew_pmc8(PortFD, r, d) == false)
679 {
680 LOG_ERROR("Failed to slew.");
681 return false;
682 }
683
685
686 return true;
687}
688
689bool PMC8::Sync(double ra, double dec)
690{
691
692 targetRA = ra;
693 targetDEC = dec;
694 char RAStr[64] = {0}, DecStr[64] = {0};
695
696 fs_sexa(RAStr, targetRA, 2, 3600);
697 fs_sexa(DecStr, targetDEC, 2, 3600);
698
699 LOGF_DEBUG("Syncing to RA: %s - DEC: %s", RAStr, DecStr);
700
701 if (sync_pmc8(PortFD, ra, dec) == false)
702 {
703 LOG_ERROR("Failed to sync.");
704 }
705
707
708 currentRA = ra;
709 currentDEC = dec;
710
711 NewRaDec(currentRA, currentDEC);
712
713 return true;
714}
715
717{
718 //static_cast<PMC8*>(p)->TrackState = static_cast<PMC8*>(p)->RememberTrackState;
719 static_cast<PMC8*>(p)->Goto(static_cast<PMC8*>(p)->targetRA, static_cast<PMC8*>(p)->targetDEC);
720}
721
723{
724 //GUIDE Abort guide operations.
726 {
729 GuideNSNP[0].setValue(0);
730 GuideNSNP[1].setValue(0);
731 GuideWENP[0].setValue(0);
732 GuideWENP[1].setValue(0);
733
734 if (GuideNSTID)
735 {
737 GuideNSTID = 0;
738 }
739
740 if (GuideWETID)
741 {
743 GuideNSTID = 0;
744 }
745
746 LOG_INFO("Guide aborted.");
749 return true;
750 }
751
752
753 //GOTO Abort slew operations.
755 {
757 //It will take about 2s to abort; we'll rely on ReadScopeStatus to detect when that occurs
758 LOG_INFO("Goto aborted.");
759 return true;
760 }
761
762 //MOVE Abort move operations.
763 if ((moveInfoDEC.state == PMC8_MOVE_ACTIVE) || (moveInfoRA.state == PMC8_MOVE_ACTIVE))
764 {
765 if (moveInfoDEC.state == PMC8_MOVE_ACTIVE)
766 {
767 MoveNS((INDI_DIR_NS)moveInfoDEC.moveDir, MOTION_STOP);
768 }
769 if (moveInfoRA.state == PMC8_MOVE_ACTIVE)
770 {
772 }
773 LOG_INFO("Move aborted.");
774 return true;
775 }
776
777 LOG_INFO("Abort called--stopping all motion.");
778 if (abort_pmc8(PortFD))
779 {
781 return true;
782 }
783 else return false;
784}
785
787{
788#if 0
789 // FIXME - Currently only support parking at motor position (0, 0)
790 targetRA = GetAxis1Park();
791 targetDEC = GetAxis2Park();
792 if (set_pmc8_radec(PortFD, r, d) == false)
793 {
794 LOG_ERROR("Error setting RA/DEC.");
795 return false;
796 }
797#endif
798
799 //if we're already parking, no need to do anything
801 {
802 return true;
803 }
804
805 if (park_pmc8(PortFD))
806 {
808 LOG_INFO("Telescope parking in progress to motor position (0, 0)");
809 return true;
810 }
811 else
812 {
813 return false;
814 }
815}
816
818{
819 if (unpark_pmc8(PortFD))
820 {
821 SetParked(false);
823 return true;
824 }
825 else
826 {
827 return false;
828 }
829}
830
832{
833 if (isSimulation())
834 {
837 set_pmc8_sim_move_rate(64 * 15);
838 // set_pmc8_sim_hemisphere(HEMI_NORTH);
839 }
840
843 {
844 if (IUFindOnSwitchIndex(&SerialCableTypeSP) == 1) conn = PMC8_SERIAL_INVERTED;
845 if (IUFindOnSwitchIndex(&SerialCableTypeSP) == 2) conn = PMC8_SERIAL_STANDARD;
846 }
847 else
848 {
849 conn = PMC8_ETHERNET;
850 }
851
852 return check_pmc8_connection(PortFD, conn);
853}
854
855bool PMC8::updateTime(ln_date *utc, double utc_offset)
856{
857 // mark unused
858 INDI_UNUSED(utc);
859 INDI_UNUSED(utc_offset);
860
861 LOG_ERROR("PMC8::updateTime() not implemented!");
862 return false;
863
864}
865
866bool PMC8::updateLocation(double latitude, double longitude, double elevation)
867{
868 INDI_UNUSED(elevation);
869
870 if (longitude > 180)
871 longitude -= 360;
872
873 // Southern Hemisphere support
874 // As of 2024, southern hemisphere is now supported with proper coordinate
875 // transformations and motor direction handling via pmc8_east_dir variable.
876 // The low-level driver (pmc8driver.cpp) handles the coordinate math.
877 if (latitude < 0)
878 {
879 LOG_INFO("Southern Hemisphere detected - using inverted coordinate system.");
880 }
881
882 // must also keep "low level" aware of position to convert motor counts to RA/DEC
883 set_pmc8_location(latitude, longitude);
884
885 char l[32] = {0}, L[32] = {0};
886 fs_sexa(l, latitude, 3, 3600);
887 fs_sexa(L, longitude, 4, 3600);
888
889 LOGF_INFO("Site location updated to Lat %.32s - Long %.32s", l, L);
890
891 return true;
892}
893
894void PMC8::debugTriggered(bool enable)
895{
896 set_pmc8_debug(enable);
897}
898
900{
901 set_pmc8_simulation(enable);
902}
903
905{
906 int mode = SlewRateSP.findOnSwitchIndex();
907 if (mode >= 8) return PMC8_MAX_MOVE_RATE;
908 return 4 * pow(2, mode) * 15;
909}
910
911
913{
914
915 PMC8MoveInfo *moveInfo = ((dir == PMC8_N) | (dir == PMC8_S)) ? &moveInfoDEC : &moveInfoRA;
916
917 if (moveInfo->state != PMC8_MOVE_RAMPING)
918 {
919 return false; //shouldn't be here
920 LOG_ERROR("Ramp function called while not in ramp state");
921 }
922
923 int newrate = moveInfo->rampLastStep;
924
925 if (moveInfo->rampDir == PMC8_RAMP_UP)
926 {
927 newrate += RampN[1].value * pow(RampN[2].value, moveInfo->rampIteration++) * 15;
928 }
929 else
930 {
931 newrate -= RampN[1].value * pow(RampN[2].value, --moveInfo->rampIteration) * 15;
932 }
933
934 int adjrate = newrate;
935
936 //check to see if we're done
937 if (newrate >= moveInfo->targetRate)
938 {
939 adjrate = moveInfo->targetRate;
940 moveInfo->state = PMC8_MOVE_ACTIVE;
941 }
942 else if (newrate <= 0)
943 {
944 adjrate = 0;
945 moveInfo->state = PMC8_MOVE_INACTIVE;
946 //restore tracking if we're at 0
947 if ((dir == PMC8_E) || (dir == PMC8_W))
948 {
950 {
951 if (!SetTrackEnabled(true))
952 {
953 LOG_ERROR("slew complete - unable to enable tracking");
954 return false;
955 }
956 }
957
958 return true;
959 }
960 }
961
962 //adjust for current tracking rate
963 if (dir == PMC8_E) adjrate += round(TrackRateNP[AXIS_RA].getValue());
964 else if (dir == PMC8_W) adjrate -= round(TrackRateNP[AXIS_RA].getValue());
965
966 // Solar second to Sideral second conversion
967 adjrate /= SOLAR_SECOND;
968
969 LOGF_EXTRA3("Ramping: mount dir %d, ramping dir %d, iteration %d, step to %d", dir, moveInfo->rampDir,
970 moveInfo->rampIteration, adjrate);
971
972 if (!set_pmc8_move_rate_axis(PortFD, dir, adjrate))
973 {
974 LOGF_ERROR("Error ramping move rate: mount dir %d, ramping dir %d, iteration %d, step to %d", dir, moveInfo->rampDir,
975 moveInfo->rampIteration, adjrate);
976 moveInfo->state = PMC8_MOVE_INACTIVE;
977 return false;
978 }
979
980 moveInfo->rampLastStep = newrate;
981
982 return true;
983}
984
985//MOVE The timer helper functions.
987{
988 PMC8* pmc8 = static_cast<PMC8*>(p);
989 if (pmc8->ramp_movement(PMC8_N) && (pmc8->moveInfoDEC.state == PMC8_MOVE_RAMPING))
990 pmc8->moveInfoDEC.timer = IEAddTimer(pmc8->RampN[0].value, rampTimeoutHelperN, p);
991}
993{
994 PMC8* pmc8 = static_cast<PMC8*>(p);
995 if (pmc8->ramp_movement(PMC8_S) && (pmc8->moveInfoDEC.state == PMC8_MOVE_RAMPING))
996 pmc8->moveInfoDEC.timer = IEAddTimer(pmc8->RampN[0].value, rampTimeoutHelperS, p);
997}
999{
1000 PMC8* pmc8 = static_cast<PMC8*>(p);
1001 if (pmc8->ramp_movement(PMC8_W) && (pmc8->moveInfoRA.state == PMC8_MOVE_RAMPING))
1002 pmc8->moveInfoRA.timer = IEAddTimer(pmc8->RampN[0].value, rampTimeoutHelperW, p);
1003}
1005{
1006 PMC8* pmc8 = static_cast<PMC8*>(p);
1007 if (pmc8->ramp_movement(PMC8_E) && (pmc8->moveInfoRA.state == PMC8_MOVE_RAMPING))
1008 pmc8->moveInfoRA.timer = IEAddTimer(pmc8->RampN[0].value, rampTimeoutHelperE, p);
1009}
1010
1011
1013{
1014 if (TrackState == SCOPE_PARKED)
1015 {
1016 LOG_ERROR("Please unpark the mount before issuing any motion commands.");
1017 return false;
1018 }
1020 {
1021 LOG_ERROR("Mount is slewing. Wait to issue move command until goto completes.");
1022 return false;
1023 }
1024 if ((moveInfoDEC.state == PMC8_MOVE_ACTIVE) && (moveInfoDEC.moveDir != dir))
1025 {
1026 LOG_ERROR("Mount received command to move in opposite direction before stopping. This shouldn't happen.");
1027 return false;
1028 }
1029
1030 // read desired move rate
1031 int currentIndex = SlewRateSP.findOnSwitchIndex();
1032 LOGF_DEBUG("MoveNS at slew index %d", currentIndex);
1033
1034 switch (command)
1035 {
1036 case MOTION_START:
1037 moveInfoDEC.rampDir = PMC8_RAMP_UP;
1038 moveInfoDEC.targetRate = getSlewRate();
1039 // if we're still ramping down, we can bypass resetting the state and adding a timer
1040 // but we do need to make sure it's the same direction first (if not, kill our previous timer)
1041 if (moveInfoDEC.state == PMC8_MOVE_RAMPING)
1042 {
1043 if (moveInfoDEC.moveDir == dir) return true;
1044 IERmTimer(moveInfoDEC.timer);
1045 LOG_WARN("Started moving other direction before ramp down completed. This *may* cause mechanical problems with mount. It is adviseable to wait for axis movement to settle before switching directions.");
1046 }
1047 moveInfoDEC.moveDir = dir;
1048 moveInfoDEC.state = PMC8_MOVE_RAMPING;
1049 moveInfoDEC.rampIteration = 0;
1050 moveInfoDEC.rampLastStep = 0;
1051
1052 LOGF_INFO("Moving toward %s.", (dir == DIRECTION_NORTH) ? "North" : "South");
1053
1054 break;
1055
1056 case MOTION_STOP:
1057 // if we've already started moving other direction, no need to stop
1058 if (moveInfoDEC.moveDir != dir)
1059 {
1060 LOGF_DEBUG("Stop command issued for direction %d, but we're not moving that way", dir);
1061 return false;
1062 }
1063
1064 moveInfoDEC.rampDir = PMC8_RAMP_DOWN;
1065 // if we're still ramping up, we can bypass adding a timer
1066 if (moveInfoDEC.state == PMC8_MOVE_RAMPING) return true;
1067 moveInfoDEC.state = PMC8_MOVE_RAMPING;
1068
1069 LOGF_INFO("%s motion stopping.", (dir == DIRECTION_NORTH) ? "North" : "South");
1070
1071 break;
1072 }
1073
1074 if (dir == DIRECTION_NORTH)
1075 rampTimeoutHelperN(this);
1076 else
1077 rampTimeoutHelperS(this);
1078
1079 return true;
1080}
1081
1082
1084{
1085 if (TrackState == SCOPE_PARKED)
1086 {
1087 LOG_ERROR("Please unpark the mount before issuing any motion commands.");
1088 return false;
1089 }
1091 {
1092 LOG_ERROR("Mount is already slewing. Wait to issue move command until done slewing.");
1093 return false;
1094 }
1095 if ((moveInfoRA.state == PMC8_MOVE_ACTIVE) && (moveInfoRA.moveDir != dir))
1096 {
1097 LOG_ERROR("Mount received command to move in opposite direction before stopping. This shouldn't happen.");
1098 return false;
1099 }
1100
1101 // read desired move rate
1102 int currentIndex = SlewRateSP.findOnSwitchIndex();
1103 LOGF_DEBUG("MoveWE at slew index %d", currentIndex);
1104
1105 switch (command)
1106 {
1107 case MOTION_START:
1108 moveInfoRA.rampDir = PMC8_RAMP_UP;
1109 moveInfoRA.targetRate = getSlewRate();
1110 // if we're still ramping down, we can bypass resetting the state and adding a timer
1111 // but we do need to make sure it's the same direction first (if not, kill our previous timer)
1112 if (moveInfoRA.state == PMC8_MOVE_RAMPING)
1113 {
1114 if (moveInfoRA.moveDir == dir) return true;
1115 IERmTimer(moveInfoRA.timer);
1116 LOG_WARN("Started moving other direction before ramp down completed. This *may* cause mechanical problems with mount. It is adviseable to wait for axis movement to settle before switching directions.");
1117 }
1118 moveInfoRA.moveDir = dir;
1119 moveInfoRA.state = PMC8_MOVE_RAMPING;
1120 moveInfoRA.rampIteration = 0;
1121 moveInfoRA.rampLastStep = 0;
1122
1123 LOGF_INFO("Moving toward %s.", (dir == DIRECTION_WEST) ? "West" : "East");
1124
1125 break;
1126
1127 case MOTION_STOP:
1128 // if we've already started moving other direction, no need to stop
1129 if (moveInfoRA.moveDir != dir)
1130 {
1131 LOGF_DEBUG("Stop command issued for direction %d, but we're not moving that way", dir);
1132 return false;
1133 }
1134
1135 moveInfoRA.rampDir = PMC8_RAMP_DOWN;
1136 // if we're still ramping up, we can bypass adding a timer
1137 if (moveInfoRA.state == PMC8_MOVE_RAMPING) return true;
1138 moveInfoRA.state = PMC8_MOVE_RAMPING;
1139
1140 LOGF_INFO("%s motion stopping.", (dir == DIRECTION_WEST) ? "West" : "East");
1141
1142 break;
1143 }
1144
1145 if (dir == DIRECTION_EAST)
1146 rampTimeoutHelperE(this);
1147 else
1148 rampTimeoutHelperW(this);
1149
1150 return true;
1151}
1152
1154{
1155 IPState ret = IPS_IDLE;
1156 long timetaken_us = 0;
1157 int timeremain_ms = 0;
1158
1159 //only guide if tracking
1161 {
1162
1163 // If already moving, then stop movement
1165 {
1166 int dir = MovementNSSP.findOnSwitchIndex();
1168 }
1169
1170 if (GuideNSTID)
1171 {
1173 GuideNSTID = 0;
1174 }
1175
1176 isPulsingNS = true;
1177 start_pmc8_guide(PortFD, PMC8_N, (int)ms, timetaken_us, 0);
1178
1179 timeremain_ms = (int)(ms - ((float)timetaken_us) / 1000.0);
1180
1181 if (timeremain_ms < 0)
1182 timeremain_ms = 0;
1183
1184 ret = IPS_BUSY;
1185 }
1186 else
1187 {
1188 LOG_INFO("Mount not tracking--cannot guide.");
1189 }
1190 GuideNSTID = IEAddTimer(timeremain_ms, guideTimeoutHelperN, this);
1191 return ret;
1192}
1193
1195{
1196 IPState ret = IPS_IDLE;
1197 long timetaken_us = 0;
1198 int timeremain_ms = 0;
1199
1200 //only guide if tracking
1202 {
1203
1204 // If already moving, then stop movement
1206 {
1207 int dir = MovementNSSP.findOnSwitchIndex();
1209 }
1210
1211 if (GuideNSTID)
1212 {
1214 GuideNSTID = 0;
1215 }
1216
1217 isPulsingNS = true;
1218 start_pmc8_guide(PortFD, PMC8_S, (int)ms, timetaken_us, 0);
1219
1220 timeremain_ms = (int)(ms - ((float)timetaken_us) / 1000.0);
1221
1222 if (timeremain_ms < 0)
1223 timeremain_ms = 0;
1224
1225 ret = IPS_BUSY;
1226 }
1227 else
1228 {
1229 LOG_INFO("Mount not tracking--cannot guide.");
1230 }
1231 GuideNSTID = IEAddTimer(timeremain_ms, guideTimeoutHelperS, this);
1232 return ret;
1233}
1234
1236{
1237 IPState ret = IPS_IDLE;
1238 long timetaken_us = 0;
1239 int timeremain_ms = 0;
1240
1241 //only guide if tracking
1243 {
1244
1245 // If already moving (no pulse command), then stop movement
1247 {
1248 int dir = MovementWESP.findOnSwitchIndex();
1250 }
1251
1252 if (GuideWETID)
1253 {
1255 GuideWETID = 0;
1256 }
1257
1258 isPulsingWE = true;
1259
1260 start_pmc8_guide(PortFD, PMC8_E, (int)ms, timetaken_us, TrackRateNP[AXIS_RA].getValue() / SOLAR_SECOND);
1261
1262 timeremain_ms = (int)(ms - ((float)timetaken_us) / 1000.0);
1263
1264 if (timeremain_ms < 0)
1265 timeremain_ms = 0;
1266
1267 ret = IPS_BUSY;
1268 }
1269 else
1270 {
1271 LOG_INFO("Mount not tracking--cannot guide.");
1272 }
1273 GuideWETID = IEAddTimer(timeremain_ms, guideTimeoutHelperE, this);
1274 return ret;
1275}
1276
1278{
1279 IPState ret = IPS_IDLE;
1280 long timetaken_us = 0;
1281 int timeremain_ms = 0;
1282
1283 //only guide if tracking
1285 {
1286
1287 // If already moving (no pulse command), then stop movement
1289 {
1290 int dir = MovementWESP.findOnSwitchIndex();
1292 }
1293
1294 if (GuideWETID)
1295 {
1297 GuideWETID = 0;
1298 }
1299
1300 isPulsingWE = true;
1301 start_pmc8_guide(PortFD, PMC8_W, (int)ms, timetaken_us, TrackRateNP[AXIS_RA].getValue() / SOLAR_SECOND);
1302
1303 timeremain_ms = (int)(ms - ((float)timetaken_us) / 1000.0);
1304
1305 if (timeremain_ms < 0)
1306 timeremain_ms = 0;
1307
1308 ret = IPS_BUSY;
1309 }
1310 else
1311 {
1312 LOG_INFO("Mount not tracking--cannot guide.");
1313 }
1314 GuideWETID = IEAddTimer(timeremain_ms, guideTimeoutHelperW, this);
1315 return ret;
1316}
1317
1319{
1320 // end previous pulse command
1321 stop_pmc8_guide(PortFD, calldir);
1322
1323 if (calldir == PMC8_N || calldir == PMC8_S)
1324 {
1325 isPulsingNS = false;
1326 GuideNSNP[0].setValue(0);
1327 GuideNSNP[1].setValue(0);
1329 GuideNSTID = 0;
1330 GuideNSNP.apply();
1331 }
1332 if (calldir == PMC8_W || calldir == PMC8_E)
1333 {
1334 isPulsingWE = false;
1335 GuideWENP[0].setValue(0);
1336 GuideWENP[1].setValue(0);
1338 GuideWETID = 0;
1339 GuideWENP.apply();
1340 }
1341
1342 LOG_DEBUG("GUIDE CMD COMPLETED");
1343}
1344
1345//GUIDE The timer helper functions.
1347{
1348 static_cast<PMC8*>(p)->guideTimeout(PMC8_N);
1349}
1351{
1352 static_cast<PMC8*>(p)->guideTimeout(PMC8_S);
1353}
1355{
1356 static_cast<PMC8*>(p)->guideTimeout(PMC8_W);
1357}
1359{
1360 static_cast<PMC8*>(p)->guideTimeout(PMC8_E);
1361}
1362
1363bool PMC8::SetSlewRate(int index)
1364{
1365
1366 INDI_UNUSED(index);
1367
1368 // slew rate is rate for MoveEW/MOVENE commands - not for GOTOs!!!
1369
1370 // just return true - we will check SlewRateSP when we do actually moves
1371 return true;
1372}
1373
1375{
1377
1378 IUSaveConfigSwitch(fp, &SerialCableTypeSP);
1379 IUSaveConfigSwitch(fp, &MountTypeSP);
1380 IUSaveConfigNumber(fp, &RampNP);
1381 IUSaveConfigNumber(fp, &LegacyGuideRateNP);
1382 IUSaveConfigSwitch(fp, &PostGotoSP);
1383
1384 return true;
1385}
1386
1388{
1389 static struct timeval ltv;
1390 struct timeval tv;
1391 double dt, da, dx;
1392 int nlocked;
1393
1394 /* update elapsed time since last poll, don't presume exactly POLLMS */
1395 gettimeofday(&tv, nullptr);
1396
1397 if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1398 ltv = tv;
1399
1400 dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1401 ltv = tv;
1402 da = SLEWRATE * dt;
1403
1404 /* Process per current state. We check the state of EQUATORIAL_COORDS and act accordingly */
1405 switch (TrackState)
1406 {
1407 case SCOPE_IDLE:
1408 currentRA += (TrackRateNP[AXIS_RA].getValue() / 3600.0 * dt) / 15.0;
1410 break;
1411
1412 case SCOPE_TRACKING:
1413 if (TrackModeSP[1].getState() == ISS_ON)
1414 {
1415 currentRA += ( ((TRACKRATE_SIDEREAL / 3600.0) - (TrackRateNP[AXIS_RA].getValue() / 3600.0)) * dt) / 15.0;
1416 currentDEC += ( (TrackRateNP[AXIS_DE].getValue() / 3600.0) * dt);
1417 }
1418 break;
1419
1420 case SCOPE_SLEWING:
1421 case SCOPE_PARKING:
1422 /* slewing - nail it when both within one pulse @ SLEWRATE */
1423 nlocked = 0;
1424
1425 dx = targetRA - currentRA;
1426
1427 // Take shortest path
1428 if (fabs(dx) > 12)
1429 dx *= -1;
1430
1431 if (fabs(dx) <= da)
1432 {
1433 currentRA = targetRA;
1434 nlocked++;
1435 }
1436 else if (dx > 0)
1437 currentRA += da / 15.;
1438 else
1439 currentRA -= da / 15.;
1440
1441 if (currentRA < 0)
1442 currentRA += 24;
1443 else if (currentRA > 24)
1444 currentRA -= 24;
1445
1446 dx = targetDEC - currentDEC;
1447 if (fabs(dx) <= da)
1448 {
1449 currentDEC = targetDEC;
1450 nlocked++;
1451 }
1452 else if (dx > 0)
1453 currentDEC += da;
1454 else
1455 currentDEC -= da;
1456
1457 if (nlocked == 2)
1458 {
1461 else
1463 }
1464
1465 break;
1466
1467 case SCOPE_PARKED:
1468 TelescopePierSide unused;
1469 // setting system status to parked will automatically
1470 // set the simulated RA/DEC to park position so reread
1473
1474 break;
1475
1476 default:
1477 break;
1478 }
1479
1482}
1483
1484// PMC8 parks to motor position (0, 0) which corresponds to HA=6h, DEC=+/-90
1485// Southern Hemisphere support: Use get_pmc8_east_dir() to determine proper DEC park position
1487{
1490
1491 return true;
1492}
1493
1495{
1496 // By default set RA to HA (Hour Angle)
1497 SetAxis1Park(ln_get_apparent_sidereal_time(ln_get_julian_from_sys()));
1498
1499 // Set DEC to 90 or -90 depending on the hemisphere
1500 // get_pmc8_east_dir() returns 1 for northern hemisphere, 0 for southern
1501 // In northern hemisphere, park pointing at north celestial pole (DEC +90)
1502 // In southern hemisphere, park pointing at south celestial pole (DEC -90)
1503 SetAxis2Park(get_pmc8_east_dir() ? 90.0 : -90.0);
1504
1505 return true;
1506}
1507
1508uint8_t PMC8::convertToPMC8TrackMode(uint8_t mode)
1509{
1510 switch (mode)
1511 {
1512 case TRACK_SIDEREAL:
1513 return PMC8_TRACK_SIDEREAL;
1514 break;
1515 case TRACK_LUNAR:
1516 return PMC8_TRACK_LUNAR;
1517 break;
1518 case TRACK_SOLAR:
1519 return PMC8_TRACK_SOLAR;
1520 break;
1521 case TRACK_CUSTOM:
1522 return PMC8_TRACK_CUSTOM;
1523 break;
1524 default:
1525 return PMC8_TRACK_UNDEFINED;
1526 }
1527}
1528
1529uint8_t PMC8::convertFromPMC8TrackMode(uint8_t mode)
1530{
1531 switch (mode)
1532 {
1534 return TRACK_SIDEREAL;
1535 break;
1536 case PMC8_TRACK_LUNAR:
1537 return TRACK_LUNAR;
1538 break;
1539 case PMC8_TRACK_SOLAR:
1540 return TRACK_SOLAR;
1541 break;
1542 default:
1543 return TRACK_CUSTOM;
1544 }
1545}
1546
1547bool PMC8::SetTrackMode(uint8_t mode)
1548{
1549 uint8_t pmc8_mode;
1550
1551 LOGF_DEBUG("PMC8::SetTrackMode called mode=%d", mode);
1552
1553 pmc8_mode = convertToPMC8TrackMode(mode);
1554
1555 if (pmc8_mode == PMC8_TRACK_UNDEFINED)
1556 {
1557 LOGF_ERROR("PMC8::SetTrackMode mode=%d not supported!", mode);
1558 return false;
1559 }
1560
1561 if (pmc8_mode == PMC8_TRACK_CUSTOM)
1562 {
1564 {
1565 return true;
1566 }
1567 }
1568 else
1569 {
1570 if (set_pmc8_track_mode(PortFD, pmc8_mode))
1571 return true;
1572 }
1573
1574 return false;
1575}
1576
1577bool PMC8::SetTrackRate(double raRate, double deRate)
1578{
1579 static bool deRateWarning = true;
1580 double pmc8RARate;
1581
1582 LOGF_INFO("Custom tracking rate set: raRate=%f deRate=%f", raRate, deRate);
1583
1584 // for now just send rate
1585 pmc8RARate = raRate / SOLAR_SECOND;
1586
1587 if (deRate != 0 && deRateWarning)
1588 {
1589 // Only send warning once per session
1590 deRateWarning = false;
1591 LOG_WARN("Custom Declination tracking rate is not implemented yet.");
1592 }
1593
1594 if (set_pmc8_ra_tracking(PortFD, pmc8RARate))
1595 return true;
1596
1597 LOG_ERROR("PMC8::SetTrackRate not implemented!");
1598 return false;
1599}
1600
1601bool PMC8::SetTrackEnabled(bool enabled)
1602{
1603
1604 LOGF_DEBUG("PMC8::SetTrackEnabled called enabled=%d", enabled);
1605
1606 // need to determine current tracking mode and start tracking
1607 if (enabled)
1608 {
1610 {
1611 LOG_ERROR("PMC8::SetTrackEnabled - unable to enable tracking");
1612 return false;
1613 }
1614 }
1615 else
1616 {
1617 bool rc;
1618
1620 if (!rc)
1621 {
1622 LOG_ERROR("PMC8::SetTrackEnabled - unable to set RA track rate to 0");
1623 return false;
1624 }
1625
1626 // currently only support tracking rate in RA
1627 // rc=set_pmc8_custom_dec_track_rate(PortFD, 0);
1628 // if (!rc)
1629 // {
1630 // LOG_ERROR("PMC8::SetTrackREnabled - unable to set DEC track rate to 0");
1631 // return false;
1632 // }
1633 }
1634
1635 return true;
1636}
1637
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
bool isConnected() const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
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 setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
uint32_t getDriverInterface() const
Connection::Interface * getActiveConnection()
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
INDI::PropertyNumber GuideNSNP
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
INDI::PropertyNumber GuideWENP
void initProperties(const char *groupName)
Initialize guider properties. It is recommended to call this function within initProperties() of your...
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void setLabel(const char *label)
TelescopeStatus TrackState
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
double GetAxis1Park() const
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...
double GetAxis2Park() const
bool isParked()
isParked is mount currently parked?
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
Connection::Serial * serialConnection
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
Connection::TCP * tcpConnection
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
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...
TelescopeStatus RememberTrackState
RememberTrackState Remember last state of Track State to fall back to in case of errors or aborts.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
Definition pmc8.h:52
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition pmc8.cpp:348
static void guideTimeoutHelperE(void *p)
Definition pmc8.cpp:1358
virtual void simulationTriggered(bool enable) override
Inform driver that the simulation option was triggered. This function is called after setSimulation i...
Definition pmc8.cpp:899
virtual bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
Definition pmc8.cpp:1363
bool ramp_movement(PMC8_DIRECTION calldir)
Definition pmc8.cpp:912
virtual bool Handshake() override
perform handshake with device to check communication
Definition pmc8.cpp:831
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
Definition pmc8.cpp:1494
PMC8()
Definition pmc8.cpp:59
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
Definition pmc8.cpp:1374
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
Definition pmc8.cpp:1012
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
Definition pmc8.cpp:171
static void rampTimeoutHelperN(void *p)
Definition pmc8.cpp:986
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
Definition pmc8.cpp:689
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...
Definition pmc8.cpp:397
~PMC8() override
Definition pmc8.cpp:77
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
Definition pmc8.cpp:855
static void AbortGotoTimeoutHelper(void *p)
Definition pmc8.cpp:716
virtual const char * getDefaultName() override
Definition pmc8.cpp:81
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
Definition pmc8.cpp:1601
static void rampTimeoutHelperS(void *p)
Definition pmc8.cpp:992
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
Definition pmc8.cpp:1153
virtual bool ReadScopeStatus() override
Read telescope status.
Definition pmc8.cpp:452
int GuideNSTID
Definition pmc8.h:127
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
Definition pmc8.cpp:1083
static void rampTimeoutHelperE(void *p)
Definition pmc8.cpp:1004
virtual bool Park() override
Park the telescope to its home position.
Definition pmc8.cpp:786
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
Definition pmc8.cpp:1486
virtual void debugTriggered(bool enable) override
Inform driver that the debug option was triggered. This function is called after setDebug is triggere...
Definition pmc8.cpp:894
virtual bool SetTrackRate(double raRate, double deRate) override
SetTrackRate Set custom tracking rates.
Definition pmc8.cpp:1577
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
Definition pmc8.cpp:1277
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
Definition pmc8.cpp:866
void mountSim()
Definition pmc8.cpp:1387
static void guideTimeoutHelperS(void *p)
Definition pmc8.cpp:1350
static void guideTimeoutHelperW(void *p)
Definition pmc8.cpp:1354
virtual bool initProperties() override
Called to initialize basic properties required all the time.
Definition pmc8.cpp:86
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition pmc8.cpp:415
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
Definition pmc8.cpp:1235
static void guideTimeoutHelperN(void *p)
Definition pmc8.cpp:1346
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
Definition pmc8.cpp:722
void guideTimeout(PMC8_DIRECTION calldir)
Definition pmc8.cpp:1318
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
Definition pmc8.cpp:635
virtual bool UnPark() override
Unpark the telescope if already parked.
Definition pmc8.cpp:817
static void rampTimeoutHelperW(void *p)
Definition pmc8.cpp:998
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
Definition pmc8.cpp:1194
int GuideWETID
Definition pmc8.h:128
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
Definition pmc8.cpp:1547
int getSlewRate()
Definition pmc8.cpp:904
const char * GUIDE_TAB
GUIDE_TAB Where all the properties for guiding are located.
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 * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition eventloop.c:606
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition eventloop.c:586
INDI::PropertyNumber TrackRateNP
INDI::PropertySwitch MovementNSSP
INDI::PropertyNumber EqNP
INDI::PropertySwitch TrackStateSP
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch TrackModeSP
INDI::PropertySwitch MovementWESP
INDI::PropertySwitch ParkOptionSP
INDI::PropertyNumber LocationNP
INDI::PropertyNumber ParkPositionNP
#define currentDEC
Definition ieq45.cpp:48
#define currentRA
Definition ieq45.cpp:47
#define SLEWRATE
Definition ieqpro.cpp:35
double ra
void set_sim_system_status(IEQ_SYSTEM_STATUS value)
double dec
@ ST_STOPPED
@ ST_PARKED
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
@ AXIS_DE
@ AXIS_RA
INDI_DIR_WE
@ DIRECTION_EAST
@ DIRECTION_WEST
INDI_DIR_NS
@ DIRECTION_SOUTH
@ DIRECTION_NORTH
double range24(double r)
range24 Limits a number to be between 0-24 range.
Definition indicom.c:1311
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.
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 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 IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition indidevapi.c:23
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 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 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,...)
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define LOGF_EXTRA3(fmt,...)
Definition indilogger.h:86
#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 TRACKRATE_SIDEREAL
Definition indimacros.h:39
#define getDeviceName
#define PMC8_TRACKING_AUTODETECT_INTERVAL
Definition pmc8.cpp:52
#define PMC8_DEFAULT_IP_ADDRESS
Definition pmc8.cpp:51
#define PMC8_VERSION_MINOR
Definition pmc8.cpp:54
#define PMC8_DEFAULT_PORT
Definition pmc8.cpp:50
#define PMC8_VERSION_MAJOR
Definition pmc8.cpp:53
@ PMC8_MOVE_INACTIVE
Definition pmc8.h:33
@ PMC8_MOVE_RAMPING
Definition pmc8.h:33
@ PMC8_MOVE_ACTIVE
Definition pmc8.h:33
#define SOLAR_SECOND
Definition pmc8.h:37
@ PMC8_RAMP_UP
Definition pmc8.h:34
@ PMC8_RAMP_DOWN
Definition pmc8.h:34
bool get_pmc8_guide_rate(int fd, PMC8_AXIS axis, double &rate)
void set_pmc8_location(double latitude, double longitude)
bool set_pmc8_custom_ra_track_rate(int fd, double rate)
bool start_pmc8_guide(int fd, PMC8_DIRECTION gdir, int ms, long &timetaken_us, double ratehint)
void set_pmc8_sim_system_status(PMC8_SYSTEM_STATUS value)
void set_pmc8_device(const char *name)
bool park_pmc8(int fd)
bool stop_pmc8_guide(int fd, PMC8_DIRECTION gdir)
void set_pmc8_debug(bool enable)
bool sync_pmc8(int fd, double ra, double dec)
bool abort_pmc8_goto(int fd)
bool slew_pmc8(int fd, double ra, double dec)
bool get_pmc8_is_scope_slewing(int fd, bool &isslew)
bool set_pmc8_track_mode(int fd, uint8_t mode)
bool set_pmc8_radec(int fd, double ra, double dec)
void set_pmc8_mountParameters(int index)
bool set_pmc8_guide_rate(int fd, PMC8_AXIS axis, double rate)
void set_pmc8_simulation(bool enable)
bool get_pmc8_coords(int fd, double &ra, double &dec, INDI::Telescope::TelescopePierSide &sop)
void set_pmc8_goto_resume(bool resume)
bool get_pmc8_firmware(int fd, FirmwareInfo *info)
bool abort_pmc8(int fd)
bool get_pmc8_reconnect_flag()
bool get_pmc8_tracking_data(int fd, double &rate, uint8_t &mode)
bool check_pmc8_connection(int fd, PMC8_CONNECTION_TYPE connection)
bool unpark_pmc8(int fd)
int get_pmc8_east_dir()
void set_pmc8_sim_move_rate(int value)
bool stop_pmc8_tracking_motion(int fd)
void set_pmc8_sim_track_rate(PMC8_TRACK_RATE value)
void set_pmc8_sim_ra(double ra)
bool set_pmc8_ra_tracking(int fd, double rate)
void set_pmc8_sim_dec(double dec)
bool set_pmc8_move_rate_axis(int fd, PMC8_DIRECTION dir, int reqrate)
PMC8_CONNECTION_TYPE
Definition pmc8driver.h:68
@ PMC8_SERIAL_AUTO
Definition pmc8driver.h:68
@ PMC8_ETHERNET
Definition pmc8driver.h:68
@ PMC8_SERIAL_STANDARD
Definition pmc8driver.h:68
@ PMC8_SERIAL_INVERTED
Definition pmc8driver.h:68
#define PMC8_MAX_MOVE_RATE
Definition pmc8driver.h:41
@ MOUNT_G11
Definition pmc8driver.h:66
@ MOUNT_iEXOS100
Definition pmc8driver.h:66
@ MOUNT_EXOS2
Definition pmc8driver.h:66
@ PMC8_TRACK_CUSTOM
Definition pmc8driver.h:59
@ PMC8_TRACK_SIDEREAL
Definition pmc8driver.h:59
@ PMC8_TRACK_LUNAR
Definition pmc8driver.h:59
@ PMC8_TRACK_SOLAR
Definition pmc8driver.h:59
@ PMC8_TRACK_UNDEFINED
Definition pmc8driver.h:59
@ ST_TRACKING
Definition pmc8driver.h:49
@ PMC8_AXIS_DEC
Definition pmc8driver.h:63
@ PMC8_AXIS_RA
Definition pmc8driver.h:63
PMC8_DIRECTION
Definition pmc8driver.h:64
@ PMC8_N
Definition pmc8driver.h:64
@ PMC8_W
Definition pmc8driver.h:64
@ PMC8_S
Definition pmc8driver.h:64
@ PMC8_E
Definition pmc8driver.h:64
#define PMC8_MAX_TRACK_RATE
Definition pmc8driver.h:38
PMC8_MOUNT_TYPES MountType
Definition pmc8driver.h:81
std::string MainBoardFirmware
bool IsRev2Compliant
Definition pmc8driver.h:82
static Logger & getInstance()
Method to get a reference to the object (i.e., Singleton) It is a static method.
int addDebugLevel(const char *debugLevelName, const char *LoggingLevelName)
Adds a new debugging level to the driver.
PMC8_RAMP_DIRECTION rampDir
Definition pmc8.h:46
int targetRate
Definition pmc8.h:43
int rampLastStep
Definition pmc8.h:45
int timer
Definition pmc8.h:47
PMC8_MOVE_STATE state
Definition pmc8.h:41
uint8_t moveDir
Definition pmc8.h:42
int rampIteration
Definition pmc8.h:44
char name[MAXINDINAME]
Definition indiapi.h:321
char name[MAXINDINAME]
Definition indiapi.h:369
char name[MAXINDINAME]
Definition indiapi.h:248
double round(double value, int decimal_places)