Instrument Neutral Distributed Interface INDI 2.2.5
celestrondriver.cpp
Go to the documentation of this file.
1/*
2 Celestron driver
3
4 Copyright (C) 2015 Jasem Mutlaq
5 Copyright (C) 2017 Juan Menendez
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20*/
21
22/*
23 Version with experimental pulse guide support. GC 04.12.2015
24*/
25
26#include "indicom.h"
27#include "indilogger.h"
28#include "celestrondriver.h"
29
30#include <libnova/julian_day.h>
31
32#include <map>
33#include <cstring>
34#include <cmath>
35#include <termios.h>
36#include <unistd.h>
37
38#define CELESTRON_TIMEOUT 5 /* FD timeout in seconds */
39
40using namespace Celestron;
41
42static char device_str[MAXINDIDEVICE] = "Celestron GPS";
43
44// Account for the quadrant in declination
45double Celestron::trimDecAngle(double angle)
46{
47 angle = angle - 360 * floor(angle / 360);
48 if (angle < 0)
49 angle += 360.0;
50
51 if ((angle > 90.) && (angle <= 270.))
52 angle = 180. - angle;
53 else if ((angle > 270.) && (angle <= 360.))
54 angle = angle - 360.;
55
56 return angle;
57}
58
59// Convert decimal degrees to NexStar angle
60uint16_t Celestron::dd2nex(double angle)
61{
62 angle = angle - 360 * floor(angle / 360);
63 if (angle < 0)
64 angle += 360.0;
65
66 return static_cast<uint16_t>(angle * 0x10000 / 360.0);
67}
68
69// Convert decimal degrees to NexStar angle (precise)
70uint32_t Celestron::dd2pnex(double angle)
71{
72 angle = angle - 360 * floor(angle / 360);
73 if (angle < 0)
74 angle += 360.0;
75
76 return static_cast<uint32_t>(angle * 0x100000000 / 360.0);
77}
78
79// Convert NexStar angle to decimal degrees
80double Celestron::nex2dd(uint32_t value)
81{
82 return 360.0 * (static_cast<double>(value) / 0x10000);
83}
84
85// Convert NexStar angle to decimal degrees (precise)
86double Celestron::pnex2dd(uint32_t value)
87{
88 return 360.0 * (static_cast<double>(value) / 0x100000000);
89}
90
91void hex_dump(char *buf, const char *data, size_t size)
92{
93 for (size_t i = 0; i < size; i++)
94 sprintf(buf + 3 * i, "%02X ", data[i]);
95
96 if (size > 0)
97 buf[3 * size - 1] = '\0';
98}
99
100// This method is required by the logging macros
102{
103 return device_str;
104}
105
106void CelestronDriver::set_device(const char *name)
107{
108 snprintf(device_str, MAXINDIDEVICE, "%s", name);
109}
110
111// Virtual method for testing
112int CelestronDriver::serial_write(const char *cmd, int nbytes, int *nbytes_written)
113{
114 tcflush(fd, TCIOFLUSH);
115 return tty_write(fd, cmd, nbytes, nbytes_written);
116}
117
118// Virtual method for testing
119int CelestronDriver::serial_read(int nbytes, int *nbytes_read)
120{
121 return tty_read(fd, response, nbytes, CELESTRON_TIMEOUT, nbytes_read);
122}
123
124// Virtual method for testing
125int CelestronDriver::serial_read_section(char stop_char, int *nbytes_read)
126{
127 return tty_nread_section(fd, response, MAX_RESP_SIZE, stop_char, CELESTRON_TIMEOUT, nbytes_read);
128}
129
130// Set the expected response for a command in simulation mode
131__attribute__((__format__ (__printf__, 2, 0)))
132void CelestronDriver::set_sim_response(const char *fmt, ...)
133{
134 if (simulation)
135 {
136 va_list args;
137 va_start(args, fmt);
138 vsprintf(response, fmt, args);
139 va_end(args);
140 }
141}
142
143//static std::mutex tty_lock;
144
145// Send a command to the mount. Return the number of bytes received or 0 if
146// case of error
147size_t CelestronDriver::send_command(const char *cmd, size_t cmd_len, char *resp,
148 size_t resp_len, bool ascii_cmd, bool ascii_resp)
149{
150 int err;
151 size_t nbytes = resp_len;
152 char errmsg[MAXRBUF];
153 char hexbuf[3 * MAX_RESP_SIZE];
154
155 if (ascii_cmd)
156 LOGF_DEBUG("CMD <%s>", cmd);
157 else
158 {
159 // Non-ASCII commands should be represented as hex strings
160 hex_dump(hexbuf, cmd, cmd_len);
161 LOGF_DEBUG("CMD <%s>", hexbuf);
162 }
163
164 if (!simulation && fd)
165 {
166 // lock the serial command, unlocks when this goes out of scope
167 //std::lock_guard<std::mutex> lockGuard(tty_lock);
168 if ((err = serial_write(cmd, static_cast<int>(cmd_len), reinterpret_cast<int *>(&nbytes))) != TTY_OK)
169 {
170 tty_error_msg(err, errmsg, MAXRBUF);
171 LOGF_ERROR("Serial write error: %s", errmsg);
172 return 0;
173 }
174
175 if (resp_len > 0)
176 {
177 if (ascii_resp)
178 err = serial_read_section('#', reinterpret_cast<int *>(&nbytes));
179 else
180 {
181 err = serial_read(static_cast<int>(resp_len), reinterpret_cast<int *>(&nbytes));
182 // passthrough commands that fail will return an extra 0 then the terminator
183 while (err == TTY_OK && resp[nbytes - 1] != '#')
184 {
185 char m[1];
186 int n;
187 err = tty_read(fd, m, 1, CELESTRON_TIMEOUT, &n);
188 if (n == 1)
189 {
190 resp[nbytes] = m[0];
191 nbytes++;
192 }
193 }
194 }
195 if (err)
196 {
197 tty_error_msg(err, errmsg, MAXRBUF);
198 LOGF_ERROR("Serial read error: %s", errmsg);
199 return 0;
200 }
201 }
202 }
203
204 if (nbytes != resp_len)
205 {
206 size_t max = nbytes > resp_len ? nbytes : resp_len;
207 hex_dump(hexbuf, resp, max);
208 LOGF_DEBUG("Received %d bytes, expected %d <%s>", nbytes, resp_len, hexbuf);
209 return max;
210 }
211
212 if (resp_len == 0)
213 {
214 LOG_DEBUG("resp_len 0, no response expected");
215 return true;
216 }
217
218 resp[nbytes] = '\0';
219
220 if (ascii_resp)
221 LOGF_DEBUG("RES <%s>", resp);
222 else
223 {
224 // Non-ASCII commands should be represented as hex strings
225 hex_dump(hexbuf, resp, resp_len);
226 LOGF_DEBUG("RES <%s>", hexbuf);
227 }
228
229 return nbytes;
230}
231
232// Send a 'passthrough command' to the mount. Return the number of bytes
233// received or 0 in case of error
234size_t CelestronDriver::send_passthrough(int dest, int cmd_id, const char *payload,
235 size_t payload_len, char *resp, size_t response_len)
236{
237 char cmd[8] = {0};
238
239 cmd[0] = 0x50;
240 cmd[1] = static_cast<char>(payload_len + 1);
241 cmd[2] = static_cast<char>(dest);
242 cmd[3] = static_cast<char>(cmd_id);
243 cmd[7] = static_cast<char>(response_len);
244
245 // payload_len must be <= 3 !
246 memcpy(cmd + 4, payload, payload_len);
247
248 return send_command(cmd, 8, resp, response_len + 1, false, false);
249}
250
252{
253 LOG_DEBUG("Initializing Celestron using Kx CMD...");
254
255 for (int i = 0; i < 2; i++)
256 {
257 if (echo())
258 return true;
259
260 usleep(50000);
261 }
262
263 return false;
264}
265
267{
268 char version[8], model[16], RAVersion[8], DEVersion[8];
269 bool isGem;
270 bool canPec;
271 bool hasHomeIndex;
272
273 LOG_DEBUG("Getting controller version...");
274 if (!get_version(version, 8))
275 return false;
276 info->Version = version;
277 info->controllerVersion = atof(version);
278
279 LOG_DEBUG("Getting controller variant...");
281 // variant is only available for NexStar + versions 5.28 or more and Starsense.
282 // StarSense versions are currently 1.9 so overlap the early NexStar versions.
283 // NS HCs before 2.0 will test and timeout
284 if (info->controllerVersion < 2.2 || info->controllerVersion >= 5.28)
285 {
287 }
288
289 if (((info->controllerVariant == ISSTARSENSE) &&
291 (info->controllerVersion >= 2.2))
292 {
293 LOG_DEBUG("Getting controller model...");
294 if (!get_model(model, 16, &isGem, &canPec, &hasHomeIndex))
295 return false;
296 info->Model = model;
297 info->isGem = isGem;
298 info->canPec = canPec;
299 info->hasHomeIndex = hasHomeIndex;
300 }
301 else
302 {
303 info->Model = "Unknown";
304 info->isGem = false;
305 info->canPec = false;
306 info->hasHomeIndex = false;
307 }
308
309 //LOG_DEBUG("Getting GPS firmware version...");
310 // char GPSVersion[8];
311 //if (!get_dev_firmware(CELESTRON_DEV_GPS, GPSVersion, 8))
312 //return false;
313 //info->GPSFirmware = GPSVersion;
314 //info->GPSFirmware = "0.0";
315
316 LOG_DEBUG("Getting RA firmware version...");
317 if (!get_dev_firmware(CELESTRON_DEV_RA, RAVersion, 8))
318 return false;
319 info->RAFirmware = RAVersion;
320
321 LOG_DEBUG("Getting DEC firmware version...");
322 if (!get_dev_firmware(CELESTRON_DEV_DEC, DEVersion, 8))
323 return false;
324 info->DEFirmware = DEVersion;
325
326
327 LOG_DEBUG("Getting focuser version...");
328 info->hasFocuser = foc_exists();
329
330 LOGF_DEBUG("Firmware Info HC Ver %s model %s %s %s mount, HW Ver %s",
331 info->Version.c_str(),
332 info->Model.c_str(),
333 info->controllerVariant == ISSTARSENSE ? "StarSense" : "NexStar",
334 info->isGem ? "GEM" : "Fork",
335 info->RAFirmware.c_str());
336
337 return true;
338}
339
341{
342 set_sim_response("x#");
343
344 if (!send_command("Kx", 2, response, 2, true, true))
345 return false;
346
347 return !strcmp(response, "x#");
348}
349
350bool CelestronDriver::get_version(char *version, size_t size)
351{
352 set_sim_response("\x04\x29#");
353
354 if (!send_command("V", 1, response, 3, true, false))
355 return false;
356
357 // Versions up to 2.2 have a single-digit minor version
358 if (static_cast<uint8_t>(response[0]) > 2)
359 snprintf(version, size, "%d.%02d", static_cast<uint8_t>(response[0]), static_cast<uint8_t>(response[1]));
360 else
361 snprintf(version, size, "%d.%d", static_cast<uint8_t>(response[0]), static_cast<uint8_t>(response[1]));
362
363 LOGF_INFO("Controller version: %s", version);
364 return true;
365}
366
367//TODO: no critical errors for this command
369{
370 set_sim_response("\x11#");
371
372 if (!send_command("v", 1, response, 2, true, false))
373 return false;
374
375 *variant = static_cast<uint8_t>(response[0]);
376 return true;
377}
378
380{
381 set_sim_response("%c#", 20); // AVX
382 if (!send_command("m", 1, response, 2, true, false))
383 return -1;
384
385 return static_cast<uint8_t>(response[0]);
386}
387
388bool CelestronDriver::get_model(char *model, size_t size, bool *isGem, bool *canPec, bool *hasHomeIndex)
389{
390 // extended list of mounts
391 std::map<int, std::string> models =
392 {
393 {1, "GPS Series"},
394 {3, "i-Series"},
395 {4, "i-Series SE"},
396 {5, "CGE"},
397 {6, "Advanced GT"},
398 {7, "SLT"},
399 {9, "CPC"},
400 {10, "GT"},
401 {11, "4/5 SE"},
402 {12, "6/8 SE"},
403 {13, "CGE Pro"},
404 {14, "CGEM DX"},
405 {15, "LCM"},
406 {16, "Sky Prodigy"},
407 {17, "CPC Deluxe"},
408 {18, "GT 16"},
409 {19, "StarSeeker"},
410 {20, "AVX"},
411 {21, "Cosmos"},
412 {22, "Evolution"},
413 {23, "CGX"},
414 {24, "CGXL"},
415 {25, "Astrofi"},
416 {26, "SkyWatcher"},
417 };
418
419 set_sim_response("\x14#"); // Simulated response, AVX
420
421
422 int m = CelestronDriver::model();
423 if (m < 0)
424 return false;
425
426 if (models.find(m) != models.end())
427 {
428 snprintf(model, size, "%s", models[m].c_str());
429 LOGF_INFO("Mount model: %s", model);
430 }
431 else
432 {
433 snprintf(model, size, "%s", "Unknown");
434 LOGF_WARN("Unrecognized model (%d).", model);
435 }
436
437 // use model# to detect the GEMs amd if PEC can be done
438 // Only Gem mounts can report the pier side pointing state
439 switch(m)
440 {
441 // fork mounts with PEC index
442 case 1: // GPS
443 case 9: // CPC
444 case 17: // CPC Deluxe
445 case 22: // Evolution
446 *isGem = false;
447 *canPec = true;
448 break;
449
450 // GEM with no PEC index
451 case 6: // AS-GT
452 *isGem = true;
453 *canPec = false;
454 break;
455
456 // GEM with PEC
457 case 5: // CGE
458 case 13: // CGE 2
459 case 14: // EQ6
460 case 20: // AVX
461 case 23: // CGX
462 case 24: // CGXL
463 *isGem = true;
464 *canPec = true;
465 break;
466
467 // the rest are fork mounte with no PEC
468 default:
469 *isGem = false;
470 *canPec = false;
471 break;
472 }
473
474 // CGX and CGX-L mounts have home indexes
475 *hasHomeIndex = (m == 23 || m == 24);
476
477 LOGF_DEBUG("get_model %s, %s mount, %s, %s", model, *isGem ? "GEM" : "Fork", *canPec ? "has PEC" : "no PEC",
478 *hasHomeIndex ? "has home indexes" : "no home indexes");
479
480 return true;
481}
482
483bool CelestronDriver::get_dev_firmware(int dev, char *version, size_t size)
484{
485 set_sim_response("\x06\x10#");
486
487 size_t rlen = send_passthrough(dev, GET_VER, nullptr, 0, response, 2);
488
489 switch (rlen)
490 {
491 case 2:
492 snprintf(version, size, "%01d.0", static_cast<uint8_t>(response[0]));
493 break;
494 case 3:
495 snprintf(version, size, "%d.%02d", static_cast<uint8_t>(response[0]), static_cast<uint8_t>(response[1]));
496 break;
497 default:
498 return false;
499 }
500
501 return true;
502}
503
504/*****************************************************************
505 PulseGuide commands
506******************************************************************/
507
508/*****************************************************************
509 Send a guiding pulse to the mount in direction "dir".
510 "rate" should be an unsigned 8-bit integer in the range (0,100) that
511 represents the pulse velocity in % of sidereal.
512 "duration_csec" is an unsigned 8-bit integer (0,255) with the pulse
513 duration in centiseconds (i.e. 1/100 s = 10ms).
514 The max pulse duration is 2550 ms.
515*******************************************)()***********************/
516size_t CelestronDriver::send_pulse(CELESTRON_DIRECTION dir, unsigned char rate, unsigned char duration_csec)
517{
518 char payload[2];
519 int dev = CELESTRON_DEV_RA;
520 switch (dir)
521 {
522 case CELESTRON_N:
523 dev = CELESTRON_DEV_DEC;
524 payload[0] = rate;
525 break;
526 case CELESTRON_S:
527 dev = CELESTRON_DEV_DEC;
528 payload[0] = -rate;
529 break;
530 case CELESTRON_W:
531 dev = CELESTRON_DEV_RA;
532 payload[0] = rate;
533 break;
534 case CELESTRON_E:
535 dev = CELESTRON_DEV_RA;
536 payload[0] = -rate;
537 break;
538 }
539 payload[1] = duration_csec;
540
541 set_sim_response("#");
542 return send_passthrough(dev, MTR_AUX_GUIDE, payload, 2, response, 0);
543}
544
545/*****************************************************************
546 Send the guiding pulse status check command to the mount for the motor
547 responsible for "dir". If a pulse is being executed, returns true,
548 otherwise false.
549 If the getting the status fails returns false.
550******************************************************************/
552{
553 int dev = CELESTRON_DEV_RA;
554 //char payload[2] = {0, 0};
555 switch (dir)
556 {
557 case CELESTRON_N:
558 case CELESTRON_S:
559 dev = CELESTRON_DEV_DEC;
560 break;
561 case CELESTRON_W:
562 case CELESTRON_E:
563 dev = CELESTRON_DEV_RA;
564 break;
565 }
566 set_sim_response("%c#", 0);
567
568 if (!send_passthrough(dev, MTR_IS_AUX_GUIDE_ACTIVE, nullptr, 0, response, 1))
569 return false;
570
571 return static_cast<bool>(response[0]);
572}
573
574/*****************************************************************
575 Get the guide rate from the mount for the axis.
576 rate is 0 to 255 representing 0 to 100% sidereal
577 If getting the rate fails returns false.
578******************************************************************/
580{
581 int dev = CELESTRON_DEV_DEC;
582 switch (axis)
583 {
585 dev = CELESTRON_DEV_RA;
587 break;
589 dev = CELESTRON_DEV_DEC;
591 break;
592 }
593 if (!send_passthrough(dev, MC_GET_AUTOGUIDE_RATE, nullptr, 0, response, 1))
594 return false;
595 *rate = static_cast<uint8_t>(response[0]);
596 LOGF_DEBUG("get_guide_rate raw response (0-255) %i", *rate);
597 return true;
598}
599
600/*****************************************************************
601 Set the guide rate for the axis.
602 rate is 0 to 255 representing 0 to 100% sidereal
603 If setting the rate fails returns false.
604******************************************************************/
606{
607 int dev = CELESTRON_DEV_DEC;
608 switch (axis)
609 {
611 dev = CELESTRON_DEV_RA;
612 sim_ra_guide_rate = rate;
613 break;
615 dev = CELESTRON_DEV_DEC;
616 sim_dec_guide_rate = rate;
617 break;
618 }
619 char payload[1];
620 payload[0] = rate;
621 set_sim_response("#");
622 return send_passthrough(dev, MC_SET_AUTOGUIDE_RATE, payload, 1, response, 0);
623}
624
626{
627 int dev = (dir == CELESTRON_N || dir == CELESTRON_S) ? CELESTRON_DEV_DEC : CELESTRON_DEV_RA;
628 int cmd_id = (dir == CELESTRON_N || dir == CELESTRON_W) ? MC_MOVE_POS : MC_MOVE_NEG;
629 char payload[1];
630 payload[0] = rate + 1;
631
632 set_sim_response("#");
633 return send_passthrough(dev, cmd_id, payload, 1, response, 0);
634}
635
637{
638 int dev = (dir == CELESTRON_N || dir == CELESTRON_S) ? CELESTRON_DEV_DEC : CELESTRON_DEV_RA;
639 char payload[] = { 0 };
640
641 set_sim_response("#");
642 return send_passthrough(dev, MC_MOVE_POS, payload, 1, response, 0);
643}
644
646{
647 set_sim_response("#");
648 return send_command("M", 1, response, 1, true, true);
649}
650
651bool CelestronDriver::slew_radec(double ra, double dec, bool precise)
652{
653 char RAStr[16], DecStr[16];
654 fs_sexa(RAStr, ra, 2, 3600);
655 fs_sexa(DecStr, dec, 2, 3600);
656
657 LOGF_DEBUG("Goto RA-DEC(%s,%s)", RAStr, DecStr);
658
659 set_sim_slewing(true);
660
661 char cmd[20];
662 if (precise)
663 sprintf(cmd, "r%08X,%08X", dd2pnex(ra * 15), dd2pnex(dec));
664 else
665 sprintf(cmd, "R%04X,%04X", dd2nex(ra * 15), dd2nex(dec));
666
667 set_sim_response("#");
668 return send_command(cmd, strlen(cmd), response, 1, true, true);
669}
670
671bool CelestronDriver::slew_azalt(double az, double alt, bool precise)
672{
673 char AzStr[16], AltStr[16];
674 fs_sexa(AzStr, az, 3, 3600);
675 fs_sexa(AltStr, alt, 2, 3600);
676
677 LOGF_DEBUG("Goto AZM-ALT (%s,%s)", AzStr, AltStr);
678
679 set_sim_slewing(true);
680
681 char cmd[20];
682 if (precise)
683 sprintf(cmd, "b%08X,%08X", dd2pnex(az), dd2pnex(alt));
684 else
685 sprintf(cmd, "B%04X,%04X", dd2nex(az), dd2nex(alt));
686
687 set_sim_response("#");
688 return send_command(cmd, strlen(cmd), response, 1, true, true);
689}
690
691bool CelestronDriver::sync(double ra, double dec, bool precise)
692{
693 char RAStr[16], DecStr[16];
694 fs_sexa(RAStr, ra, 2, 3600);
695 fs_sexa(DecStr, dec, 2, 3600);
696
697 LOGF_DEBUG("Sync (%s,%s)", RAStr, DecStr);
698
699 sim_data.ra = ra;
700 sim_data.dec = dec;
701
702 char cmd[20];
703 if (precise)
704 sprintf(cmd, "s%08X,%08X", dd2pnex(ra * 15), dd2pnex(dec));
705 else
706 sprintf(cmd, "S%04X,%04X", dd2nex(ra * 15), dd2nex(dec));
707
708 set_sim_response("#");
709 return send_command(cmd, strlen(cmd), response, 1, true, true);
710}
711
712// NS+ 5.28 and more only, not StarSense
714{
715 LOG_DEBUG("Unsync");
716 set_sim_response("#");
717 return send_command("u", 1, response, 1, true, true);
718}
719
720void parseCoordsResponse(char *response, double *d1, double *d2, bool precise)
721{
722 uint32_t d1_int = 0, d2_int = 0;
723
724 sscanf(response, "%x,%x#", &d1_int, &d2_int);
725
726 if (precise)
727 {
728 *d1 = pnex2dd(d1_int);
729 *d2 = pnex2dd(d2_int);
730 }
731 else
732 {
733 *d1 = nex2dd(d1_int);
734 *d2 = nex2dd(d2_int);
735 }
736}
737
738bool CelestronDriver::get_radec(double *ra, double *dec, bool precise)
739{
740 if (precise)
741 {
742 set_sim_response("%08X,%08X#", dd2pnex(sim_data.ra * 15), dd2pnex(sim_data.dec));
743
744 if (!send_command("e", 1, response, 18, true, true))
745 return false;
746 }
747 else
748 {
749 set_sim_response("%04X,%04X#", dd2nex(sim_data.ra * 15), dd2nex(sim_data.dec));
750
751 if (!send_command("E", 1, response, 10, true, true))
752 return false;
753 }
754
756 *ra /= 15.0;
757 *dec = trimDecAngle(*dec);
758
759 char RAStr[16], DecStr[16];
760 fs_sexa(RAStr, *ra, 2, 3600);
761 fs_sexa(DecStr, *dec, 2, 3600);
762
763 LOGF_EXTRA1("RA-DEC (%s,%s)", RAStr, DecStr);
764 return true;
765}
766
767bool CelestronDriver::get_azalt(double *az, double *alt, bool precise)
768{
769 if (precise)
770 {
772
773 if (!send_command("z", 1, response, 18, true, true))
774 return false;
775 }
776 else
777 {
779
780 if (!send_command("Z", 1, response, 10, true, true))
781 return false;
782 }
783
784 parseCoordsResponse(response, az, alt, precise);
785
786 char AzStr[16], AltStr[16];
787 fs_sexa(AzStr, *az, 3, 3600);
788 fs_sexa(AltStr, *alt, 2, 3600);
789 LOGF_EXTRA1("RES <%s> ==> AZM-ALT (%s,%s)", response, AzStr, AltStr);
790 return true;
791}
792
793bool CelestronDriver::set_location(double longitude, double latitude)
794{
795 LOGF_DEBUG("Setting location (%.3f,%.3f)", longitude, latitude);
796
797 // Convert from INDI standard to regular east/west -180 to 180
798 if (longitude > 180)
799 longitude -= 360;
800
801 int lat_d, lat_m, lat_s;
802 int long_d, long_m, long_s;
803 getSexComponents(latitude, &lat_d, &lat_m, &lat_s);
804 getSexComponents(longitude, &long_d, &long_m, &long_s);
805
806 char cmd[9];
807 cmd[0] = 'W';
808 cmd[1] = static_cast<char>(abs(lat_d));
809 cmd[2] = static_cast<char>(lat_m);
810 cmd[3] = static_cast<char>(lat_s);
811 cmd[4] = lat_d > 0 ? 0 : 1;
812 cmd[5] = static_cast<char>(abs(
813 long_d)); // not sure how the conversion from int to char will work for longtitudes > 127
814 cmd[6] = static_cast<char>(long_m);
815 cmd[7] = static_cast<char>(long_s);
816 cmd[8] = longitude > 0 ? 0 : 1; //Error fixed here (was cmd[8] = long_d >= 0 ? 0 : 1;) which was wrong for < 1 degree East.
817
818 set_sim_response("#");
819 return send_command(cmd, 9, response, 1, false, true);
820}
821
822bool CelestronDriver::get_location(double *longitude, double *latitude)
823{
824 // Simulated response (lat_d lat_m lat_s N|S long_d long_m long_s E|W)
825 set_sim_response("%c%c%c%c%c%c%c%c#", 51, 36, 17, 0, 0, 43, 3, 1);
826
827 if (!send_command("w", 1, response, 9, true, false))
828 return false;
829
830 *latitude = response[0];
831 *latitude += response[1] / 60.0;
832 *latitude += response[2] / 3600.0;
833 if (response[3] != 0)
834 *latitude = -*latitude;
835
836 *longitude = response[4];
837 *longitude += response[5] / 60.0;
838 *longitude += response[6] / 3600.0;
839 if(response[7] != 0)
840 *longitude = -*longitude;
841
842 // convert longitude to INDI range 0 to 359.999
843 if (*longitude < 0)
844 *longitude += 360.0;
845
846 return true;
847}
848
849// there are newer time commands that have the utc offset in 15 minute increments
850bool CelestronDriver::set_datetime(struct ln_date *utc, double utc_offset, bool dst, bool precise)
851{
852 struct ln_zonedate local_date;
853
854 // Celestron takes local time and DST but ln_zonedate doesn't have DST
855 ln_date_to_zonedate(utc, &local_date, static_cast<int>(utc_offset * 3600));
856
857 char cmd[9];
858 cmd[0] = 'H';
859 cmd[1] = static_cast<char>(local_date.hours);
860 cmd[2] = static_cast<char>(local_date.minutes);
861 cmd[3] = static_cast<char>(local_date.seconds);
862 cmd[4] = static_cast<char>(local_date.months);
863 cmd[5] = static_cast<char>(local_date.days);
864 cmd[6] = static_cast<char>(local_date.years - 2000);
865
866 int utc_int = static_cast<int>(utc_offset);
867
868 // changes for HC versions that support the high precision time zone
869 if(precise)
870 {
871 cmd[0] = 'I';
872 utc_int *= 4;
873 }
874
875 cmd[7] = static_cast<char>(utc_int & 0xFF);
876
877 // set dst
878 cmd[8] = dst ? 1 : 0;
879
880 set_sim_response("#");
881 return send_command(cmd, 9, response, 1, false, true);
882}
883
884bool CelestronDriver::get_utc_date_time(double *utc_hours, int *yy, int *mm,
885 int *dd, int *hh, int *minute, int *ss, bool* dst, bool precise)
886{
887 // Simulated response (HH MM SS MONTH DAY YEAR OFFSET DAYLIGHT)
888 // 2015-04-01T17:30:10 tz +3 dst 0
889 //set_sim_response("%c%c%c%c%c%c%c%c#", 17, 30, 10, 4, 1, 15, 3, 0);
890 // use current system time for simulator
891 time_t now = time(nullptr);
892 tm *ltm = localtime(&now);
893
894 set_sim_response("%c%c%c%c%c%c%c%c#",
895 ltm->tm_hour, ltm->tm_min, ltm->tm_sec,
896 ltm->tm_mon, ltm->tm_mday, ltm->tm_year - 100,
897 precise ? ltm->tm_gmtoff / 900 : ltm->tm_gmtoff / 3600, ltm->tm_isdst);
898
899 // the precise time reader reports the time zone in 15 minute steps
900
901 // read the local time from the HC
902 if (!send_command(precise ? "i" : "h", 1, response, 9, true, false))
903 return false;
904
905 // Celestron returns local time, offset and DST
906 ln_zonedate localTime;
907 ln_date utcTime;
908
909 // HH MM SS MONTH DAY YEAR OFFSET DAYLIGHT
910 localTime.hours = response[0];
911 localTime.minutes = response[1];
912 localTime.seconds = response[2];
913 localTime.months = response[3];
914 localTime.days = response[4];
915 localTime.years = 2000 + response[5];
916 int gmtoff = response[6];
917 *dst = response[7] != 0;
918
919 // make gmtoff signed
920 if (gmtoff > 50)
921 gmtoff -= 256;
922
923 // precise returns offset in 15 minute steps
924 if (precise)
925 {
926 *utc_hours = gmtoff / 4;
927 localTime.gmtoff = gmtoff * 900;
928 }
929 else
930 {
931 *utc_hours = gmtoff;
932 localTime.gmtoff = gmtoff * 3600;
933 }
934
935 if (*dst)
936 {
937 *utc_hours += 1;
938 localTime.gmtoff += 3600;
939 }
940
941 // LOGF_DEBUG("LT %d-%d-%dT%d:%d:%f, gmtoff %d, dst %s",
942 // localTime.years, localTime.months, localTime.days, localTime.hours, localTime.minutes, localTime.seconds,
943 // localTime.gmtoff, *dst ? "On" : "Off");
944
945 // convert to UTC
946 ln_zonedate_to_date(&localTime, &utcTime);
947
948 *yy = utcTime.years;
949 *mm = utcTime.months;
950 *dd = utcTime.days;
951 *hh = utcTime.hours;
952 *minute = utcTime.minutes;
953 *ss = static_cast<int>(utcTime.seconds);
954
955 // LOGF_DEBUG("UTC %d-%d-%dT%d:%d:%d utc_hours %f", *yy, *mm, *dd, *hh, *minute, *ss, *utc_hours);
956
957 return true;
958}
959
961{
963
964 if (!send_command("L", 1, response, 2, true, true))
965 return false;
966
967 *slewing = response[0] != '0';
968 return true;
969}
970
972{
973 set_sim_response("\02#");
974
975 if (!send_command("t", 1, response, 2, true, false))
976 return false;
977
978 *mode = static_cast<CELESTRON_TRACK_MODE>(response[0]);
979 return true;
980}
981
983{
984 char cmd[3];
985 sprintf(cmd, "T%c", mode);
986 set_sim_response("#");
987
988 return send_command(cmd, 2, response, 1, false, true);
989}
990
992{
993 set_sim_response("#");
994 return send_command("x", 1, response, 1, true, true);
995}
996
997// wakeup the mount
999{
1000 set_sim_response("#");
1001 return send_command("y", 1, response, 1, true, true);
1002}
1003
1004// do a last align, assumes the mount is at the index position
1006{
1007 set_sim_response("#");
1008 return send_command("Y", 1, response, 1, true, true);
1009}
1010
1012{
1014 return false;
1016}
1017
1019{
1021 return false;
1022 bool atDecIndex = response[0] != '\00';
1024 return false;
1025 bool atRaIndex = response[0] != '\00';
1026 *atIndex = atDecIndex && atRaIndex;
1027 return true;
1028}
1029
1030// Get pier side command, returns 'E' or 'W'
1031bool CelestronDriver:: get_pier_side(char *side_of_pier)
1032{
1033 set_sim_response("W#");
1034
1035 if (!send_command("p", 1, response, 2, true, true))
1036 return false;
1037 *side_of_pier = response[0];
1038
1039 return true;
1040}
1041
1042// check if the mount is aligned using the mount J command
1044{
1045 // returns 0x01 or 0x00
1046 set_sim_response("\x01#");
1047 if (!send_command("J", 1, response, 2, true, false))
1048 return false;
1049
1050 *isAligned = response[0] == 0x01;
1051 return true;
1052}
1053
1055{
1056 set_sim_response("#");
1057 char cmd;
1058 switch (mode)
1059 {
1060 case CTM_EQN:
1062 break;
1063 case CTM_EQS:
1065 break;
1066 default:
1067 return false;
1068 }
1069 char payload[] = {static_cast<char>(rate >> 8 & 0xff), static_cast<char>(rate & 0xff)};
1070 return send_passthrough(CELESTRON_DEV_RA, cmd, payload, 2, response, 0);
1071}
1072
1073// focuser commands
1074
1076{
1077 char focVersion[16];
1078 int vernum = 0; // version as a number: 0xMMmmbbbb
1079 LOG_DEBUG("Does focuser exist...");
1080 size_t rlen = send_passthrough(CELESTRON_DEV_FOC, GET_VER, nullptr, 0, response, 4);
1081 switch (rlen)
1082 {
1083 case 2:
1084 case 3:
1085 snprintf(focVersion, 15, "%d.%02d", static_cast<uint8_t>(response[0]), static_cast<uint8_t>(response[1]));
1086 vernum = (static_cast<uint8_t>(response[0]) << 24) + (static_cast<uint8_t>(response[1]) << 16);
1087 break;
1088 case 4:
1089 case 5:
1090 snprintf(focVersion, 15, "%d.%02d.%d",
1091 static_cast<uint8_t>(response[0]), static_cast<uint8_t>(response[1]),
1092 static_cast<int>((static_cast<uint8_t>(response[2]) << 8) + static_cast<uint8_t>(response[3])));
1093 vernum = (static_cast<uint8_t>(response[0]) << 24) + (static_cast<uint8_t>(response[1]) << 16) + (static_cast<uint8_t>
1094 (response[2]) << 8) + static_cast<uint8_t>(response[3]);
1095 break;
1096 default:
1097 LOGF_DEBUG("No focuser found, %i", echo());
1098 return false;
1099 }
1100
1101 LOGF_DEBUG("Focuser Version %s, exists %s", focVersion, vernum != 0 ? "true" : "false");
1102 return vernum != 0;
1103}
1104
1106{
1107 if (simulation)
1108 {
1109 int offset = get_sim_foc_offset();
1110 if (offset > 250)
1111 move_sim_foc(250);
1112 else if (offset < -250)
1113 move_sim_foc(-250);
1114 else
1115 move_sim_foc(offset);
1116 }
1117 set_sim_response("%c%c%c#", sim_data.foc_position >> 16 & 0xff, sim_data.foc_position >> 8 & 0XFF,
1118 sim_data.foc_position & 0XFF);
1119
1120 size_t rlen = send_passthrough(CELESTRON_DEV_FOC, MC_GET_POSITION, nullptr, 0, response, 3);
1121 if (rlen >= 3)
1122 {
1123 int pos = (static_cast<uint8_t>(response[0]) << 16) + (static_cast<uint8_t>(response[1]) << 8) + static_cast<uint8_t>
1124 (response[2]);
1125 LOGF_DEBUG("get focus position %d", pos);
1126 return pos;
1127 }
1128 LOG_DEBUG("get Focus position fail");
1129 return -1;
1130}
1131
1132bool CelestronDriver::foc_move(uint32_t steps)
1133{
1134 sim_data.foc_target = steps;
1135 LOGF_DEBUG("Focus move %d", steps);
1136 char payload[] = {static_cast<char>(steps >> 16 & 0xff), static_cast<char>(steps >> 8 & 0xff), static_cast<char>(steps & 0xff)};
1137 set_sim_response("#");
1138 size_t rlen = send_passthrough(CELESTRON_DEV_FOC, MC_GOTO_FAST, payload, 3, response, 0);
1139 return rlen > 0;
1140}
1141
1143{
1144 set_sim_response("%c#", sim_data.foc_target == sim_data.foc_position ? 0xff : 0x00);
1145 size_t rlen = send_passthrough(CELESTRON_DEV_FOC, MC_SLEW_DONE, nullptr, 0, response, 1);
1146 if (rlen < 1 )
1147 return false;
1148 return response[0] != '\xff'; // use char comparison because some compilers object
1149}
1150
1151bool CelestronDriver::foc_limits(int * low, int * high)
1152{
1153 set_sim_response("%c%c%c%c%c%c%c%c#", 0, 0, 0x07, 0xd0, 0, 0, 0x9C, 0x40); // 2000, 40000
1154
1155 size_t rlen = send_passthrough(CELESTRON_DEV_FOC, FOC_GET_HS_POSITIONS, nullptr, 0, response, 8);
1156 if (rlen < 8)
1157 return false;
1158
1159 *low = (static_cast<uint8_t>(response[0]) << 24) + (static_cast<uint8_t>(response[1]) << 16) + (static_cast<uint8_t>
1160 (response[2]) << 8) + static_cast<uint8_t>(response[3]);
1161 *high = (static_cast<uint8_t>(response[4]) << 24) + (static_cast<uint8_t>(response[5]) << 16) + (static_cast<uint8_t>
1162 (response[6]) << 8) + static_cast<uint8_t>(response[7]);
1163
1164 // check on integrity of values, they must be sensible and the range must be more than 2 turns
1165 if (*high - *low < 2000 || *high < 0 || *high > 60000 || *low < 0 || *low > 50000)
1166 {
1167 LOGF_INFO("Focus range %i to %i invalid, range not updated", *high, *low);
1168 return false;
1169 }
1170
1171 LOGF_DEBUG("Focus Limits: Maximum (%i) Minimum (%i)", *high, *low);
1172 return true;
1173}
1174
1176{
1177 if(simulation)
1178 {
1180 }
1181 set_sim_response("#");
1182
1183 char payload[] = {0};
1184 size_t rlen = send_passthrough(CELESTRON_DEV_FOC, MC_MOVE_POS, payload, 1, response, 0);
1185 return rlen > 0;
1186}
1187
1189// PEC Handling
1191
1193{
1195 {
1196 LOG_DEBUG("PecSeekIndex - already found");
1197 return true;
1198 }
1199
1200 set_sim_response("#");
1201
1202 size_t rlen = send_passthrough(CELESTRON_DEV_RA, MC_SEEK_INDEX, nullptr, 0, response, 0);
1203 if (rlen < 1)
1204 {
1205 LOG_WARN("Start PEC seek index failed");
1206 return false;
1207 }
1208
1210
1211 simSeekIndex = true;
1212
1213 LOGF_DEBUG("PecSeekIndex %s", PecStateStr());
1214
1215 return true;
1216}
1217
1219{
1221 return false;
1222
1223 if (!force && pecState >= PEC_STATE::PEC_INDEXED)
1224 return true;
1225
1226 set_sim_response("%c#", simSeekIndex ? 0xFF : 0x00);
1227
1228 size_t rlen = send_passthrough(CELESTRON_DEV_RA, MC_AT_INDEX, nullptr, 0, response, 1);
1229 if (rlen < 1)
1230 return false;
1231
1232 bool indexed = (response[0] == '\xFF');
1233 // update the local PEC state
1234 if (indexed && pecState <= PEC_STATE::PEC_INDEXED)
1235 {
1237 LOG_INFO("PEC Index Found");
1238 }
1239
1240 LOGF_DEBUG("isPecAtIndex? %s", indexed ? "yes" : "no");
1241
1242 return indexed;
1243}
1244
1246{
1248 {
1249 LOG_DEBUG("getPecNumBins - PEC not available");
1250 return 0;
1251 }
1252 set_sim_response("%c#", 88);
1253 char payload[] = { 0x3F };
1254 size_t rlen = send_passthrough(CELESTRON_DEV_RA, MC_PEC_READ_DATA, payload, 1, response, 1);
1255 if (rlen < 1)
1256 return 0;
1257
1258 size_t numPecBins = response[0];
1259 LOGF_DEBUG("getPecNumBins %d", numPecBins);
1260 return numPecBins;
1261}
1262
1264{
1265 if (simulation)
1266 {
1267 // increment the index each time we read it. Timing will be too fast, a good thing!
1268 simIndex++;
1269 if (simIndex >= 88)
1270 simIndex = 0;
1271 }
1272 set_sim_response("%c#", simIndex);
1273
1274 size_t rlen = send_passthrough(CELESTRON_DEV_RA, MTR_PECBIN, nullptr, 0, response, 1);
1275 if (rlen < 1)
1276 return 0;
1277
1278 return response[0];
1279}
1280
1282{
1284 return false;
1285 char data[1];
1286 data[0] = start ? 0x01 : 0x00;
1287
1288 set_sim_response("#");
1289
1290 size_t rlen = send_passthrough(CELESTRON_DEV_RA, MC_PEC_PLAYBACK, data, 1, response, 0);
1291 if (rlen <= 0)
1292 {
1293 LOGF_WARN("PEC Playback %s failed", start ? "start" : "stop");
1294 return false;
1295 }
1296
1297 // we can't read the PEC state so use the start state to set it
1299
1300 LOGF_DEBUG("PecPayback %s, pecState %s", start ? "start" : "stop", PecStateStr());
1301
1302 return true;
1303}
1304
1306{
1308 return false;
1309
1310 int command = start ? MC_PEC_RECORD_START : MC_PEC_RECORD_STOP;
1311
1312 set_sim_response("#");
1314
1315 size_t rlen = send_passthrough(CELESTRON_DEV_RA, command, nullptr, 0, response, 0);
1316 if (rlen <= 0)
1317 {
1318 LOGF_WARN("PEC Record %s failed", start ? "start" : "stop");
1319 return false;
1320 }
1321
1323
1324 LOGF_DEBUG("PecRecord %s, pecState %s", start ? "start" : "stop", PecStateStr());
1325 return true;
1326}
1327
1329{
1331 return true;
1332
1333 set_sim_response("%c#", simIndex == simRecordStart ? 1 : 0);
1334
1335 size_t rlen = send_passthrough(CELESTRON_DEV_RA, MC_PEC_RECORD_DONE, nullptr, 0, response, 1);
1336 if (rlen < 1)
1337 return false;
1338
1339 bool done = response[0] != 0x00;
1340 if (done)
1342
1343 LOGF_DEBUG("isPecRecordDone %s", done ? "yes" : "no");
1344
1345 return done;
1346}
1347
1349{
1350 if (simulation)
1351 {
1352 // generate PEC value from index, range -100 to +100, 1 cycle
1353 int val = static_cast<int>(std::round(std::cos(index * 2.0 * 3.14192 / 87.0) * 100.0));
1354 if (val < 0) val = 256 + val;
1355 set_sim_response("%c#", val);
1356 }
1357 char data[] = { static_cast<char>(0x40 + index) };
1358 size_t rlen = send_passthrough(CELESTRON_DEV_RA, MC_PEC_READ_DATA, data, 1, response, 1);
1359 if (rlen < 1)
1360 return 0;
1361
1362 // make result signed
1363 return response[0] <= '\127' ? response[0] : -256 + response[0];
1364}
1365
1366bool CelestronDriver::setPecValue(size_t index, int data)
1367{
1368 char payload[2];
1369 payload[0] = static_cast<char>(0x40 + index);
1370 payload[1] = static_cast<char>((data < 127) ? data : 256 - data);
1371 set_sim_response("#");
1372 return send_passthrough(CELESTRON_DEV_RA, MC_PEC_WRITE_DATA, payload, 2, response, 1) == 0;
1373}
1374
1376{
1377 switch (pecState)
1378 {
1380 isPecAtIndex();
1381 break;
1384 break;
1385 default:
1386 break;
1387 }
1388 return pecState;
1389}
1390
1392{
1393 return PecStateStr(pecState);
1394}
1395
1397{
1398 switch (state)
1399 {
1400 default:
1401 return "None";
1403 return "Not Available";
1405 return "Available";
1407 return "PEC Playback";
1409 return "seeking index";
1411 return "Index Found";
1413 return "PEC Recording";
1414 }
1415}
1416
1420
1421// constructor, generates test data
1423{
1424 numBins = 88;
1425 for (size_t i = 0; i <= numBins; ++i)
1426 {
1427 double p = i * 2.0 * 3.14192 / numBins;
1428 data[i] = std::sin(p) * 5;
1429 }
1430 wormArcSeconds = 7200;
1431}
1432
1433// Load PEC data from the mount
1435{
1436 // get model # and use it to set wormArcSeconds and rateScale
1437 int mountType = driver->model();
1438 rateScale = (mountType <= 2) ? 512 : 1024;
1439 wormArcSeconds = mountType == 8 ? 3600 : 7200;
1440 //LOGF_DEBUG("rateScale %f, wormArcSeconds %f, SiderealRate %f", rateScale, wormArcSeconds, SIDEREAL_ARCSEC_PER_SEC);
1441
1442 numBins = driver->getPecNumBins();
1443 if (numBins < 88 || numBins > 254)
1444 return false;
1445
1446 double posError = 0;
1447 data[0] = 0.0;
1448 for (size_t i = 0; i < numBins; i++)
1449 {
1450 // this is ported from the Celestron PECTool VB6 code
1451 // ' We traveled at SIDEREAL + binRate arcsec/sec over a distance of wormArcseconds/numPecBins arcseconds.
1452 // ' We need to figure out how long that took to get the error in arcseconds...
1453 // ' ie., error = binRate * binTime
1454 // binRate = rawPecData(i)
1455 // binTime = wormArcseconds / numPecBins / (SIDEREAL_ARCSEC_PER_SEC + binRate)
1456 // posError = posError + binRate * binTime
1457 // currentPecData(i + 1, 0) = posError
1458
1459 int rawPec = driver->getPecValue(i);
1460
1461 double binRate = rawPec * SIDEREAL_ARCSEC_PER_SEC / rateScale;
1462 double binTime = (wormArcSeconds / numBins) / (SIDEREAL_ARCSEC_PER_SEC + binRate);
1463 posError += binRate * binTime;
1464 data[i + 1] = posError;
1465
1466 LOGF_DEBUG("i %d, rawPec %d, binRate %f, binTime %f, data[%d] %f", i, rawPec, binRate, binTime, i + 1, data[i + 1]);
1467 }
1468 return true;
1469}
1470
1471// PEC file format, this matches the format used by Celestron in their PECTool application
1472// file format, one line for each entry:
1473// line 0: numBins, currently 88
1474// lines 1 to 90: double data[0] to data[numBins], numBins + 1 values, currently 89
1475// line 91: wormArcSecs, currently 7200
1476//
1477
1478// Load PEC data from file
1479bool PecData::Load(const char *fileName)
1480{
1481 std::ifstream pecFile(fileName);
1482
1483 if (pecFile.is_open())
1484 {
1485 pecFile >> numBins;
1486 for (size_t i = 0; i <= numBins; i++)
1487 {
1488 pecFile >> data[i];
1489 }
1490 pecFile >> wormArcSeconds;
1491 LOGF_DEBUG("PEC Load File %s, numBins %d, wormarcsecs %d", fileName, numBins, wormArcSeconds);
1492 return true;
1493 }
1494 else
1495 {
1496 // report file open failure
1497 LOGF_WARN("Load PEC file %s, error %s", fileName, strerror(errno));
1498 }
1499 return false;
1500}
1501
1502// Save the current PEC data to file
1503// returns false if it fails
1504bool PecData::Save(const char *filename)
1505{
1506 std::ofstream pecFile(filename);
1507 if (!pecFile.is_open())
1508 return false;
1509
1510 pecFile << numBins << "\n";
1511 for (size_t i = 0; i <= numBins; i++)
1512 {
1513 pecFile << data[i] << "\n";
1514 LOGF_DEBUG("data[%d] = %f", i, data[i]);
1515 }
1516 pecFile << wormArcSeconds << "\n";
1517 return true;
1518}
1519
1520// Save the current PEC data to the mount
1522{
1523 if (driver->getPecNumBins() != numBins)
1524 {
1525 return false;
1526 }
1527
1528 double deltaDist;
1529 for (size_t i = 0; i < numBins; i++)
1530 {
1531 // this is ported from the Celestron PECTool VB6 code
1532 // deltaDist = currentPecData(i + 1, 0) - currentPecData(i, 0)
1533 // rawPecData(i) = deltaDist * SIDEREAL_ARCSEC_PER_SEC / (wormArcseconds / numPecBins - deltaDist)
1534
1535 // get the offset in arcsecs per bin
1536 deltaDist = data[i + 1] - data[i];
1537 // convert to offset in arcsecs per second
1538 double rawPecData = deltaDist * SIDEREAL_ARCSEC_PER_SEC / (wormArcSeconds / numBins - deltaDist);
1539
1540 int rawdata = static_cast<int>(std::round(rawPecData * rateScale / SIDEREAL_ARCSEC_PER_SEC));
1541 LOGF_DEBUG("i %d, deltaDist %f, rawPecdata %f, rawData %d", i, deltaDist, rawPecData, rawdata);
1542 if (rawdata < 0)
1543 rawdata += 256;
1544 driver->setPecValue(i, rawdata);
1545 }
1546 return true;
1547}
1548
1549// Removes any drift over the PEC cycle
1551{
1552 // this works by taking the offset in arcseconds over one PEC cycle and correcting the PEC values
1553 // linearly so the drift is eliminated.
1554 // It gives slightly different values to what the original drift removal does but the difference is
1555 // small
1556 double delta = (data[numBins] - data[0]) / numBins;
1557 double offset = data[0];
1558 for (size_t i = 0; i <= numBins; i++)
1559 {
1560 data[i] = data[i] - offset - delta * i;
1561 }
1562}
1563
1564void PecData::Kalman(PecData newData, int num)
1565{
1566 if (numBins != newData.numBins)
1567 {
1568 //throw new ApplicationException("Kalman not possible numBins do not match");
1569 return;
1570 }
1571 auto fraction = 1.0 / num;
1572 auto kf = 1 - fraction;
1573 //auto nd = newData.Data;
1574 for (size_t i = 0; i <= numBins; i++)
1575 {
1576 data[i] = data[i] * kf + newData.data[i] * fraction;
1577 }
1578}
1579
1580// This method is required by the logging macros
1582{
1583 return device_str;
1584}
1585
1586
std::string fmt(const char *fmt,...)
Definition Utils.cpp:241
void parseCoordsResponse(char *response, double *d1, double *d2, bool precise)
void hex_dump(char *buf, const char *data, size_t size)
__attribute__((__format__(__printf__, 2, 0))) void CelestronDriver
#define CELESTRON_TIMEOUT
#define CELESTRON_DEV_RA
#define MC_PEC_RECORD_DONE
#define MC_SET_NEG_GUIDERATE
#define MC_SEEK_INDEX
#define MC_PEC_RECORD_STOP
CELESTRON_DIRECTION
@ CELESTRON_W
@ CELESTRON_N
@ CELESTRON_S
@ CELESTRON_E
#define MC_PEC_PLAYBACK
#define MC_GOTO_FAST
CELESTRON_SLEW_RATE
#define MC_PEC_RECORD_START
#define FOC_GET_HS_POSITIONS
CELESTRON_TRACK_MODE
@ CTM_EQN
@ CTM_EQS
#define MC_MOVE_NEG
#define MTR_AUX_GUIDE
#define ISSTARSENSE
#define MC_PEC_WRITE_DATA
#define MC_LEVEL_START
#define GET_VER
#define MINSTSENSVER
#define MC_LEVEL_DONE
#define MAX_RESP_SIZE
#define CELESTRON_DEV_FOC
#define MC_GET_POSITION
CELESTRON_TRACK_RATE
PEC_STATE
@ PEC_SEEKING
The PEC index is being searched for, goes to PEC_INDEXED when found.
@ PEC_AVAILABLE
PEC is available but inactive, can seek index Seek index is only available command.
@ PEC_PLAYBACK
PEC is being played back, stays in this state until stopped equivalent to TelescopePECState PEC_ON.
@ PEC_RECORDING
PEC is being recorded, goes to PEC_INDEXED when completed.
@ PEC_NOT_AVAILABLE
PEC is not available, hardware has been checked, no other state is possible.
@ PEC_INDEXED
the PEC index has been found, can go to Playback or Recording this is equivalent to TelescopePECState...
#define CELESTRON_DEV_DEC
#define MC_SET_AUTOGUIDE_RATE
#define MC_PEC_READ_DATA
CELESTRON_AXIS
@ DEC_AXIS
@ RA_AXIS
#define MC_MOVE_POS
#define MC_GET_AUTOGUIDE_RATE
#define MC_SET_POS_GUIDERATE
#define ISNEXSTAR
#define MTR_PECBIN
#define MC_AT_INDEX
#define MTR_IS_AUX_GUIDE_ACTIVE
#define MC_SLEW_DONE
bool slew_radec(double ra, double dec, bool precise)
bool get_azalt(double *az, double *alt, bool precise)
bool set_guide_rate(CELESTRON_AXIS axis, uint8_t rate)
bool indexreached(bool *atIndex)
bool check_aligned(bool *isAligned)
bool set_datetime(struct ln_date *utc, double utc_offset, bool dst=false, bool precise=false)
bool set_location(double longitude, double latitude)
bool stop_motion(CELESTRON_DIRECTION dir)
int getPecValue(size_t index)
bool get_firmware(FirmwareInfo *info)
bool sync(double ra, double dec, bool precise)
bool foc_limits(int *low, int *high)
bool PecPlayback(bool start)
bool get_dev_firmware(int dev, char *version, size_t size)
size_t send_pulse(CELESTRON_DIRECTION direction, unsigned char rate, unsigned char duration_msec)
size_t send_passthrough(int dest, int cmd_id, const char *payload, size_t payload_len, char *resp, size_t response_len)
bool get_radec(double *ra, double *dec, bool precise)
bool get_track_mode(CELESTRON_TRACK_MODE *mode)
void move_sim_foc(int offset)
void set_device(const char *name)
bool set_track_rate(CELESTRON_TRACK_RATE rate, CELESTRON_TRACK_MODE mode)
bool set_track_mode(CELESTRON_TRACK_MODE mode)
bool get_model(char *model, size_t size, bool *isGem, bool *canPec, bool *hasHomeIndex)
bool get_version(char *version, size_t size)
size_t send_command(const char *cmd, size_t cmd_len, char *resp, size_t resp_len, bool ascii_cmd, bool ascii_resp)
char response[MAX_RESP_SIZE]
virtual int serial_read(int nbytes, int *nbytes_read)
const char * PecStateStr()
void set_sim_response(const char *fmt,...)
bool PecRecord(bool start)
bool isPecAtIndex(bool force=false)
bool get_variant(char *variant)
bool get_location(double *longitude, double *latitude)
const char * getDeviceName()
bool get_pulse_status(CELESTRON_DIRECTION direction)
void set_sim_slewing(bool isSlewing)
virtual int serial_write(const char *cmd, int nbytes, int *nbytes_written)
PEC_STATE updatePecState()
virtual int serial_read_section(char stop_char, int *nbytes_read)
bool get_guide_rate(CELESTRON_AXIS axis, uint8_t *rate)
bool slew_azalt(double az, double alt, bool precise)
bool setPecValue(size_t index, int data)
bool foc_move(uint32_t steps)
bool get_pier_side(char *sop)
bool is_slewing(bool *slewing)
bool get_utc_date_time(double *utc_hours, int *yy, int *mm, int *dd, int *hh, int *minute, int *ss, bool *dst, bool precise)
bool start_motion(CELESTRON_DIRECTION dir, CELESTRON_SLEW_RATE rate)
PecData()
PecData class.
const char * getDeviceName()
bool Load(CelestronDriver *driver)
bool Save(const char *filename)
double max(void)
int errno
double ra
double dec
#define MAXINDIDEVICE
Definition indiapi.h:191
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
void getSexComponents(double value, int *d, int *m, int *s)
Definition indicom.c:243
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
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
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
#define LOGF_EXTRA1(fmt,...)
Definition indilogger.h:84
double pnex2dd(uint32_t value)
uint32_t dd2pnex(double angle)
uint16_t dd2nex(double angle)
double trimDecAngle(double angle)
double nex2dd(uint32_t value)
uint8_t cmd[4]
Definition pwc-ioctl.h:2
double controllerVersion
std::string Version
std::string RAFirmware
std::string Model
std::string DEFirmware
uint32_t foc_position
uint32_t foc_target