Instrument Neutral Distributed Interface INDI 2.2.5
pmc8driver.cpp
Go to the documentation of this file.
1/*
2 INDI Explore Scientific PMC8 driver
3
4 Copyright (C) 2017 Michael Fulbright
5 Additional contributors:
6 Thomas Olson, Copyright (C) 2019
7 Karl Rees, Copyright (C) 2019-2023
8 Martin Ruiz, Copyright (C) 2023
9 Daniel Karnaukh, Copyright (C) 2026
10
11 Based on IEQPro driver.
12
13 This library is free software; you can redistribute it and/or
14 modify it under the terms of the GNU Lesser General Public
15 License as published by the Free Software Foundation; either
16 version 2.1 of the License, or (at your option) any later version.
17
18 This library is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 Lesser General Public License for more details.
22
23 You should have received a copy of the GNU Lesser General Public
24 License along with this library; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26*/
27
28#include "pmc8driver.h"
29
30#include "indicom.h"
31#include "indilogger.h"
32#include "inditelescope.h"
33
34#include <libnova/julian_day.h>
35#include <libnova/sidereal_time.h>
36
37#include <math.h>
38#include <string.h>
39#include <termios.h>
40#include <unistd.h>
41
42#include <sys/ioctl.h>
43
44// only used for test timing of pulse guiding
45#include <sys/time.h>
46
47#define PMC8_TIMEOUT 5 /* FD timeout in seconds */
48
49#define PMC8_SIMUL_VERSION_RESP "ESGvES06B9T9"
50
51// MOUNT_G11
52#define PMC8_G11_AXIS0_SCALE 4608000.0
53#define PMC8_G11_AXIS1_SCALE 4608000.0
54// MOUNT_EXOS2
55#define PMC8_EXOS2_AXIS0_SCALE 4147200.0
56#define PMC8_EXOS2_AXIS1_SCALE 4147200.0
57// MOUNT_iEXOS100
58#define PMC8_iEXOS100_AXIS0_SCALE 4147200.0
59#define PMC8_iEXOS100_AXIS1_SCALE 4147200.0
60
61// Need to initialize to some value, or certain clients (e.g., KStars Lite) freak out
64
65#define ARCSEC_IN_CIRCLE 1296000.0
66
67// Reference says 2621.44 counts, which then needs to be multiplied by 25 (so actually 16^4-1)
68// However, on Exos2 62500 (F424) is reported when slewing
69#define PMC8_MAX_PRECISE_MOTOR_RATE 62500
70
71// any guide pulses less than this are ignored as it will not result in any actual motor motion
72#define PMC8_PULSE_GUIDE_MIN_MS 20
73
74// guide pulses longer than this require using a timer
75#define PMC8_PULSE_GUIDE_MAX_NOTIMER 250
76
77#define PMC8_MAX_RETRIES 3 /*number of times to retry reading a response */
78#define PMC8_RETRY_DELAY 30000 /* how long to wait before retrying i/o */
79#define PMC8_MAX_IO_ERROR_THRESHOLD 2 /* how many consecutive read timeouts before trying to reset the connection */
80
81// Thanks to John Wells who contributed to this issue: https://github.com/indilib/indi/issues/2132
82// and PMC8 documentation: https://02d3287.netsolhost.com/pmc-eight/PMC_Eight_ProgrammersReferenceManual_Release2_2019_February_01.pdf
83// and confirmation: https://espmc-eight.groups.io/g/MAIN/topic/96696552?p=Created,,,20,2,0,0
84// The rate is communicated as "arcsecs per sidereal second" NOT per second so it should be 15.000
85// not 15.041067
86// The INDI Driver uses arcsecs per SOLAR second, so it needs to be converted.
87#define PMC8_RATE_SIDEREAL 15.000
88#define PMC8_RATE_LUNAR 14.451
89#define PMC8_RATE_SOLAR 14.959
90#define PMC8_RATE_KING 14.996
91
93bool pmc8_debug = false;
94bool pmc8_simulation = false;
97bool pmc8_goto_resume = true;
100double pmc8_latitude = 0; // must be kept updated by pmc8.cpp when it is changed!
101double pmc8_longitude = 0; // must be kept updated by pmc8.cpp when it is changed!
104int pmc8_east_dir = 1; // 1 is for northern hemisphere, switch to 0 for southern
106
107// state variable for driver based pulse guiding
108typedef struct PulseGuideState
109{
110 bool pulseguideactive = false;
111 bool fakepulse = false;
112 int ms;
113 long long pulse_start_us;
114 double cur_rate;
116 double new_rate;
119
120// need one for NS and EW pulses which may be simultaneous
122
123struct
124{
125 double ra;
126 double dec;
129 double trackRate;
130 double moveRate;
133
134// convert mount count to 6 character two complement hex string
135void convert_motor_counts_to_hex(int val, char *hex)
136{
137 unsigned tmp;
138 char h[16];
139
140 if (val < 0)
141 {
142 tmp = abs(val);
143 tmp = ~tmp;
144 tmp++;
145 }
146 else
147 {
148 tmp = val;
149 }
150
151 sprintf(h, "%08X", tmp);
152
153 strcpy(hex, h + 2);
154
155 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_motor_counts_to_hex val=%d, h=%s, hex=%s", val, h, hex);
156}
157
158// convert rate in arcsec/sidereal_second to internal PMC8 precise motor rate for RA axis tracking ONLY
159bool convert_precise_rate_to_motor(double rate, int *mrate)
160{
161
162 *mrate = round(25 * rate * (PMC8_AXIS0_SCALE / ARCSEC_IN_CIRCLE));
163
164 if (*mrate > PMC8_MAX_PRECISE_MOTOR_RATE)
165 {
166 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_WARNING, "requested tracking motor rate %d exceeds maximum, using %d", *mrate,
169 }
170 else if (*mrate < -PMC8_MAX_PRECISE_MOTOR_RATE)
171 {
172 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_WARNING, "requested tracking motor rate %d exceeds maximum, using %d", *mrate,
175 }
176
177 return true;
178}
179
180// convert rate in arcsec/sidereal_second to internal PMC8 precise motor rate for RA axis tracking ONLY
181bool convert_precise_motor_to_rate(int mrate, double *rate)
182{
183 *rate = ((double)mrate) * (ARCSEC_IN_CIRCLE / PMC8_AXIS0_SCALE) / 25;
184
185 return true;
186}
187
188// convert rate in arcsec/sidereal_second to internal PMC8 motor rate for move action (not slewing)
189bool convert_move_rate_to_motor(float rate, int *mrate)
190{
191
192 float capped_move_rate = rate;
193 if (rate > PMC8_MAX_MOVE_RATE)
194 capped_move_rate = PMC8_MAX_MOVE_RATE;
195 else if (rate < -PMC8_MAX_MOVE_RATE)
196 capped_move_rate = -PMC8_MAX_MOVE_RATE;
197
198 *mrate = (int)(capped_move_rate * (PMC8_AXIS0_SCALE / ARCSEC_IN_CIRCLE));
199
200 return true;
201}
202
203// convert rate internal PMC8 motor rate to arcsec/sec for move action (not slewing)
204bool convert_motor_rate_to_move_rate(int mrate, double *rate)
205{
206 *rate = ((double)mrate) * ARCSEC_IN_CIRCLE / PMC8_AXIS0_SCALE;
207
208 return true;
209}
210
212{
213 switch(index)
214 {
215 case 0: // LosMandy G11
218 break;
219 case 1: // EXOS2
222 break;
223 case 2: // iEXOS100
226 break;
227 default:
228 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Need To Select a Mount");
229 break;
230 }
231}
232
233void set_pmc8_debug(bool enable)
234{
235 pmc8_debug = enable;
236}
237
238void set_pmc8_simulation(bool enable)
239{
240 pmc8_simulation = enable;
241 if (enable)
242 simPMC8Data.guide_rate = 0.5;
243}
244
245void set_pmc8_device(const char *name)
246{
247 snprintf(pmc8_device, MAXINDIDEVICE, "%s", name);
248}
249
250void set_pmc8_location(double latitude, double longitude)
251{
252 pmc8_latitude = latitude;
253 pmc8_longitude = longitude;
254
255 pmc8_east_dir = (latitude < 0) ? 0 : 1;
256
257 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Set PMC8 'lowlevel' lat:%f long:%f", pmc8_latitude, pmc8_longitude);
258}
259
261{
263
264 if (value == ST_PARKED)
265 {
266 double lst;
267 double ra;
268
270
271 ra = lst + 6;
272 if (ra > 24)
273 ra -= 24;
274
276 if (pmc8_latitude < 0)
277 set_pmc8_sim_dec(-90.0);
278 else
279 set_pmc8_sim_dec(90.0);
280
281 }
282}
283
285{
286 simPMC8Data.trackRate = value;
287}
288
290{
291 simPMC8Data.moveRate = value;
292}
293
294void set_pmc8_sim_ra(double ra)
295{
296 simPMC8Data.ra = ra;
297}
298
300{
301 simPMC8Data.dec = dec;
302}
303
305{
306 pmc8_connection = connection;
307
308 if (connection == PMC8_ETHERNET)
309 {
310 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_SESSION, "Connecting to PMC8 via Ethernet.");
311 }
312 else
313 {
315 "Connecting to PMC8 via standard Serial cable. Please wait 15 seconds for mount to reset.");
317 "Connecting to PMC8 via Serial. Autodecting cable type. This could take up to 30 seconds.");
318 else DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_SESSION, "Connecting to PMC8 via inverted Serial.");
319 }
320
321 if (connection != PMC8_SERIAL_STANDARD)
322 {
323 for (int i = 0; i < 2; i++)
324 {
325 if (i) DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_SESSION, "Retrying...");
326
327 if (detect_pmc8(fd)) return true;
328 usleep(PMC8_RETRY_DELAY);
329 }
330 }
331
332 if ((connection == PMC8_SERIAL_STANDARD) || (connection == PMC8_SERIAL_AUTO))
333 {
334 // If they're not using a custom-configured cable, we need to clear DTR for serial to start working
335 // But this resets the PMC8, so only do it after we've already checked for connection
336 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Attempting to clear DTR for standard cable.");
337 int serial = TIOCM_DTR;
338 ioctl(fd, TIOCMBIC, &serial);
339
340 // when we clear DTR, the PMC8 will respond with initialization screen, so may need read several times
341 for (int i = 0; i < 2; i++)
342 {
343 if (i) DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_SESSION, "Retrying...");
344
345 if (detect_pmc8(fd))
346 {
347 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_WARNING, "Connected to PMC8 using a standard-configured FTDI cable."
348 "Your mount will reset and lose its position anytime you disconnect and reconnect."
349 "See http://indilib.org/devices/telescopes/explore-scientific-g11-pmc-eight/ ");
350 return true;
351 }
352 usleep(PMC8_RETRY_DELAY);
353 }
354 }
355
357 "check_pmc8_connection(): Error connecting. Check power and connection settings.");
358
359 return false;
360}
361
363{
364 char initCMD[] = "ESGv!";
365 int errcode = 0;
366 char errmsg[MAXRBUF];
367 char response[64];
368 int nbytes_read = 0;
369 int nbytes_written = 0;
370
371 if (pmc8_simulation)
372 {
373 strcpy(response, PMC8_SIMUL_VERSION_RESP);
374 nbytes_read = strlen(response);
375 }
376 else
377 {
378 if ((errcode = send_pmc8_command(fd, initCMD, strlen(initCMD), &nbytes_written)) != TTY_OK)
379 {
380 tty_error_msg(errcode, errmsg, MAXRBUF);
381 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error connecting on write: %s", errmsg);
382 return false;
383 }
384
385 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGv")))
386 {
387 tty_error_msg(errcode, errmsg, MAXRBUF);
388 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Error connecting on read: %s", errmsg);
389 return false;
390 }
391 }
392
393 // return true if valid firmware response
394 return (!strncmp(response, "ESGvES", 6));
395}
396
398{
399 // Only one model for now
400 info->Model.assign("PMC-Eight");
401
402 // Set the mount type from firmware if we can (instead of relying on interface)
403 // older firmware has type in firmware string
405 {
407
408 if (strstr(info->MainBoardFirmware.c_str(), "G11"))
409 {
410 info->MountType = MOUNT_G11;
411 }
412 else if (strstr(info->MainBoardFirmware.c_str(), "EXOS2"))
413 {
414 info->MountType = MOUNT_EXOS2;
415 }
416 else if (strstr(info->MainBoardFirmware.c_str(), "ES1A"))
417 {
419 }
420 }
421 else
422 {
423 //for newer firmware, need to use ESGi to get mount type
424 char cmd[] = "ESGi!";
425 int errcode = 0;
426 char errmsg[MAXRBUF];
427 char response[64];
428 int nbytes_read = 0;
429 int nbytes_written = 0;
430
431 if (pmc8_simulation)
432 {
433 strcpy(response, PMC8_SIMUL_VERSION_RESP);
434 nbytes_read = strlen(response);
435 }
436 else
437 {
438 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
439 {
440 tty_error_msg(errcode, errmsg, MAXRBUF);
442 return false;
443 }
444
445 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGi")))
446 {
447 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "get_pmc8_main_firmware(): Error reading response.");
448 return false;
449 }
450
451 //ESGi response should be 31 characters
452 if (nbytes_read >= 31)
453 {
454 //locate P9 code in response
455 char num_str[3] = {0};
456 snprintf(num_str, sizeof(num_str), "%.2s", response + 20);
457 int p9 = (int)strtol(num_str, nullptr, 10);
458
459 // Set mount type based on P9 code
460 if (p9 <= 1) info->MountType = MOUNT_iEXOS100;
461 // these codes are reserved. I'm assuming for something like iExos100, so let's go with that
462 else if (p9 <= 3)
463 {
465 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Unrecognized device code #%d. Treating as iEXOS100.", p9);
466 }
467 else if (p9 <= 7) info->MountType = MOUNT_G11;
468 else if (p9 <= 11) info->MountType = MOUNT_EXOS2;
469 // unrecognized code. Just going to guess and treat as iExos100.
470 else
471 {
473 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Unrecognized device code #%d. Treating as iEXOS100.", p9);
474 }
475
476 }
477 else
478 {
480 "Could not detect device type. Only received #%d bytes, expected at least 31.", nbytes_read);
481 return false;
482 }
483
484 tcflush(fd, TCIFLUSH);
485 }
486 }
487 // update mount parameters
489 return true;
490}
491
493{
494 char cmd[] = "ESGv!";
495 char board[64];
496 int errcode = 0;
497 char errmsg[MAXRBUF];
498 char response[64];
499 int nbytes_read = 0;
500 int nbytes_written = 0;
501
502 if (pmc8_simulation)
503 {
504 strcpy(response, PMC8_SIMUL_VERSION_RESP);
505 nbytes_read = strlen(response);
506 }
507 else
508 {
509 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
510 {
511 tty_error_msg(errcode, errmsg, MAXRBUF);
513 return false;
514 }
515
516 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGv")))
517 {
518 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "get_pmc8_main_firmware(): Error reading response.");
519 return false;
520 }
521 }
522
523 // prior to v2, minimum size firmware string is 12 (for iExos100), 14 for others, but can be up to 20
524 // post v2, can be 50+
525 if (nbytes_read >= 12)
526 {
527
528 // strip ESGvES from string when getting firmware version
529 strncpy(board, response + 6, nbytes_read - 7);
530 info->MainBoardFirmware.assign(board, nbytes_read - 7);
531
532 // Assuming version strings longer than 24 must be version 2.0 and up
533 if (nbytes_read > 24) info->IsRev2Compliant = pmc8_isRev2Compliant = true;
534
535 tcflush(fd, TCIFLUSH);
536
537 return true;
538 }
539
541 "Could not read firmware. Only received #%d bytes, expected at least 12.", nbytes_read);
542 return false;
543}
544
546{
547 bool rc = false;
548
549 rc = get_pmc8_main_firmware(fd, info);
550
551 if (rc == false)
552 return rc;
553
554 rc = get_pmc8_model(fd, info);
555
556 return rc;
557}
558
559// return move rate in arcsec / sec
560bool get_pmc8_move_rate_axis(int fd, PMC8_AXIS axis, double &rate)
561{
562 char cmd[32];
563 int errcode = 0;
564 char errmsg[MAXRBUF];
565 char response[16];
566 int nbytes_read = 0;
567 int nbytes_written = 0;
568
569 snprintf(cmd, sizeof(cmd), "ESGr%d!", axis);
570
571 if (pmc8_simulation)
572 {
573 if (axis == PMC8_AXIS_RA)
574 rate = simPMC8Data.trackRate;
575 else if (axis == PMC8_AXIS_DEC)
576 rate = 0; // DEC tracking not supported yet
577 else
578 return false;
579
580 return true;
581 }
582
583 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
584 {
585 tty_error_msg(errcode, errmsg, MAXRBUF);
587 return false;
588 }
589
590 cmd[5] = '\0';
591
592 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
593 {
594 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting Move Rate");
595 return false;
596 }
597
598 if (nbytes_read != 10)
599 {
600 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis get move rate cmd response incorrect");
601 return false;
602 }
603
604 char num_str[16] = {0};
605
606 snprintf(num_str, sizeof(num_str), "0X%.6s", response + 5);
607
608 int mrate = (int)strtol(num_str, nullptr, 0);
609
611
612 return true;
613}
614
615bool get_pmc8_direction_axis(int fd, PMC8_AXIS axis, int &dir)
616{
617 char cmd[32];
618 int errcode = 0;
619 char errmsg[MAXRBUF];
620 char response[16];
621 int nbytes_read = 0;
622 int nbytes_written = 0;
623
624 snprintf(cmd, sizeof(cmd), "ESGd%d!", axis);
625
626 if (pmc8_simulation)
627 {
628 if (axis == PMC8_AXIS_RA)
629 dir = simPMC8Data.raDirection;
630 else if (axis == PMC8_AXIS_DEC)
631 dir = simPMC8Data.decDirection;
632 else
633 return false;
634
635 return true;
636 }
637
638 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
639 {
640 tty_error_msg(errcode, errmsg, MAXRBUF);
642 return false;
643 }
644
645 cmd[5] = '\0';
646
647 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
648 {
649 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting direction axis");
650 return false;
651 }
652
653 if (nbytes_read != 7)
654 {
655 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis get dir cmd response incorrect");
656 return false;
657 }
658
659 char num_str[16] = {0};
660
661 snprintf(num_str, sizeof(num_str), "%.2s", response + 5);
662
663 dir = (int)strtol(num_str, nullptr, 0);
664
665 return true;
666}
667
668// if fast is true dont wait on response! Used for psuedo-pulse guide
669// NOTE that this will possibly mean the response will be read by a following command if it is called before
670// response comes from controller, since next command will flush before data is in buffer!
671bool set_pmc8_direction_axis(int fd, PMC8_AXIS axis, int dir, bool fast)
672{
673
674 char cmd[32], expresp[32];
675 int errcode = 0;
676 char errmsg[MAXRBUF];
677 char response[16];
678 int nbytes_read = 0;
679 int nbytes_written = 0;
680
681 snprintf(cmd, sizeof(cmd), "ESSd%d%d!", axis, dir);
682
683 if (pmc8_simulation)
684 {
685 if (axis == PMC8_AXIS_RA)
686 simPMC8Data.raDirection = (PMC8_DIRECTION) dir;
687 else if (axis == PMC8_AXIS_DEC)
688 simPMC8Data.decDirection = (PMC8_DIRECTION) dir;
689 else
690 return false;
691
692 return true;
693 }
694
695 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
696 {
697 tty_error_msg(errcode, errmsg, MAXRBUF);
699 return false;
700 }
701
702 if (fast)
703 {
704 return true;
705 }
706
707 snprintf(expresp, sizeof(expresp), "ESGd%d%d!", axis, dir);
708
709 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
710 {
711 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis get dir cmd response incorrect: expected=%s", expresp);
712 return false;
713 }
714
715 return true;
716}
717
718bool get_pmc8_is_scope_slewing(int fd, bool &isslew)
719{
720 double rarate;
721 double decrate;
722 bool rc;
723
725 if (!rc)
726 {
727 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "get_pmc8_is_scope_slewing(): Error reading RA move rate");
728 return false;
729 }
730
732 if (!rc)
733 {
734 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "get_pmc8_is_scope_slewing(): Error reading DEC move rate");
735 return false;
736 }
737
738 if (pmc8_simulation)
739 {
740 isslew = (simPMC8Info.systemStatus == ST_SLEWING);
741 }
742 else
743 {
744 isslew = ((rarate > PMC8_MAX_TRACK_RATE) || (decrate >= PMC8_MAX_TRACK_RATE));
745 }
746
747 return true;
748}
749
750// set move speed in terms of how many times sidereal
751// Southern Hemisphere support: In the southern hemisphere, the mount is oriented
752// differently relative to the celestial pole, so we need to invert the motor
753// directions for N/S movements. The pmc8_east_dir variable is 1 for northern
754// hemisphere and 0 for southern hemisphere.
755bool set_pmc8_move_rate_axis(int fd, PMC8_DIRECTION dir, int reqrate)
756{
757 int rate = reqrate;
758
759 if (rate > PMC8_MAX_MOVE_RATE)
760 rate = PMC8_MAX_MOVE_RATE;
761 else if (rate < -PMC8_MAX_MOVE_RATE)
762 rate = -PMC8_MAX_MOVE_RATE;
763
764 switch (dir)
765 {
766 case PMC8_S:
767 // In northern hemisphere, South is negative direction
768 // In southern hemisphere, South is positive direction (mount is flipped)
769 rate = pmc8_east_dir ? -rate : rate;
770 return set_pmc8_custom_dec_move_rate(fd, rate);
771 case PMC8_N:
772 // In northern hemisphere, North is positive direction
773 // In southern hemisphere, North is negative direction (mount is flipped)
774 rate = pmc8_east_dir ? rate : -rate;
775 return set_pmc8_custom_dec_move_rate(fd, rate);
776 case PMC8_E:
777 rate = -rate;
778 [[fallthrough]];
779 case PMC8_W:
780 return set_pmc8_custom_ra_move_rate(fd, rate);
781 }
782
783 return false;
784}
785
787{
788 bool rc;
789
790 // stop tracking
792 if (!rc)
793 {
794 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error stopping RA axis!");
795 return false;
796 }
797
798 return true;
799}
800
801// get current (precise) tracking rate in arcsec/sec
802bool get_pmc8_track_rate(int fd, double &rate)
803{
804 char cmd[32];
805 int errcode = 0;
806 char errmsg[MAXRBUF];
807 char response[16];
808 int nbytes_read = 0;
809 int nbytes_written = 0;
810
811 snprintf(cmd, sizeof(cmd), "ESGx!");
812
813 if (pmc8_simulation)
814 {
815 rate = simPMC8Data.trackRate;
816 return true;
817 }
818
819 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
820 {
821 tty_error_msg(errcode, errmsg, MAXRBUF);
823 return false;
824 }
825
826 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGx")))
827 {
828 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting Tracking Rate");
829 return false;
830 }
831
832 if (nbytes_read != 9)
833 {
834 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Get track rate cmd response incorrect");
835 return false;
836 }
837
838 char num_str[16] = {0};
839
840 snprintf(num_str, sizeof(num_str), "0X%.4s", response + 4);
841
842 int mrate = (int)strtol(num_str, nullptr, 0);
843 convert_precise_motor_to_rate(mrate, &rate);
844
845 return true;
846}
847
848bool get_pmc8_tracking_data(int fd, double &rate, uint8_t &mode)
849{
850 if (!get_pmc8_track_rate(fd, rate)) return false;
852 return true;
853}
854
855
857{
858 int tmotor, refmotor;
859 uint8_t mode;
860
861 //get precise motor rate
862 convert_precise_rate_to_motor(rate, &tmotor);
863
864 //now check what sidereal would be
866 if (tmotor == refmotor) mode = PMC8_TRACK_SIDEREAL;
867 else
868 {
869
870 //now check lunar
872 if (tmotor == refmotor) mode = PMC8_TRACK_LUNAR;
873 else
874 {
875
876 //now check solar
878 if (tmotor == refmotor) mode = PMC8_TRACK_SOLAR;
879 else
880 {
881
882 //now check king
884 if (tmotor == refmotor) mode = PMC8_TRACK_KING;
885 // must be custom
886 else mode = PMC8_TRACK_CUSTOM;
887 }
888 }
889 }
890 return mode;
891}
892
893
894// set speed for move action (MoveNS/MoveWE) NOT slews! This version DOESNT handle direction and expects a motor rate!
895// if fast is true dont wait on response! Used for psuedo-pulse guide
896// NOTE that this will possibly mean the response will be read by a following command if it is called before
897// response comes from controller, since next command will flush before data is in buffer!
898bool set_pmc8_axis_motor_rate(int fd, PMC8_AXIS axis, int mrate, bool fast)
899{
900 char cmd[24];
901 int errcode = 0;
902 char errmsg[MAXRBUF];
903 char response[24];
904 int nbytes_read = 0;
905 int nbytes_written = 0;
906
907 snprintf(cmd, sizeof(cmd), "ESSr%d%04X!", axis, mrate);
908
909 if (pmc8_simulation)
910 {
911 return true;
912 }
913
914 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
915 {
916 tty_error_msg(errcode, errmsg, MAXRBUF);
918 return false;
919 }
920
921 if (fast)
922 {
923 return true;
924 }
925
926 snprintf(cmd, sizeof(cmd), "ESGr%d", axis);
927
928 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
929 {
930 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error setting axis motor rate");
931 return false;
932 }
933
934 if (nbytes_read == 10)
935 {
936 tcflush(fd, TCIFLUSH);
937 return true;
938 }
939
940 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Only received #%d bytes, expected 10.", nbytes_read);
941 return false;
942}
943
944// set speed for move action (MoveNS/MoveWE) NOT slews! This version accepts arcsec/sec as rate.
945// also handles direction
946bool set_pmc8_axis_move_rate(int fd, PMC8_AXIS axis, float rate)
947{
948 bool rc;
949 int motor_rate;
950
951 // set direction
952 if (rate < 0)
953 rc = set_pmc8_direction_axis(fd, axis, 0, false);
954 else
955 rc = set_pmc8_direction_axis(fd, axis, 1, false);
956
957 if (!rc)
958 return rc;
959
960 if (!convert_move_rate_to_motor(fabs(rate), &motor_rate))
961 {
962 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error converting rate %f", rate);
963 return false;
964 }
965
966 rc = set_pmc8_axis_motor_rate(fd, axis, motor_rate, false);
967
968 if (pmc8_simulation)
969 {
970 simPMC8Data.moveRate = rate;
971 return true;
972 }
973
974 return rc;
975}
976
977#if 0
978bool set_pmc8_track_enabled(int fd, bool enabled)
979{
980 char cmd[32];
981 int errcode = 0;
982 char errmsg[MAXRBUF];
983 char response[8];
984 int nbytes_read = 0;
985 int nbytes_written = 0;
986
987 snprintf(cmd, 32, ":ST%d#", enabled ? 1 : 0);
988
990
991 if (pmc8_simulation)
992 {
993 // FIXME - need to implement pmc8 track enabled sim
994 // simPMC8Info.systemStatus = enabled ? ST_TRACKING_PEC_ON : ST_STOPPED;
995 // strcpy(response, "1");
996 // nbytes_read = strlen(response);
997
998 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Need to implement pmc8 track enabled sim");
999 return false;
1000 }
1001 else
1002 {
1003 // determine current tracking mode
1004
1005 // return SetTrackMode(enabled ? IUFindOnSwitchIndex(&TrackModeSP) : AP_TRACKING_OFF);
1006 }
1007}
1008#endif
1009
1010bool set_pmc8_track_mode(int fd, uint8_t mode)
1011{
1012 float ratereal = 0;
1013
1014 switch (mode)
1015 {
1017 ratereal = PMC8_RATE_SIDEREAL;
1018 break;
1019 case PMC8_TRACK_LUNAR:
1020 ratereal = PMC8_RATE_LUNAR;
1021 break;
1022 case PMC8_TRACK_SOLAR:
1023 ratereal = PMC8_RATE_SOLAR;
1024 break;
1025 case PMC8_TRACK_KING:
1026 ratereal = PMC8_RATE_KING;
1027 break;
1028 default:
1029 return false;
1030 break;
1031 }
1032
1033 if (!set_pmc8_direction_axis(fd, PMC8_AXIS_RA, pmc8_east_dir, false)) return false;
1034 return set_pmc8_custom_ra_track_rate(fd, ratereal);
1035}
1036
1037// start tracking at a precision track rate
1038bool set_pmc8_ra_tracking(int fd, double rate)
1039{
1040 //set right direction
1041 int direction = pmc8_east_dir;
1042 if (rate < 0) direction = !direction;
1043 if (!set_pmc8_direction_axis(fd, PMC8_AXIS_RA, direction, false)) return false;
1044
1045 //then set rate
1046 return (set_pmc8_custom_ra_track_rate(fd, fabs(rate)));
1047}
1048
1049// just set the precision track rate - for when we've already set tracking direction
1050bool set_pmc8_custom_ra_track_rate(int fd, double rate)
1051{
1052 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_custom_ra_track_rate() called rate=%f ", rate);
1053
1054 char cmd[24];
1055 int errcode = 0;
1056 char errmsg[MAXRBUF];
1057 char response[24];
1058 int nbytes_read = 0;
1059 int nbytes_written = 0;
1060 int rateval;
1061
1062 if (!convert_precise_rate_to_motor(rate, &rateval))
1063 {
1064 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error converting rate %f", rate);
1065 return false;
1066 }
1067
1068 snprintf(cmd, sizeof(cmd), "ESTr%04X!", rateval);
1069
1070 if (pmc8_simulation)
1071 {
1072 simPMC8Data.trackRate = rate;
1073 return true;
1074 }
1075 else
1076 {
1077 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1078 {
1079 tty_error_msg(errcode, errmsg, MAXRBUF);
1081 return false;
1082 }
1083
1084 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGx")))
1085 {
1086 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error setting custom RA track rate");
1087 return false;
1088 }
1089 }
1090
1091 if (nbytes_read != 9)
1092 {
1093 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Only received #%d bytes, expected 9.", nbytes_read);
1094 return false;
1095 }
1096
1097 tcflush(fd, TCIFLUSH);
1098
1099 // set direction to 1
1100 // return set_pmc8_direction_axis(fd, PMC8_AXIS_RA, 1, false);
1101 return true;
1102}
1103
1104#if 0
1105bool set_pmc8_custom_dec_track_rate(int fd, double rate)
1106{
1107 bool rc;
1108
1109 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_custom_dec_track_rate() called rate=%f ", rate);
1110
1111 if (pmc8_simulation)
1112 {
1113 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_custom_dec_track_rate simulation not implemented");
1114
1115 rc = false;
1116 }
1117 else
1118 {
1119 rc = set_pmc8_axis_rate(fd, PMC8_AXIS_DEC, rate);
1120 }
1121
1122 return rc;
1123}
1124#else
1126{
1127 INDI_UNUSED(fd);
1128 INDI_UNUSED(rate);
1129
1130 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_custom_dec_track_rate not implemented!");
1131 return false;
1132}
1133#endif
1134
1135bool set_pmc8_custom_ra_move_rate(int fd, double rate)
1136{
1137 bool rc;
1138
1139 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_custom_ra move_rate() called rate=%f ", rate);
1140
1141 // safe guard for now - only all use to STOP slewing or MOVE commands with this
1142 if (fabs(rate) > PMC8_MAX_MOVE_RATE)
1143 {
1144 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_custom_ra_move rate only supports low rates currently");
1145
1146 return false;
1147 }
1148
1150
1151 return rc;
1152}
1153
1154bool set_pmc8_custom_dec_move_rate(int fd, double rate)
1155{
1156 bool rc;
1157
1158 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_custom_dec_move_rate() called rate=%f ", rate);
1159
1160 // safe guard for now - only all use to STOP slewing with this
1161 if (fabs(rate) > PMC8_MAX_MOVE_RATE)
1162 {
1163 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_custom_dec_move_rate only supports low rates currently");
1164 return false;
1165 }
1166
1168
1169 return rc;
1170}
1171
1172// rate is fraction of sidereal
1173bool set_pmc8_guide_rate(int fd, PMC8_AXIS axis, double rate)
1174{
1175 if (pmc8_simulation)
1176 {
1177 simPMC8Data.guide_rate = rate;
1178 return true;
1179 }
1180
1181 // set driver values
1182 if (axis == PMC8_AXIS_RA)
1183 {
1185 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_guide_rate: ra guide rate set to %f", rate);
1186 }
1187 if ((axis == PMC8_AXIS_DEC) || !pmc8_isRev2Compliant)
1188 {
1190 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_guide_rate: dec guide rate set to %f", rate);
1191 }
1192
1194 {
1195 // now write to mount to sync ST4 rates
1196 char cmd[32], expresp[32];
1197 int errcode = 0;
1198 char errmsg[MAXRBUF];
1199 char response[16];
1200 int nbytes_read = 0;
1201 int nbytes_written = 0;
1202
1203 snprintf(cmd, sizeof(cmd), "ESSf%d%02X!", axis, int(rate * 100));
1204
1205 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1206 {
1207 tty_error_msg(errcode, errmsg, MAXRBUF);
1209 return false;
1210 }
1211
1212 snprintf(expresp, sizeof(expresp), "ESGf%d%02X!", axis, int(rate * 100));
1213
1214 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
1215 {
1216 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "SRF set cmd response incorrect: expected=%s", expresp);
1217 return false;
1218 }
1219 }
1220
1221 return true;
1222}
1223
1224// get SRF value for axis
1225bool get_pmc8_guide_rate(int fd, PMC8_AXIS axis, double &rate)
1226{
1227 if (pmc8_simulation)
1228 {
1229 rate = simPMC8Data.guide_rate;
1230 return true;
1231 }
1232
1233 // read from mount
1234 char cmd[32];
1235 int errcode = 0;
1236 char errmsg[MAXRBUF];
1237 char response[16];
1238 int nbytes_read = 0;
1239 int nbytes_written = 0;
1240
1241 snprintf(cmd, sizeof(cmd), "ESGf%d!", axis);
1242
1243 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1244 {
1245 tty_error_msg(errcode, errmsg, MAXRBUF);
1247 return false;
1248 }
1249
1250 cmd[5] = '\0';
1251
1252 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
1253 {
1254 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting SRF rate");
1255 return false;
1256 }
1257
1258 if (nbytes_read != 8)
1259 {
1260 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "SRF Get rate cmd response incorrect");
1261 return false;
1262 }
1263
1264 char num_str[16] = {0};
1265
1266 snprintf(num_str, sizeof(num_str), "0X%.2s", response + 5);
1267 int tint = strtol(num_str, nullptr, 0);
1268
1269 rate = ((double)tint) / 100;
1270
1271 // set driver values
1272 if (axis == PMC8_AXIS_RA)
1273 {
1275 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "get_pmc8_guide_rate: ra guide rate set to %f", rate);
1276 }
1277 else
1278 {
1280 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "get_pmc8_guide_rate: dec guide rate set to %f", rate);
1281 }
1282
1283 return true;
1284}
1285
1287{
1288
1289 switch (gdir)
1290 {
1291 case PMC8_N:
1292 case PMC8_S:
1293 *pstate = &NS_PulseGuideState;
1294 break;
1295
1296 case PMC8_W:
1297 case PMC8_E:
1298 *pstate = &EW_PulseGuideState;
1299 break;
1300
1301 default:
1302 return false;
1303 break;
1304 }
1305 return true;
1306}
1307
1308// if return value is true then timetaken will return how much pulse time has already occurred
1309bool start_pmc8_guide(int fd, PMC8_DIRECTION gdir, int ms, long &timetaken_us, double ratehint)
1310{
1311 bool rc;
1312 double cur_rate = 0;
1313 int cur_dir = -1;
1314
1315 // used to test timing
1316 struct timeval tp;
1317 long long pulse_start_us;
1318 long long pulse_sofar_us;
1319
1320 PulseGuideState *pstate;
1321
1322 if (!get_pmc8_guide_state(gdir, &pstate))
1323 {
1324 return false;
1325 }
1326
1327 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): pulse dir=%d dur=%d ms", gdir, ms);
1328
1329 if (pstate->pulseguideactive)
1330 {
1331 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_start_guide(): already executing a pulse guide!");
1332 return false;
1333 }
1334
1335 // ignore short pulses - they do nothing
1336 if (ms < PMC8_PULSE_GUIDE_MIN_MS)
1337 {
1338 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): ignore short pulse ms=%d ms", ms);
1339 timetaken_us = ms * 1000;
1340 pstate->pulseguideactive = true;
1341 pstate->fakepulse = true;
1342 return true;
1343 }
1344
1345 // get precise tracking rate if in RA
1346 if ((gdir == PMC8_E) || (gdir == PMC8_W))
1347 {
1348
1349 // use rate provided by interface if valid rather than querying for it
1350 if (ratehint <= 0)
1351 {
1352 rc = get_pmc8_track_rate(fd, cur_rate);
1353 if (!rc)
1354 {
1355 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_start_guide(): error reading current RA rate!");
1356 return rc;
1357 }
1358 }
1359 else
1360 cur_rate = ratehint;
1361 }
1362 // we could get slew rate if in DEC, but driver doesn't currently support DEC tracking
1363 // and we shouldn't get here if we're slewing, so for now we assume interface is always correct and avoid delay from unnecessary calls to mount
1364 else
1365 {
1366 cur_rate = ratehint;
1367 }
1368
1369 // if slewing abort
1370 // shouldn't get here if slewing, but doesn't hurt to check
1371 if (cur_rate > PMC8_MAX_TRACK_RATE)
1372 {
1374 "pmc8_start_guide(): Cannot send guide correction while slewing! rate=%d dir=%d",
1375 cur_rate, gdir);
1376 return rc;
1377 }
1378
1379 double new_rate = cur_rate;
1380 int new_dir = 0;
1381
1382 // RA guiding routine just changes the precision tracking call
1383 if ((gdir == PMC8_E) || (gdir == PMC8_W))
1384 {
1386
1387 if (gdir == PMC8_E) new_rate -= guide_rate;
1388 else new_rate += guide_rate;
1389
1390 if (new_rate < 0)
1391 {
1393 "pmc8_start_guide(): with current tracking rate of %f, requested guide rate of %f would flip RA motor in opposite direction, so pausing motor instead.",
1394 cur_rate, new_rate);
1395 new_rate = 0;
1396 }
1397
1398 // measure time when we start pulse
1399 gettimeofday(&tp, nullptr);
1400 pulse_start_us = tp.tv_sec * 1000000 + tp.tv_usec;
1401
1402 if (!set_pmc8_custom_ra_track_rate(fd, new_rate))
1403 {
1404 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_start_guide(): error settings new_rate to %f", new_rate);
1405 return false;
1406 }
1407
1408 }
1409 // DEC guiding routine needs to set a DEC move rate and possibly a new direction
1410 // Southern Hemisphere support: In the southern hemisphere, the celestial pole
1411 // is in the opposite direction, so guide pulses need to be inverted.
1412 // pmc8_east_dir is 1 for northern hemisphere, 0 for southern hemisphere.
1413 else if ((gdir == PMC8_N) || (gdir == PMC8_S))
1414 {
1416
1417 // Determine effective guide direction considering hemisphere
1418 // In southern hemisphere, N/S guide commands need to be inverted
1419 bool effectiveSouth = (gdir == PMC8_S);
1420 if (!pmc8_east_dir) effectiveSouth = !effectiveSouth; // flip for southern hemisphere
1421
1422 if (effectiveSouth) new_rate -= guide_rate;
1423 else new_rate += guide_rate;
1424
1425 if (new_rate < 0) new_dir = 1;
1426
1427 int mrate;
1428 if (!convert_move_rate_to_motor(fabs(new_rate), &mrate))
1429 {
1430 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error converting rate %f", new_rate);
1431 return false;
1432 }
1433
1434 // we should flip direction first so that we decrease the distance we could be going in the wrong direction
1435 // this is of course obvious with dec assumed to be 0, but just a reminder in case we ever support dec tracking
1436
1437 // ideally, we would set direction only if needed
1438 // but based on our current assumptions, that could cost us an extra call to find out the current direction
1439 // so for now we'll always end up setting the direction
1440 if (cur_dir != new_dir)
1441 if (!set_pmc8_direction_axis(fd, PMC8_AXIS_DEC, new_dir, false))
1442 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): error setting new_dec_dir");
1443
1444 // measure time when we start pulse
1445 gettimeofday(&tp, nullptr);
1446 pulse_start_us = tp.tv_sec * 1000000 + tp.tv_usec;
1447
1448 if (!set_pmc8_axis_motor_rate(fd, PMC8_AXIS_DEC, mrate, false))
1449 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): error setting new_dec_rate");
1450
1451 }
1452 // if for some reason the gdir is non-sensical ...
1453 else
1454 {
1455 return false;
1456 }
1457
1458 // store state
1459 pstate->pulseguideactive = true;
1460 pstate->fakepulse = false;
1461 pstate->ms = ms;
1462 pstate->pulse_start_us = pulse_start_us;
1463 pstate->cur_rate = cur_rate;
1464 pstate->cur_dir = cur_dir;
1465 pstate->new_rate = new_rate;
1466 pstate->new_dir = new_dir;
1467
1468 // see how long we've waited
1469 gettimeofday(&tp, nullptr);
1470 pulse_sofar_us = (tp.tv_sec * 1000000 + tp.tv_usec) - pulse_start_us;
1471
1472 timetaken_us = pulse_sofar_us;
1473
1474 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): timetaken_us=%d us", timetaken_us);
1475
1476 return true;
1477}
1478
1480{
1481 struct timeval tp;
1482 long long pulse_end_us;
1483
1484 PulseGuideState *pstate;
1485
1486 if (!get_pmc8_guide_state(gdir, &pstate))
1487 {
1488 return false;
1489 }
1490
1491 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): pulse dir=%d dur=%d ms", gdir, pstate->ms);
1492
1493 if (!pstate->pulseguideactive)
1494 {
1495 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_stop_guide(): pulse guide not active!!");
1496 return false;
1497 }
1498
1499 // flush any responses to commands we ignored above!
1500 tcflush(fd, TCIFLUSH);
1501
1502 // "fake pulse" - it was so short we would have overshot its length AND the motors wouldn't have moved anyways
1503 if (pstate->fakepulse)
1504 {
1505
1506 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): fake pulse done");
1507 pstate->pulseguideactive = false;
1508 return true;
1509 }
1510
1511 gettimeofday(&tp, nullptr);
1512 pulse_end_us = tp.tv_sec * 1000000 + tp.tv_usec;
1513
1514 if ((gdir == PMC8_E) || (gdir == PMC8_W))
1515 {
1517 {
1518 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_stop_guide(): error restoring tracking_rate to %f",
1519 pstate->cur_rate);
1520 return false;
1521 }
1522 }
1523 else if ((gdir == PMC8_N) || (gdir == PMC8_S))
1524 {
1525 int mrate;
1526
1527 if (!convert_move_rate_to_motor(fabs(pstate->cur_rate), &mrate))
1528 {
1529 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error converting rate %f", pstate->cur_rate);
1530 return false;
1531 }
1532
1533 // under assumption of no dec tracking, all we need to do is stop motion
1534 // but if dec tracking is ever supported, need to fix direction, and it may be better to do that first if cur_rate > new_rate
1535 if (!set_pmc8_axis_motor_rate(fd, PMC8_AXIS_DEC, mrate, false))
1536 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): error returning to old move rate");
1537 // only change direction if needed
1538 if ((pstate->cur_rate != 0) && (pstate->cur_dir != pstate->new_dir))
1539 if (!set_pmc8_direction_axis(fd, PMC8_AXIS_DEC, pstate->cur_dir, false))
1540 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): error returning to old direction");
1541 }
1542 else
1543 {
1544 return false;
1545 }
1546
1547 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): requested = %d ms, actual = %f ms",
1548 pstate->ms, (pulse_end_us - pstate->pulse_start_us) / 1000.0);
1549
1550 // flush any responses to commands we ignored above!
1551 tcflush(fd, TCIFLUSH);
1552
1553 // mark pulse done
1554 pstate->pulseguideactive = false;
1555
1556 return true;
1557}
1558
1559// convert from axis position returned by controller to motor counts used in conversion to RA/DEC
1561{
1562 int r;
1563
1564 if (axispos > 8388608)
1565 r = 0 - (16777216 - axispos);
1566 else
1567 r = axispos;
1568
1569 return r;
1570}
1571
1573{
1574 double motor_angle;
1575 double hour_angle;
1576 double lst;
1577
1578 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_ra_to_motor - ra=%f sop=%d", ra, sop);
1579
1581
1582 hour_angle = lst - ra;
1583
1584 // limit values to +/- 12 hours
1585 if (hour_angle > 12)
1586 hour_angle = hour_angle - 24;
1587 else if (hour_angle <= -12)
1588 hour_angle = hour_angle + 24;
1589
1590 // Northern Hemisphere
1591 if (pmc8_east_dir)
1592 {
1593 if (sop == INDI::Telescope::PIER_EAST)
1594 motor_angle = hour_angle - 6;
1595 else if (sop == INDI::Telescope::PIER_WEST)
1596 motor_angle = hour_angle + 6;
1597 else
1598 return false;
1599 }
1600 // Southern Hemisphere
1601 else
1602 {
1603 if (sop == INDI::Telescope::PIER_EAST)
1604 motor_angle = -(hour_angle + 6);
1605 else if (sop == INDI::Telescope::PIER_WEST)
1606 motor_angle = -(hour_angle - 6);
1607 else
1608 return false;
1609 }
1610
1611
1612 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_ra_to_motor - lst = %f hour_angle=%f", lst, hour_angle);
1613
1614 *mcounts = motor_angle * PMC8_AXIS0_SCALE / 24;
1615
1616 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_ra_to_motor - motor_angle=%f *mcounts=%d", motor_angle, *mcounts);
1617
1618
1619 return true;
1620}
1621
1622bool convert_motor_to_radec(int racounts, int deccounts, double &ra_value, double &dec_value)
1623{
1624 double motor_angle;
1625 double hour_angle;
1626
1627 double lst;
1628
1630
1631 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "lst = %f", lst);
1632
1633 motor_angle = (24.0 * racounts) / PMC8_AXIS0_SCALE;
1634
1635 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "racounts = %d motor_angle = %f", racounts, motor_angle);
1636
1637 // Northern Hemisphere
1638 if (pmc8_east_dir)
1639 {
1640 if (deccounts < 0)
1641 hour_angle = motor_angle + 6;
1642 else
1643 hour_angle = motor_angle - 6;
1644 }
1645 // Southern Hemisphere
1646 else
1647 {
1648 if (deccounts < 0)
1649 hour_angle = -(motor_angle + 6);
1650 else
1651 hour_angle = -(motor_angle - 6);
1652 }
1653
1654 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "hour_angle = %f", hour_angle);
1655
1656 ra_value = lst - hour_angle;
1657
1658 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "ra_value = %f", ra_value);
1659
1660 if (ra_value >= 24.0)
1661 ra_value = ra_value - 24.0;
1662 else if (ra_value < 0.0)
1663 ra_value = ra_value + 24.0;
1664
1665 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "ra_value (final) = %f", ra_value);
1666
1667 motor_angle = (360.0 * deccounts) / PMC8_AXIS1_SCALE;
1668
1669 // Northern Hemisphere
1670 if (pmc8_east_dir)
1671 {
1672 if (motor_angle >= 0)
1673 dec_value = 90 - motor_angle;
1674 else
1675 dec_value = 90 + motor_angle;
1676 }
1677 // Southern Hemisphere
1678 else
1679 {
1680 if (motor_angle >= 0)
1681 dec_value = -90 + motor_angle;
1682 else
1683 dec_value = -90 - motor_angle;
1684 }
1685
1686 return true;
1687}
1688
1690{
1691 double motor_angle;
1692
1693 // Northern Hemisphere
1694 if (pmc8_east_dir)
1695 {
1696 if (sop == INDI::Telescope::PIER_EAST)
1697 motor_angle = (dec - 90.0);
1698 else if (sop == INDI::Telescope::PIER_WEST)
1699 motor_angle = -(dec - 90.0);
1700 else
1701 return false;
1702 }
1703 // Southern Hemisphere
1704 else
1705 {
1706 if (sop == INDI::Telescope::PIER_EAST)
1707 motor_angle = -(dec + 90.0);
1708 else if (sop == INDI::Telescope::PIER_WEST)
1709 motor_angle = (dec + 90.0);
1710 else
1711 return false;
1712 }
1713
1714 *mcounts = (motor_angle / 360.0) * PMC8_AXIS1_SCALE;
1715
1716 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_dec_to_motor dec = %f, sop = %d", dec, sop);
1717 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_dec_to_motor motor_angle = %f, motor_counts= %d", motor_angle, *mcounts);
1718
1719 return true;
1720}
1721
1723{
1724
1725 char cmd[32];
1726 char expresp[32];
1727 char hexpt[16];
1728 int errcode = 0;
1729 char errmsg[MAXRBUF];
1730 char response[16];
1731 int nbytes_read = 0;
1732 int nbytes_written = 0;
1733
1734 convert_motor_counts_to_hex(point, hexpt);
1735
1736 // for v2+ firmware, use axis 2 if we don't want to track after the slew
1737 int naxis = axis;
1738 if (pmc8_isRev2Compliant && !axis && !pmc8_goto_resume) naxis = 2;
1739 snprintf(cmd, sizeof(cmd), "ESPt%d%s!", naxis, hexpt);
1740
1741 if (!pmc8_simulation)
1742 {
1743
1744 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1745 {
1746 tty_error_msg(errcode, errmsg, MAXRBUF);
1748 return false;
1749 }
1750
1751 snprintf(expresp, sizeof(expresp), "ESGt%d%s!", naxis, hexpt);
1752
1753 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
1754 {
1755 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis Set Point cmd response incorrect: %s - expected %s", response,
1756 expresp);
1757 return false;
1758 }
1759 }
1760
1761 return true;
1762}
1763
1764bool set_pmc8_target_position(int fd, int rapoint, int decpoint)
1765{
1766 bool rc;
1767
1769
1770 if (!rc)
1771 return rc;
1772
1774
1775 return rc;
1776}
1777
1778
1779bool set_pmc8_position_axis(int fd, PMC8_AXIS axis, int point)
1780{
1781
1782 char cmd[32];
1783 char expresp[32];
1784 char hexpt[16];
1785 int errcode = 0;
1786 char errmsg[MAXRBUF];
1787 char response[16];
1788 int nbytes_read = 0;
1789 int nbytes_written = 0;
1790
1791 if (pmc8_simulation)
1792 {
1793 // FIXME - need to implement simulation code for setting point position
1794 return true;
1795 }
1796
1797 convert_motor_counts_to_hex(point, hexpt);
1798 snprintf(cmd, sizeof(cmd), "ESSp%d%s!", axis, hexpt);
1799
1800 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1801 {
1802 tty_error_msg(errcode, errmsg, MAXRBUF);
1804 return false;
1805 }
1806
1807 snprintf(expresp, sizeof(expresp), "ESGp%d%s!", axis, hexpt);
1808
1809 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
1810 {
1811 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis Set Point cmd response incorrect: %s - expected %s", response,
1812 expresp);
1813 return false;
1814 }
1815
1816 return true;
1817}
1818
1819
1820bool set_pmc8_position(int fd, int rapoint, int decpoint)
1821{
1822 bool rc;
1823
1824 rc = set_pmc8_position_axis(fd, PMC8_AXIS_RA, rapoint);
1825
1826 if (!rc)
1827 return rc;
1828
1829 rc = set_pmc8_position_axis(fd, PMC8_AXIS_DEC, decpoint);
1830
1831 return rc;
1832}
1833
1834
1835bool get_pmc8_position_axis(int fd, PMC8_AXIS axis, int &point)
1836{
1837
1838 char cmd[32];
1839 int errcode = 0;
1840 char errmsg[MAXRBUF];
1841 char response[16];
1842 int nbytes_read = 0;
1843 int nbytes_written = 0;
1844
1845 if (pmc8_simulation)
1846 {
1847 // FIXME - need to implement simulation code for setting point position
1848 return true;
1849 }
1850
1851 snprintf(cmd, sizeof(cmd), "ESGp%d!", axis);
1852
1853 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1854 {
1855 tty_error_msg(errcode, errmsg, MAXRBUF);
1857 return false;
1858 }
1859
1860 cmd[5] = '\0';
1861
1862 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
1863 {
1864 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting position axis");
1865 return false;
1866 }
1867
1868 if (nbytes_read != 12)
1869 {
1870 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis Get Point cmd response incorrect");
1871 return false;
1872 }
1873
1874 char num_str[16] = {0};
1875
1876 snprintf(num_str, sizeof(num_str), "0X%.6s", response + 5);
1877
1878 point = (int)strtol(num_str, nullptr, 0);
1879
1880 return true;
1881}
1882
1883
1884bool get_pmc8_position(int fd, int &rapoint, int &decpoint)
1885{
1886 bool rc;
1887 int axis_ra_pos, axis_dec_pos;
1888
1889 rc = get_pmc8_position_axis(fd, PMC8_AXIS_RA, axis_ra_pos);
1890
1891 if (!rc)
1892 return rc;
1893
1894 rc = get_pmc8_position_axis(fd, PMC8_AXIS_DEC, axis_dec_pos);
1895
1896 if (!rc)
1897 return rc;
1898
1899 // convert from axis position to motor counts
1900 rapoint = convert_axispos_to_motor(axis_ra_pos);
1901 decpoint = convert_axispos_to_motor(axis_dec_pos);
1902
1903 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "ra axis pos = 0x%x motor_counts=%d", axis_ra_pos, rapoint);
1904 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "dec axis pos = 0x%x motor_counts=%d", axis_dec_pos, decpoint);
1905
1906 return rc;
1907}
1908
1909
1910bool park_pmc8(int fd)
1911{
1912
1913 bool rc;
1914
1915 rc = set_pmc8_target_position(fd, 0, 0);
1916
1917 // FIXME - Need to add code to handle simulation and also setting any scope state values
1918
1919 return rc;
1920}
1921
1922
1924{
1925 INDI_UNUSED(fd);
1926
1927 // nothing really to do for PMC8 there is no unpark command
1928
1929 if (pmc8_simulation)
1930 {
1932 return true;
1933 }
1934
1935
1936 // FIXME - probably need to set a state variable to show we're unparked
1938
1939 return true;
1940}
1941
1943{
1944 bool rc;
1945
1946 if (pmc8_simulation)
1947 {
1948 // FIXME - need to do something to represent mount has stopped slewing
1949 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "PMC8 slew stopped in simulation - need to add more code?");
1950 return true;
1951 }
1952
1953 // stop move/slew rates
1955 if (!rc)
1956 {
1957 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error stopping RA axis!");
1958 return false;
1959 }
1960
1962 if (!rc)
1963 {
1964 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error stopping DEC axis!");
1965 return false;
1966 }
1967
1968 return true;
1969}
1970
1972{
1973 char cmd[32];
1974 char expresp[32];
1975 int errcode = 0;
1976 char errmsg[MAXRBUF];
1977 char response[16];
1978 int nbytes_read = 0;
1979 int nbytes_written = 0;
1980
1981 snprintf(cmd, sizeof(cmd), "ESPt300000!");
1982
1983 if (!pmc8_simulation)
1984 {
1985
1986 if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1987 {
1988 tty_error_msg(errcode, errmsg, MAXRBUF);
1990 return false;
1991 }
1992
1993 snprintf(expresp, sizeof(expresp), "ESGt3!");
1994
1995 if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
1996 {
1997 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Abort Goto cmd response incorrect: %s - expected %s", response,
1998 expresp);
1999 return false;
2000 }
2001 }
2002
2003 return true;
2004}
2005
2006// "slew" on PMC8 is instantaneous once you set the target ra/dec
2007// no concept of setting target and then starting a slew operation as two steps
2008bool slew_pmc8(int fd, double ra, double dec)
2009{
2010 bool rc;
2011 int racounts, deccounts;
2013
2014 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "slew_pmc8: ra=%f dec=%f", ra, dec);
2015
2016 sop = destSideOfPier(ra, dec);
2017
2018 rc = convert_ra_to_motor(ra, sop, &racounts);
2019 if (!rc)
2020 {
2021 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "slew_pmc8: error converting RA to motor counts");
2022 return false;
2023 }
2024
2025 rc = convert_dec_to_motor(dec, sop, &deccounts);
2026 if (!rc)
2027 {
2028 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "slew_pmc8: error converting DEC to motor counts");
2029 return false;
2030 }
2031
2032 rc = set_pmc8_target_position(fd, racounts, deccounts);
2033
2034 if (!rc)
2035 {
2036 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error slewing PMC8");
2037 return false;
2038 }
2039
2040 if (pmc8_simulation)
2041 {
2043 }
2044
2045 return true;
2046}
2047
2049{
2050 double hour_angle;
2051 double lst;
2052
2054
2056
2057 hour_angle = lst - ra;
2058
2059 // limit values to +/- 12 hours
2060 if (hour_angle > 12)
2061 hour_angle = hour_angle - 24;
2062 else if (hour_angle <= -12)
2063 hour_angle = hour_angle + 24;
2064
2065 // Northern Hemisphere
2066 if (pmc8_east_dir)
2067 {
2068 if (hour_angle < 0.0)
2070 else
2072 }
2073 //Southern Hemisphere
2074 else
2075 {
2076 if (hour_angle < 0.0)
2078 else
2080 }
2081}
2082
2084{
2085 // Northern hemisphere
2086 if (pmc8_east_dir)
2087 {
2088 if (decCounts > 0)
2090 else
2092 }
2093 // Southern hemisphere
2094 else
2095 {
2096 if (decCounts >= 0)
2098 else
2100 }
2101}
2102
2103bool sync_pmc8(int fd, double ra, double dec)
2104{
2105 bool rc;
2106 int racounts, deccounts;
2108
2109 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "sync_pmc8: ra=%f dec=%f", ra, dec);
2110
2111 sop = destSideOfPier(ra, dec);
2112
2113 rc = convert_ra_to_motor(ra, sop, &racounts);
2114 if (!rc)
2115 {
2116 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "sync_pmc8: error converting RA to motor counts");
2117 return false;
2118 }
2119
2120 rc = convert_dec_to_motor(dec, sop, &deccounts);
2121 if (!rc)
2122 {
2123 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "sync_pmc8: error converting DEC to motor counts");
2124 return false;
2125 }
2126
2127 if (pmc8_simulation)
2128 {
2129 // FIXME - need to implement pmc8 sync sim
2130 // strcpy(response, "1");
2131 // nbytes_read = strlen(response);
2132 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Need to implement PMC8 sync simulation");
2133 return false;
2134 }
2135 else
2136 {
2137 rc = set_pmc8_position(fd, racounts, deccounts);
2138 }
2139
2140 if (!rc)
2141 {
2142 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error setting pmc8 position");
2143 return false;
2144 }
2145
2146 return true;
2147}
2148
2149bool set_pmc8_radec(int fd, double ra, double dec)
2150{
2151 bool rc;
2152 int racounts, deccounts;
2154
2155
2156 sop = destSideOfPier(ra, dec);
2157
2158 rc = convert_ra_to_motor(ra, sop, &racounts);
2159 if (!rc)
2160 {
2161 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_radec: error converting RA to motor counts");
2162 return false;
2163 }
2164
2165 rc = convert_dec_to_motor(ra, sop, &deccounts);
2166 if (!rc)
2167 {
2168 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_radec: error converting DEC to motor counts");
2169 return false;
2170 }
2171
2172 if (pmc8_simulation)
2173 {
2174 // FIXME - need to implement pmc8 sync sim
2175 // strcpy(response, "1");
2176 // nbytes_read = strlen(response);
2177 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Need to implement PMC8 sync simulation");
2178 return false;
2179 }
2180 else
2181 {
2182
2183 rc = set_pmc8_target_position(fd, racounts, deccounts);
2184 }
2185
2186 if (!rc)
2187 {
2188 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error setting target position");
2189 return false;
2190 }
2191
2192 return true;
2193}
2194
2196{
2197 int racounts, deccounts;
2198 bool rc;
2199
2200 if (pmc8_simulation)
2201 {
2202 // sortof silly but convert simulated RA/DEC to counts so we can then convert
2203 // back to RA/DEC to test that conversion code
2204 sop = destSideOfPier(simPMC8Data.ra, simPMC8Data.dec);
2205
2206 rc = convert_ra_to_motor(simPMC8Data.ra, sop, &racounts);
2207
2208 if (!rc)
2209 return rc;
2210
2211 rc = convert_dec_to_motor(simPMC8Data.dec, sop, &deccounts);
2212
2213 if (!rc)
2214 return rc;
2215 }
2216 else
2217 {
2218 rc = get_pmc8_position(fd, racounts, deccounts);
2219 sop = currentSideOfPier(deccounts);
2220 }
2221
2222 if (!rc)
2223 {
2224 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Error getting PMC8 motor position");
2225 return false;
2226 }
2227
2228 // convert motor counts to ra/dec
2229 convert_motor_to_radec(racounts, deccounts, ra, dec);
2230
2231 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "ra motor_counts=%d RA = %f", racounts, ra);
2232 // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "dec motor_counts=%d DEC = %f", deccounts, dec);
2233
2234 return rc;
2235}
2236
2237// wrap read commands to PMC8
2238bool get_pmc8_response(int fd, char* buf, int *nbytes_read, const char* expected = NULL )
2239{
2240 int err_code = 1;
2241 int cnt = 0;
2242
2243 //repeat a few times, after that, let's assume we're not getting a response
2244 while ((err_code) && (cnt++ < PMC8_MAX_RETRIES))
2245 {
2246 //Read until exclamation point to get response
2247 if ((err_code = tty_read_section(fd, buf, '!', PMC8_TIMEOUT, nbytes_read)))
2248 {
2249
2250 char errmsg[MAXRBUF];
2251 tty_error_msg(err_code, errmsg, MAXRBUF);
2252
2253 // if we see connection timed out, exit out of here and try to reconnect
2254 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Read error: %s", errmsg);
2255 if (strstr(errmsg, "Connection timed out") || strstr(errmsg, "Bad"))
2256 {
2258 return err_code;
2259 }
2260 }
2261 if (*nbytes_read > 0)
2262 {
2263 buf[*nbytes_read] = '\0';
2264 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "RES %d bytes (%s)", *nbytes_read, buf);
2265
2266 //PMC8 connection is not entirely reliable when using Ethernet instead of Serial connection.
2267 //So, try to compensate for common problems
2269 {
2270 //One problem is we get the string *HELLO* when we connect or disconnect, so discard that
2271 if (buf[0] == '*')
2272 {
2273 strcpy(buf, buf + 7);
2274 *nbytes_read = *nbytes_read - 7;
2275 }
2276 //Another problem is we sometimes get the string AT when we reconnect, so discard that
2277 if (strncmp(buf, "AT", 2) == 0)
2278 {
2279 strcpy(buf, buf + 2);
2280 *nbytes_read = *nbytes_read - 2;
2281 }
2282 //Another problem is random extraneous ESGp! reponses during slew, so when we see those, drop them and try again
2283 if (strncmp(buf, "ESGp!", 5) == 0)
2284 {
2285 err_code = 1;
2286 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Invalid response ESGp!");
2287 }
2288 }
2289 //If a particular response was expected, make sure we got it
2290 if (expected)
2291 {
2292 if (strncmp(buf, expected, strlen(expected)) != 0)
2293 {
2294 err_code = 1;
2295 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_EXTRA_1, "No Match for %s", expected);
2296 }
2297 else
2298 {
2299 DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_EXTRA_1, "Matches %s", expected);
2300 // On rare occasions, there may have been a read error even though it's the response we want, so set err_code explicitly
2301 err_code = 0;
2302 }
2303 }
2304 }
2305 else
2306 {
2308 err_code = 1;
2309 }
2310 }
2311 // if this is our nth consecutive read error, try to reconnect
2312 if (err_code)
2313 {
2315 }
2316 else
2317 {
2319 }
2320 return err_code;
2321}
2322
2323//wrap write commands to pmc8
2324bool send_pmc8_command(int fd, const char *buf, int nbytes, int *nbytes_written)
2325{
2326
2328
2329 tcflush(fd, TCIFLUSH);
2330
2331 int err_code = 1;
2332 //try to reconnect if we see broken pipe error
2333 if ((err_code = tty_write(fd, buf, nbytes, nbytes_written)))
2334 {
2335 char errmsg[MAXRBUF];
2336 tty_error_msg(err_code, errmsg, MAXRBUF);
2337 if (strstr(errmsg, "Broken pipe") || strstr(errmsg, "Bad"))
2338 {
2340 return err_code;
2341 }
2342 }
2343 return err_code;
2344}
2345
2347{
2348 DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Bad connection. Trying to reconnect.");
2349 pmc8_reconnect_flag = true;
2350}
2351
2353{
2355 {
2356 pmc8_reconnect_flag = false;
2357 return true;
2358 }
2359 return false;
2360}
2361
2362void set_pmc8_goto_resume(bool resume)
2363{
2364 pmc8_goto_resume = resume;
2365}
2366
2367// Helper to expose pmc8_east_dir to the high-level driver (pmc8.cpp)
2368// Returns 1 for Northern Hemisphere, 0 for Southern Hemisphere
2370{
2371 return pmc8_east_dir;
2372}
@ ST_STOPPED
@ ST_PARKED
@ ST_SLEWING
#define MAXINDIDEVICE
Definition indiapi.h:191
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 tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sidereal time given longitude and system clock.
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
#define DEBUGDEVICE(device, priority, msg)
Definition indilogger.h:60
#define DEBUGFDEVICE(device, priority, msg,...)
Definition indilogger.h:61
int fd
bool convert_motor_rate_to_move_rate(int mrate, double *rate)
INDI::Telescope::TelescopePierSide currentSideOfPier(int decCounts)
bool get_pmc8_model(int fd, FirmwareInfo *info)
bool get_pmc8_guide_rate(int fd, PMC8_AXIS axis, double &rate)
PMC8Info simPMC8Info
PulseGuideState NS_PulseGuideState
void set_pmc8_location(double latitude, double longitude)
#define PMC8_MAX_IO_ERROR_THRESHOLD
#define PMC8_iEXOS100_AXIS0_SCALE
#define PMC8_RATE_KING
bool set_pmc8_custom_ra_track_rate(int fd, double rate)
double PMC8_AXIS0_SCALE
#define PMC8_RATE_SIDEREAL
uint8_t get_pmc8_tracking_mode_from_rate(double rate)
double pmc8_sidereal_rate_fraction_ra
int raDirection
bool start_pmc8_guide(int fd, PMC8_DIRECTION gdir, int ms, long &timetaken_us, double ratehint)
#define PMC8_RATE_SOLAR
int pmc8_east_dir
bool set_pmc8_target_position_axis(int fd, PMC8_AXIS axis, int point)
bool pmc8_reconnect_flag
#define PMC8_SIMUL_VERSION_RESP
void set_pmc8_sim_system_status(PMC8_SYSTEM_STATUS value)
struct @376 simPMC8Data
bool set_pmc8_custom_dec_track_rate(int fd, double rate)
bool convert_precise_motor_to_rate(int mrate, double *rate)
void set_pmc8_device(const char *name)
bool set_pmc8_axis_move_rate(int fd, PMC8_AXIS axis, float rate)
bool detect_pmc8(int fd)
#define PMC8_RETRY_DELAY
#define PMC8_EXOS2_AXIS0_SCALE
bool park_pmc8(int fd)
bool stop_pmc8_guide(int fd, PMC8_DIRECTION gdir)
double pmc8_longitude
void set_pmc8_debug(bool enable)
bool sync_pmc8(int fd, double ra, double dec)
#define ARCSEC_IN_CIRCLE
void convert_motor_counts_to_hex(int val, char *hex)
bool abort_pmc8_goto(int fd)
double pmc8_sidereal_rate_fraction_de
bool convert_precise_rate_to_motor(double rate, int *mrate)
int convert_axispos_to_motor(int axispos)
bool slew_pmc8(int fd, double ra, double dec)
#define PMC8_PULSE_GUIDE_MIN_MS
double trackRate
bool convert_ra_to_motor(double ra, INDI::Telescope::TelescopePierSide sop, int *mcounts)
bool get_pmc8_response(int fd, char *buf, int *nbytes_read, const char *expected=NULL)
#define PMC8_EXOS2_AXIS1_SCALE
bool get_pmc8_is_scope_slewing(int fd, bool &isslew)
PulseGuideState EW_PulseGuideState
double ra
bool send_pmc8_command(int fd, const char *buf, int nbytes, int *nbytes_written)
bool set_pmc8_track_mode(int fd, uint8_t mode)
bool pmc8_simulation
bool convert_dec_to_motor(double dec, INDI::Telescope::TelescopePierSide sop, int *mcounts)
bool pmc8_debug
char pmc8_device[MAXINDIDEVICE]
bool get_pmc8_position_axis(int fd, PMC8_AXIS axis, int &point)
bool convert_move_rate_to_motor(float rate, int *mrate)
#define PMC8_iEXOS100_AXIS1_SCALE
bool set_pmc8_radec(int fd, double ra, double dec)
bool pmc8_isRev2Compliant
bool set_pmc8_target_position(int fd, int rapoint, int decpoint)
void set_pmc8_mountParameters(int index)
bool pmc8_goto_resume
double PMC8_AXIS1_SCALE
double pmc8_latitude
bool set_pmc8_guide_rate(int fd, PMC8_AXIS axis, double rate)
INDI::Telescope::TelescopePierSide destSideOfPier(double ra, double dec)
#define PMC8_G11_AXIS0_SCALE
bool set_pmc8_axis_motor_rate(int fd, PMC8_AXIS axis, int mrate, bool fast)
void set_pmc8_simulation(bool enable)
bool get_pmc8_coords(int fd, double &ra, double &dec, INDI::Telescope::TelescopePierSide &sop)
#define PMC8_G11_AXIS1_SCALE
void set_pmc8_goto_resume(bool resume)
bool get_pmc8_firmware(int fd, FirmwareInfo *info)
#define PMC8_MAX_RETRIES
bool get_pmc8_main_firmware(int fd, FirmwareInfo *info)
#define PMC8_RATE_LUNAR
#define PMC8_TIMEOUT
bool abort_pmc8(int fd)
bool set_pmc8_position(int fd, int rapoint, int decpoint)
bool set_pmc8_custom_ra_move_rate(int fd, double rate)
bool get_pmc8_reconnect_flag()
bool get_pmc8_guide_state(PMC8_DIRECTION gdir, PulseGuideState **pstate)
PMC8_CONNECTION_TYPE pmc8_connection
bool get_pmc8_tracking_data(int fd, double &rate, uint8_t &mode)
bool check_pmc8_connection(int fd, PMC8_CONNECTION_TYPE connection)
bool convert_motor_to_radec(int racounts, int deccounts, double &ra_value, double &dec_value)
bool set_pmc8_custom_dec_move_rate(int fd, double rate)
bool set_pmc8_direction_axis(int fd, PMC8_AXIS axis, int dir, bool fast)
double guide_rate
bool unpark_pmc8(int fd)
int get_pmc8_east_dir()
int decDirection
double dec
void set_pmc8_sim_move_rate(int value)
bool set_pmc8_position_axis(int fd, PMC8_AXIS axis, int point)
bool stop_pmc8_tracking_motion(int fd)
void set_pmc8_sim_track_rate(PMC8_TRACK_RATE value)
bool get_pmc8_direction_axis(int fd, PMC8_AXIS axis, int &dir)
bool get_pmc8_position(int fd, int &rapoint, int &decpoint)
void set_pmc8_sim_ra(double ra)
int pmc8_io_error_ctr
void set_pmc8_reconnect_flag()
double moveRate
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)
#define PMC8_MAX_PRECISE_MOTOR_RATE
bool get_pmc8_track_rate(int fd, double &rate)
bool get_pmc8_move_rate_axis(int fd, PMC8_AXIS axis, double &rate)
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
#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_RATE
Definition pmc8driver.h:59
@ PMC8_TRACK_KING
Definition pmc8driver.h:59
@ 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_SYSTEM_STATUS
Definition pmc8driver.h:47
PMC8_AXIS
Definition pmc8driver.h:63
@ 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
uint8_t cmd[4]
Definition pwc-ioctl.h:2
std::string Model
PMC8_MOUNT_TYPES MountType
Definition pmc8driver.h:81
std::string MainBoardFirmware
bool IsRev2Compliant
Definition pmc8driver.h:82
PMC8_SYSTEM_STATUS systemStatus
Definition pmc8driver.h:72
long long pulse_start_us
double round(double value, int decimal_places)