Instrument Neutral Distributed Interface INDI 2.2.5
gemini_flatpanel_adapters.cpp
Go to the documentation of this file.
2#include "indicom.h"
3#include "indibase.h"
4#include <termios.h>
5#include <cstring>
6#include <cstdio>
7#include <cstdlib>
8
10// GeminiFlatpanelRev1Adapter Implementation
12
13// Device detection and identification
15{
16 // This command is only supported by revision 1 devices
17 // It is used to check if the device is a revision 1 device
18 char response[MAXRBUF] = {0};
19
20 if (!sendCommand(">P000#", response, SERIAL_TIMEOUT_SEC, false))
21 {
22 return false;
23 }
24
25 if (strcmp(response, "*P99OOO") != 0)
26 {
27 return false;
28 }
29
30 return true;
31}
32
34{
35 // Rev1 doesn't support firmware version reporting command
36 // Always return 0 to indicate no firmware version available
37 *version = 0;
38 return true;
39}
40
41// Basic device commands
43{
44 char command[MAXRBUF] = {0};
45 char response[MAXRBUF] = {0};
46
47 if (!formatCommand('A', command))
48 {
49 return false;
50 }
51
52 if (!sendCommand(command, response))
53 {
54 return false;
55 }
56
57 if (strlen(response) < 3)
58 {
59 return false;
60 }
61
62 if (response[0] != '*' || response[1] != 'A')
63 {
64 return false;
65 }
66
67 *status = response[2] - '0';
68 return true;
69}
70
72{
73 char command[MAXRBUF] = {0};
74 char response[MAXRBUF] = {0};
75
76 if (!formatCommand('J', command))
77 {
78 return false;
79 }
80
81 if (!sendCommand(command, response))
82 {
83 return false;
84 }
85
86 if (strlen(response) < 3)
87 {
88 return false;
89 }
90
91 if (response[0] != '*' || response[1] != 'J')
92 {
93 return false;
94 }
95
96 // Rev1 brightness response index is at position 4
97 return extractIntValue(response, 4, brightness);
98}
99
101{
102 char command[MAXRBUF] = {0};
103 char response[MAXRBUF] = {0};
104
105 if (value > GEMINI_MAX_BRIGHTNESS)
106 {
107 value = GEMINI_MAX_BRIGHTNESS;
108 }
109
110 if (value < GEMINI_MIN_BRIGHTNESS)
111 {
112 value = GEMINI_MIN_BRIGHTNESS;
113 }
114
115 if (!formatCommand('B', command, value))
116 {
117 return false;
118 }
119
120 if (!sendCommand(command, response))
121 {
122 return false;
123 }
124
125 if (strlen(response) < 3)
126 {
127 return false;
128 }
129
130 if (response[0] != '*' || response[1] != 'B')
131 {
132 return false;
133 }
134
135 int response_value;
136 if (!extractIntValue(response, 4, &response_value))
137 {
138 return false;
139 }
140
141 return (response_value == value);
142}
143
145{
146 char command[MAXRBUF] = {0};
147 char response[MAXRBUF] = {0};
148
149 if (!formatCommand('L', command))
150 {
151 return false;
152 }
153
154 if (!sendCommand(command, response))
155 {
156 return false;
157 }
158
159 return (strlen(response) >= 3 && response[0] == '*' && response[1] == 'L');
160}
161
163{
164 char command[MAXRBUF] = {0};
165 char response[MAXRBUF] = {0};
166
167 if (!formatCommand('D', command))
168 {
169 return false;
170 }
171
172 if (!sendCommand(command, response))
173 {
174 return false;
175 }
176
177 return (strlen(response) >= 3 && response[0] == '*' && response[1] == 'D');
178}
179
181{
182 char command[MAXRBUF] = {0};
183 char response[MAXRBUF] = {0};
184
185 if (!formatCommand('O', command))
186 {
187 return false;
188 }
189
190 if (!sendCommand(command, response, SERIAL_TIMEOUT_SEC_LONG))
191 {
192 return false;
193 }
194
195 return (strcmp(response, "*O99OOO") == 0);
196}
197
199{
200 char command[MAXRBUF] = {0};
201 char response[MAXRBUF] = {0};
202
203 if (!formatCommand('C', command))
204 {
205 return false;
206 }
207
208 if (!sendCommand(command, response, SERIAL_TIMEOUT_SEC_LONG))
209 {
210 return false;
211 }
212
213 return (strcmp(response, "*C99OOO") == 0);
214}
215
216bool GeminiFlatpanelRev1Adapter::getStatus(int *coverStatus, int *lightStatus, int *motorStatus)
217{
218 char command[MAXRBUF] = {0};
219 char response[MAXRBUF] = {0};
220
221 if (!formatCommand('S', command))
222 {
223 return false;
224 }
225
226 if (!sendCommand(command, response))
227 {
228 return false;
229 }
230
231 if (strlen(response) < 7)
232 {
233 return false;
234 }
235
236 if (response[0] != '*' || response[1] != 'S')
237 {
238 return false;
239 }
240
241 // Check if device ID is valid (19 or 99)
242 char id_str[3] = {response[2], response[3], '\0'};
243 int id = atoi(id_str);
244 if (id != 19 && id != 99)
245 {
246 return false;
247 }
248
249 *motorStatus = response[4] - '0';
250 *lightStatus = response[5] - '0';
251 *coverStatus = response[6] - '0';
252
253 return true;
254}
255
256// Motion/calibration commands
257bool GeminiFlatpanelRev1Adapter::move(uint16_t value, int direction)
258{
259 char command[MAXRBUF] = {0};
260
261 if (direction == -1)
262 {
263 snprintf(command, sizeof(command), ">M-%02d#", value);
264 }
265 else
266 {
267 snprintf(command, sizeof(command), ">M%03d#", value);
268 }
269
270 char response[MAXRBUF] = {0};
271
272 return sendCommand(command, response, 30);
273}
274
276{
277 char command[MAXRBUF] = {0};
278 char response[MAXRBUF] = {0};
279
280 if (!formatCommand('F', command))
281 {
282 return false;
283 }
284
285 return sendCommand(command, response);
286}
287
289{
290 char command[MAXRBUF] = {0};
291 char response[MAXRBUF] = {0};
292
293 if (!formatCommand('E', command))
294 {
295 return false;
296 }
297
298 return sendCommand(command, response);
299}
300
301// Helper methods for Rev1
302bool GeminiFlatpanelRev1Adapter::formatCommand(char commandLetter, char *commandString, int value)
303{
304 if (commandString == nullptr)
305 {
306 return false;
307 }
308
309 // Rev1 format: >X000# (no value) or >XNNN# (with value) where X is command letter and NNN is 3-digit value
310 if (value == NO_VALUE)
311 {
312 // No value provided, pad with 000
313 snprintf(commandString, MAXRBUF, ">%c000#", commandLetter);
314 }
315 else
316 {
317 // Value provided, pad to 3 digits
318 snprintf(commandString, MAXRBUF, ">%c%03d#", commandLetter, value);
319 }
320
321 return true;
322}
323
324bool GeminiFlatpanelRev1Adapter::extractIntValue(const char *response, int startPos, int *value)
325{
326 if (response == nullptr || value == nullptr)
327 {
328 return false;
329 }
330
331 // Revision 1 responses have a 3-digit numeric part 0 padded
332 int length = 3;
333
334 if (length <= 0)
335 {
336 return false;
337 }
338
339 // Extract the numeric part
340 char value_str[MAXRBUF] = {0};
341 snprintf(value_str, sizeof(value_str), "%.*s", static_cast<int>(length), response + startPos);
342 *value = atoi(value_str);
343
344 return true;
345}
346
347bool GeminiFlatpanelRev1Adapter::sendCommand(const char *command, char *response, int timeout, bool log)
348{
349 int nbytes_written = 0, nbytes_read = 0, rc = -1;
350
351 tcflush(portFD, TCIOFLUSH);
352 if ((rc = tty_write_string(portFD, command, &nbytes_written)) != TTY_OK)
353 {
354 if (log)
355 {
356 char errstr[MAXRBUF] = {0};
357 tty_error_msg(rc, errstr, MAXRBUF);
358 // Note: Cannot use LOGF_ERROR here as we don't have access to logging
359 // The main driver should handle logging of errors
360 }
361 return false;
362 }
363
364 if (response == nullptr)
365 return true;
366
367 if ((rc = tty_nread_section(portFD, response, MAXRBUF, getCommandTerminator(), timeout, &nbytes_read)) != TTY_OK)
368 {
369 if (log)
370 {
371 char errstr[MAXRBUF] = {0};
372 tty_error_msg(rc, errstr, MAXRBUF);
373 // Note: Cannot use LOGF_ERROR here as we don't have access to logging
374 }
375 return false;
376 }
377
378 // Remove trailing newline character if present
379 if (nbytes_read > 0 && response[nbytes_read - 1] == '\n')
380 {
381 response[nbytes_read - 1] = '\0';
382 }
383 tcflush(portFD, TCIOFLUSH);
384
385 if (response[0] != '*' || response[1] != command[1])
386 {
387 return false;
388 }
389
390 return true;
391}
392
394// GeminiFlatpanelRev2Adapter Implementation
396
397// Device detection and identification
399{
400 // This command is only supported by revision 2 devices
401 // It is used to check if the device is a revision 2 device
402 char response[MAXRBUF] = {0};
403
404 if (!sendCommand(">H#", response, SERIAL_TIMEOUT_SEC, false))
405 {
406 return false;
407 }
408
409 if (strcmp(response, "*HGeminiFlatPanel#") != 0)
410 {
411 return false;
412 }
413
414 return true;
415}
416
418{
419 // This command is only supported by revision 2 devices
420 // It is used to get the firmware version
421 char response[MAXRBUF] = {0};
422
423 // Initialize version to 0 in case of errors
424 *version = 0;
425
426 if (!sendCommand(">V#", response))
427 {
428 return false;
429 }
430
431 if (strlen(response) < 3)
432 {
433 return false;
434 }
435
436 if (response[0] != '*' || response[1] != 'V')
437 {
438 return false;
439 }
440
441 int firmwareVersion;
442 if (!extractIntValue(response, 2, &firmwareVersion))
443 {
444 return false;
445 }
446
447 if (firmwareVersion < 402)
448 {
449 // Firmware version too old. Should be 402 or later.
450 return false;
451 }
452
453 *version = firmwareVersion;
454 return true;
455}
456
457// Basic device commands
459{
460 char command[MAXRBUF] = {0};
461 char response[MAXRBUF] = {0};
462
463 if (!formatCommand('A', command))
464 {
465 return false;
466 }
467
468 if (!sendCommand(command, response))
469 {
470 return false;
471 }
472
473 if (strlen(response) < 3)
474 {
475 return false;
476 }
477
478 if (response[0] != '*' || response[1] != 'A')
479 {
480 return false;
481 }
482
483 *status = response[2] - '0';
484 return true;
485}
486
488{
489 char command[MAXRBUF] = {0};
490 char response[MAXRBUF] = {0};
491
492 if (!formatCommand('J', command))
493 {
494 return false;
495 }
496
497 if (!sendCommand(command, response))
498 {
499 return false;
500 }
501
502 if (strlen(response) < 3)
503 {
504 return false;
505 }
506
507 if (response[0] != '*' || response[1] != 'J')
508 {
509 return false;
510 }
511
512 // Rev2 brightness response index is at position 2
513 return extractIntValue(response, 2, brightness);
514}
515
517{
518 char command[MAXRBUF] = {0};
519 char response[MAXRBUF] = {0};
520
521 if (value > GEMINI_MAX_BRIGHTNESS)
522 {
523 value = GEMINI_MAX_BRIGHTNESS;
524 }
525
526 if (value < GEMINI_MIN_BRIGHTNESS)
527 {
528 value = GEMINI_MIN_BRIGHTNESS;
529 }
530
531 if (!formatCommand('B', command, value))
532 {
533 return false;
534 }
535
536 if (!sendCommand(command, response))
537 {
538 return false;
539 }
540
541 if (strlen(response) < 3)
542 {
543 return false;
544 }
545
546 if (response[0] != '*' || response[1] != 'B')
547 {
548 return false;
549 }
550
551 int response_value;
552 if (!extractIntValue(response, 2, &response_value))
553 {
554 return false;
555 }
556
557 return (response_value == value);
558}
559
561{
562 char command[MAXRBUF] = {0};
563 char response[MAXRBUF] = {0};
564
565 if (!formatCommand('L', command))
566 {
567 return false;
568 }
569
570 if (!sendCommand(command, response))
571 {
572 return false;
573 }
574
575 return (strlen(response) >= 3 && response[0] == '*' && response[1] == 'L');
576}
577
579{
580 char command[MAXRBUF] = {0};
581 char response[MAXRBUF] = {0};
582
583 if (!formatCommand('D', command))
584 {
585 return false;
586 }
587
588 if (!sendCommand(command, response))
589 {
590 return false;
591 }
592
593 return (strlen(response) >= 3 && response[0] == '*' && response[1] == 'D');
594}
595
597{
598 char command[MAXRBUF] = {0};
599 char response[MAXRBUF] = {0};
600
601 if (!formatCommand('O', command))
602 {
603 return false;
604 }
605
606 if (!sendCommand(command, response, SERIAL_TIMEOUT_SEC_LONG))
607 {
608 return false;
609 }
610
611 return (strcmp(response, "*OOpened#") == 0);
612}
613
615{
616 char command[MAXRBUF] = {0};
617 char response[MAXRBUF] = {0};
618
619 if (!formatCommand('C', command))
620 {
621 return false;
622 }
623
624 if (!sendCommand(command, response, SERIAL_TIMEOUT_SEC_LONG))
625 {
626 return false;
627 }
628
629 return (strcmp(response, "*CClosed#") == 0);
630}
631
632bool GeminiFlatpanelRev2Adapter::getStatus(int *coverStatus, int *lightStatus, int *motorStatus)
633{
634 char command[MAXRBUF] = {0};
635 char response[MAXRBUF] = {0};
636
637 if (!formatCommand('S', command))
638 {
639 return false;
640 }
641
642 if (!sendCommand(command, response))
643 {
644 return false;
645 }
646
647 if (strlen(response) < 7)
648 {
649 return false;
650 }
651
652 if (response[0] != '*' || response[1] != 'S')
653 {
654 return false;
655 }
656
657 // Check if device ID is valid (19 or 99)
658 char id_str[3] = {response[2], response[3], '\0'};
659 int id = atoi(id_str);
660 if (id != 19 && id != 99)
661 {
662 return false;
663 }
664
665 *motorStatus = response[4] - '0';
666 *lightStatus = response[5] - '0';
667 *coverStatus = response[6] - '0';
668
669 return true;
670}
671
672// Motion/calibration commands
673bool GeminiFlatpanelRev2Adapter::move(uint16_t value, int direction)
674{
675 char command[MAXRBUF] = {0};
676
677 if (direction == -1)
678 {
679 snprintf(command, sizeof(command), ">M-%02d#", value);
680 }
681 else
682 {
683 snprintf(command, sizeof(command), ">M%03d#", value);
684 }
685
686 char response[MAXRBUF] = {0};
687
688 return sendCommand(command, response, 30);
689}
690
692{
693 char command[MAXRBUF] = {0};
694 char response[MAXRBUF] = {0};
695
696 if (!formatCommand('F', command))
697 {
698 return false;
699 }
700
701 return sendCommand(command, response);
702}
703
705{
706 char command[MAXRBUF] = {0};
707 char response[MAXRBUF] = {0};
708
709 if (!formatCommand('E', command))
710 {
711 return false;
712 }
713
714 return sendCommand(command, response);
715}
716
717// Advanced commands
719{
720 char command[MAXRBUF] = {0};
721
722 if (!formatCommand('T', command, enable ? GEMINI_BEEP_ON : GEMINI_BEEP_OFF))
723 {
724 return false;
725 }
726
727 return sendCommand(command, nullptr);
728}
729
731{
733 {
734 return false;
735 }
736
737 char command[MAXRBUF] = {0};
738
739 if (!formatCommand('Y', command, mode))
740 {
741 return false;
742 }
743
744 return sendCommand(command, nullptr);
745}
746
747// Helper methods for Rev2
748bool GeminiFlatpanelRev2Adapter::formatCommand(char commandLetter, char *commandString, int value)
749{
750 if (commandString == nullptr)
751 {
752 return false;
753 }
754
755 // Rev2 format: >X# (no value) or >Xnnn# (with value) where X is command letter and nnn is value
756 if (value == NO_VALUE)
757 {
758 // No value provided, no padding
759 snprintf(commandString, MAXRBUF, ">%c#", commandLetter);
760 }
761 else
762 {
763 // Value provided, no padding
764 snprintf(commandString, MAXRBUF, ">%c%d#", commandLetter, value);
765 }
766
767 return true;
768}
769
770bool GeminiFlatpanelRev2Adapter::extractIntValue(const char *response, int startPos, int *value)
771{
772 if (response == nullptr || value == nullptr)
773 {
774 return false;
775 }
776
777 const char *terminator = strchr(response, '#');
778 if (terminator == nullptr)
779 {
780 return false;
781 }
782
783 // Calculate the length of the numeric part
784 int length = terminator - (response + startPos);
785
786 if (length <= 0)
787 {
788 return false;
789 }
790
791 // Extract the numeric part
792 char value_str[MAXRBUF] = {0};
793 snprintf(value_str, sizeof(value_str), "%.*s", length, response + startPos);
794 *value = atoi(value_str);
795
796 return true;
797}
798
799bool GeminiFlatpanelRev2Adapter::sendCommand(const char *command, char *response, int timeout, bool log)
800{
801 int nbytes_written = 0, nbytes_read = 0, rc = -1;
802
803 tcflush(portFD, TCIOFLUSH);
804 if ((rc = tty_write_string(portFD, command, &nbytes_written)) != TTY_OK)
805 {
806 if (log)
807 {
808 char errstr[MAXRBUF] = {0};
809 tty_error_msg(rc, errstr, MAXRBUF);
810 // Note: Cannot use LOGF_ERROR here as we don't have access to logging
811 // The main driver should handle logging of errors
812 }
813 return false;
814 }
815
816 if (response == nullptr)
817 return true;
818
819 if ((rc = tty_nread_section(portFD, response, MAXRBUF, getCommandTerminator(), timeout, &nbytes_read)) != TTY_OK)
820 {
821 if (log)
822 {
823 char errstr[MAXRBUF] = {0};
824 tty_error_msg(rc, errstr, MAXRBUF);
825 // Note: Cannot use LOGF_ERROR here as we don't have access to logging
826 }
827 return false;
828 }
829
830 // Remove trailing newline character if present
831 if (nbytes_read > 0 && response[nbytes_read - 1] == '\n')
832 {
833 response[nbytes_read - 1] = '\0';
834 }
835 tcflush(portFD, TCIOFLUSH);
836
837 if (response[0] != '*' || response[1] != command[1])
838 {
839 return false;
840 }
841
842 return true;
843}
844
846// GeminiFlatpanelLiteAdapter Implementation
848
849// Device detection and identification
851{
852 // This command is only supported by Lite devices
853 // It is used to check if the device is a Lite device
854 char response[MAXRBUF] = {0};
855
856 if (!sendCommand(">H#", response, SERIAL_TIMEOUT_SEC, false))
857 {
858 return false;
859 }
860
861 if (strcmp(response, "*HGeminiFlatPanelLite#") != 0)
862 {
863 return false;
864 }
865
866 return true;
867}
868
870{
871 // This command is supported by Lite devices
872 // It is used to get the firmware version
873 char response[MAXRBUF] = {0};
874
875 // Initialize version to 0 in case of errors
876 *version = 0;
877
878 if (!sendCommand(">V#", response))
879 {
880 return false;
881 }
882
883 if (strlen(response) < 3)
884 {
885 return false;
886 }
887
888 if (response[0] != '*' || response[1] != 'V')
889 {
890 return false;
891 }
892
893 int firmwareVersion;
894 if (!extractIntValue(response, 2, &firmwareVersion))
895 {
896 return false;
897 }
898
899 if (firmwareVersion < 205)
900 {
901 // Firmware version too old. Should be 205 or later for Lite.
902 return false;
903 }
904
905 *version = firmwareVersion;
906 return true;
907}
908
909// Basic device commands
911{
912 // Lite devices don't have a separate config status command
913 // We'll assume they're always ready since they don't have motorized parts
914 *status = GEMINI_CONFIG_READY;
915 return true;
916}
917
919{
920 char command[MAXRBUF] = {0};
921 char response[MAXRBUF] = {0};
922
923 if (!formatCommand('J', command))
924 {
925 return false;
926 }
927
928 if (!sendCommand(command, response))
929 {
930 return false;
931 }
932
933 if (strlen(response) < 3)
934 {
935 return false;
936 }
937
938 if (response[0] != '*' || response[1] != 'J')
939 {
940 return false;
941 }
942
943 // Lite brightness response index is at position 2
944 return extractIntValue(response, 2, brightness);
945}
946
948{
949 char command[MAXRBUF] = {0};
950 char response[MAXRBUF] = {0};
951
952 if (value > GEMINI_MAX_BRIGHTNESS)
953 {
954 value = GEMINI_MAX_BRIGHTNESS;
955 }
956
957 if (value < GEMINI_MIN_BRIGHTNESS)
958 {
959 value = GEMINI_MIN_BRIGHTNESS;
960 }
961
962 if (!formatCommand('B', command, value))
963 {
964 return false;
965 }
966
967 if (!sendCommand(command, response))
968 {
969 return false;
970 }
971
972 if (strlen(response) < 3)
973 {
974 return false;
975 }
976
977 if (response[0] != '*' || response[1] != 'B')
978 {
979 return false;
980 }
981
982 int response_value;
983 if (!extractIntValue(response, 2, &response_value))
984 {
985 return false;
986 }
987
988 return (response_value == value);
989}
990
992{
993 char command[MAXRBUF] = {0};
994 char response[MAXRBUF] = {0};
995
996 if (!formatCommand('L', command))
997 {
998 return false;
999 }
1000
1001 if (!sendCommand(command, response))
1002 {
1003 return false;
1004 }
1005
1006 return (strlen(response) >= 3 && response[0] == '*' && response[1] == 'L');
1007}
1008
1010{
1011 char command[MAXRBUF] = {0};
1012 char response[MAXRBUF] = {0};
1013
1014 if (!formatCommand('D', command))
1015 {
1016 return false;
1017 }
1018
1019 if (!sendCommand(command, response))
1020 {
1021 return false;
1022 }
1023
1024 return (strlen(response) >= 3 && response[0] == '*' && response[1] == 'D');
1025}
1026
1028{
1029 // Lite devices don't have motorized covers
1030 return false;
1031}
1032
1034{
1035 // Lite devices don't have motorized covers
1036 return false;
1037}
1038
1039bool GeminiFlatpanelLiteAdapter::getStatus(int *coverStatus, int *lightStatus, int *motorStatus)
1040{
1041 char command[MAXRBUF] = {0};
1042 char response[MAXRBUF] = {0};
1043
1044 if (!formatCommand('S', command))
1045 {
1046 return false;
1047 }
1048
1049 if (!sendCommand(command, response))
1050 {
1051 return false;
1052 }
1053
1054 if (strlen(response) < 5) // "*SLMB#" minimum
1055 {
1056 return false;
1057 }
1058
1059 if (response[0] != '*' || response[1] != 'S')
1060 {
1061 return false;
1062 }
1063
1064 // Lite status format: "*SLMB#" where:
1065 // L = light status (0 off, 1 on)
1066 // M = brightness Mode (0 Low, 1 High)
1067 // B = Beep on/off (0 off, 1 on)
1068
1069 *lightStatus = response[2] - '0';
1070 // For Lite devices, cover is always "open" since there's no physical cover
1071 *coverStatus = GEMINI_COVER_STATUS_OPEN;
1072 // Motor is always stopped since there's no motor
1073 *motorStatus = GEMINI_MOTOR_STATUS_STOPPED;
1074
1075 return true;
1076}
1077
1078// Advanced commands
1080{
1081 char command[MAXRBUF] = {0};
1082
1083 if (!formatCommand('T', command, enable ? GEMINI_BEEP_ON : GEMINI_BEEP_OFF))
1084 {
1085 return false;
1086 }
1087
1088 return sendCommand(command, nullptr);
1089}
1090
1092{
1094 {
1095 return false;
1096 }
1097
1098 char command[MAXRBUF] = {0};
1099
1100 if (!formatCommand('Y', command, mode))
1101 {
1102 return false;
1103 }
1104
1105 return sendCommand(command, nullptr);
1106}
1107
1108// Helper methods for Lite
1109bool GeminiFlatpanelLiteAdapter::formatCommand(char commandLetter, char *commandString, int value)
1110{
1111 if (commandString == nullptr)
1112 {
1113 return false;
1114 }
1115
1116 // Lite format: >X# (no value) or >Xnnn# (with value) where X is command letter and nnn is value
1117 if (value == NO_VALUE)
1118 {
1119 // No value provided
1120 snprintf(commandString, MAXRBUF, ">%c#", commandLetter);
1121 }
1122 else
1123 {
1124 // Value provided
1125 snprintf(commandString, MAXRBUF, ">%c%d#", commandLetter, value);
1126 }
1127
1128 return true;
1129}
1130
1131bool GeminiFlatpanelLiteAdapter::extractIntValue(const char *response, int startPos, int *value)
1132{
1133 if (response == nullptr || value == nullptr)
1134 {
1135 return false;
1136 }
1137
1138 const char *terminator = strchr(response, '#');
1139 if (terminator == nullptr)
1140 {
1141 return false;
1142 }
1143
1144 // Calculate the length of the numeric part
1145 int length = terminator - (response + startPos);
1146
1147 if (length <= 0)
1148 {
1149 return false;
1150 }
1151
1152 // Extract the numeric part
1153 char value_str[MAXRBUF] = {0};
1154 strncpy(value_str, response + startPos, length);
1155 *value = atoi(value_str);
1156
1157 return true;
1158}
1159
1160bool GeminiFlatpanelLiteAdapter::sendCommand(const char *command, char *response, int timeout, bool log)
1161{
1162 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1163
1164 tcflush(portFD, TCIOFLUSH);
1165 if ((rc = tty_write_string(portFD, command, &nbytes_written)) != TTY_OK)
1166 {
1167 if (log)
1168 {
1169 char errstr[MAXRBUF] = {0};
1170 tty_error_msg(rc, errstr, MAXRBUF);
1171 // Note: Cannot use LOGF_ERROR here as we don't have access to logging
1172 // The main driver should handle logging of errors
1173 }
1174 return false;
1175 }
1176
1177 if (response == nullptr)
1178 return true;
1179
1180 if ((rc = tty_nread_section(portFD, response, MAXRBUF, getCommandTerminator(), timeout, &nbytes_read)) != TTY_OK)
1181 {
1182 if (log)
1183 {
1184 char errstr[MAXRBUF] = {0};
1185 tty_error_msg(rc, errstr, MAXRBUF);
1186 // Note: Cannot use LOGF_ERROR here as we don't have access to logging
1187 }
1188 return false;
1189 }
1190
1191 // Remove trailing newline character if present
1192 if (nbytes_read > 0 && response[nbytes_read - 1] == '\n')
1193 {
1194 response[nbytes_read - 1] = '\0';
1195 }
1196 tcflush(portFD, TCIOFLUSH);
1197
1198 if (response[0] != '*' || response[1] != command[1])
1199 {
1200 return false;
1201 }
1202
1203 return true;
1204}
1205
1207// GeminiFlatpanelProAdapter Implementation
1209
1210// Device detection and identification
1212{
1213 // This command is only supported by Pro devices
1214 // It is used to check if the device is a Pro device
1215 char response[MAXRBUF] = {0};
1216
1217 if (!sendCommand(">H#", response, SERIAL_TIMEOUT_SEC, false))
1218 {
1219 return false;
1220 }
1221
1222 if (strcmp(response, "*HGeminiFlatPanelPro#") != 0)
1223 {
1224 return false;
1225 }
1226
1227 return true;
1228}
1229
1231{
1232 // This command is supported by Pro devices
1233 // It is used to get the firmware version
1234 char response[MAXRBUF] = {0};
1235
1236 // Initialize version to 0 in case of errors
1237 *version = 0;
1238
1239 if (!sendCommand(">V#", response))
1240 {
1241 return false;
1242 }
1243
1244 if (strlen(response) < 3)
1245 {
1246 return false;
1247 }
1248
1249 if (response[0] != '*' || response[1] != 'V')
1250 {
1251 return false;
1252 }
1253
1254 int firmwareVersion;
1255 if (!extractIntValue(response, 2, &firmwareVersion))
1256 {
1257 return false;
1258 }
1259
1260 *version = firmwareVersion;
1261 return true;
1262}
1263
1265{
1266 // Pro devices don't have a separate config status command
1267 *status = GEMINI_CONFIG_READY;
1268 return true;
1269}
1270
1272{
1273 char command[MAXRBUF] = {0};
1274 char response[MAXRBUF] = {0};
1275
1276 if (!formatCommand('J', command))
1277 {
1278 return false;
1279 }
1280
1281 if (!sendCommand(command, response))
1282 {
1283 return false;
1284 }
1285
1286 if (strlen(response) < 4)
1287 {
1288 return false;
1289 }
1290
1291 if (response[0] != '*' || response[1] != 'J')
1292 {
1293 return false;
1294 }
1295
1296 // Lite brightness response index is at position 2
1297 return extractIntValue(response, 2, brightness);
1298}
1299
1301{
1302 char command[MAXRBUF] = {0};
1303 char response[MAXRBUF] = {0};
1304
1305 if (value > GEMINI_MAX_BRIGHTNESS)
1306 {
1307 value = GEMINI_MAX_BRIGHTNESS;
1308 }
1309
1310 if (value < GEMINI_MIN_BRIGHTNESS)
1311 {
1312 value = GEMINI_MIN_BRIGHTNESS;
1313 }
1314
1315 if (!formatCommand('B', command, value))
1316 {
1317 return false;
1318 }
1319
1320 if (!sendCommand(command, response))
1321 {
1322 return false;
1323 }
1324
1325 if (strlen(response) < 3)
1326 {
1327 return false;
1328 }
1329
1330 if (response[0] != '*' || response[1] != 'B')
1331 {
1332 return false;
1333 }
1334
1335 int response_value;
1336 if (!extractIntValue(response, 2, &response_value))
1337 {
1338 return false;
1339 }
1340
1341 return (response_value == value);
1342}
1343
1345{
1346 char command[MAXRBUF] = {0};
1347 char response[MAXRBUF] = {0};
1348
1349 if (!formatCommand('L', command))
1350 {
1351 return false;
1352 }
1353
1354 if (!sendCommand(command, response))
1355 {
1356 return false;
1357 }
1358
1359 return (strlen(response) >= 3 && response[0] == '*' && response[1] == 'L');
1360}
1361
1363{
1364 char command[MAXRBUF] = {0};
1365 char response[MAXRBUF] = {0};
1366
1367 if (!formatCommand('D', command))
1368 {
1369 return false;
1370 }
1371
1372 if (!sendCommand(command, response))
1373 {
1374 return false;
1375 }
1376
1377 return (strlen(response) >= 3 && response[0] == '*' && response[1] == 'D');
1378}
1379
1381{
1382 char command[MAXRBUF] = {0};
1383 char response[MAXRBUF] = {0};
1384
1385 if (!formatCommand('O', command))
1386 {
1387 return false;
1388 }
1389
1390 if (!sendCommand(command, response, SERIAL_TIMEOUT_SEC_LONG))
1391 {
1392 return false;
1393 }
1394
1395 // Pro firmware can acknowledge with variants like *O, *O#, or *OOpened#
1396 return (response[0] == '*' && response[1] == 'O');
1397}
1398
1400{
1401 char command[MAXRBUF] = {0};
1402 char response[MAXRBUF] = {0};
1403
1404 if (!formatCommand('C', command))
1405 {
1406 return false;
1407 }
1408
1409 if (!sendCommand(command, response, SERIAL_TIMEOUT_SEC_LONG))
1410 {
1411 return false;
1412 }
1413
1414 // Pro firmware can acknowledge with variants like *C, *C#, or *CClosed#
1415 return (response[0] == '*' && response[1] == 'C');
1416}
1417
1418bool GeminiFlatpanelProAdapter::getStatus(int *coverStatus, int *lightStatus, int *motorStatus)
1419{
1420 char command[MAXRBUF] = {0};
1421 char response[MAXRBUF] = {0};
1422
1423 if (!formatCommand('S', command))
1424 {
1425 return false;
1426 }
1427
1428 if (!sendCommand(command, response))
1429 {
1430 return false;
1431 }
1432
1433 if (response[0] != '*' || response[1] != 'S')
1434 {
1435 return false;
1436 }
1437
1438 if (strlen(response) < 7)
1439 {
1440 return false;
1441 }
1442
1443 *motorStatus = response[2] - '0';
1444 *lightStatus = response[4] - '0';
1445 *coverStatus = response[6] - '0';
1446
1447 if (*motorStatus < GEMINI_MOTOR_STATUS_STOPPED || *motorStatus > GEMINI_MOTOR_STATUS_RUNNING)
1448 {
1449 return false;
1450 }
1451 if (*lightStatus < GEMINI_LIGHT_STATUS_OFF || *lightStatus > GEMINI_LIGHT_STATUS_ON)
1452 {
1453 return false;
1454 }
1455 if (*coverStatus < GEMINI_COVER_STATUS_MOVING || *coverStatus > GEMINI_COVER_STATUS_TIMED_OUT)
1456 {
1457 return false;
1458 }
1459
1460 return true;
1461}
1462
1463// Motion/calibration commands
1464bool GeminiFlatpanelProAdapter::move(uint16_t value, int direction)
1465{
1466 char command[MAXRBUF] = {0};
1467
1468 if (direction == -1)
1469 {
1470 snprintf(command, sizeof(command), ">M-%02d#", value);
1471 }
1472 else
1473 {
1474 snprintf(command, sizeof(command), ">M%03d#", value);
1475 }
1476
1477 char response[MAXRBUF] = {0};
1478
1479 return sendCommand(command, response, 30);
1480}
1481
1483{
1484 char command[MAXRBUF] = {0};
1485 char response[MAXRBUF] = {0};
1486
1487 if (!formatCommand('F', command))
1488 {
1489 return false;
1490 }
1491
1492 return sendCommand(command, response);
1493}
1494
1496{
1497 char command[MAXRBUF] = {0};
1498 char response[MAXRBUF] = {0};
1499
1500 if (!formatCommand('E', command))
1501 {
1502 return false;
1503 }
1504
1505 return sendCommand(command, response);
1506}
1507
1509{
1510 (void) enable;
1511 return false;
1512}
1513
1515{
1516 (void) mode;
1517 return false;
1518}
1519
1520// Helper methods for Pro
1521bool GeminiFlatpanelProAdapter::formatCommand(char commandLetter, char *commandString, int value)
1522{
1523 if (commandString == nullptr)
1524 {
1525 return false;
1526 }
1527
1528 // Pro format: >X# (no value) or >Xnnn# (with value) where X is command letter and nnn is value
1529 if (value == NO_VALUE)
1530 {
1531 // No value provided
1532 snprintf(commandString, MAXRBUF, ">%c#", commandLetter);
1533 }
1534 else
1535 {
1536 // Value provided
1537 snprintf(commandString, MAXRBUF, ">%c%d#", commandLetter, value);
1538 }
1539
1540 return true;
1541}
1542
1543bool GeminiFlatpanelProAdapter::extractIntValue(const char *response, int startPos, int *value)
1544{
1545 if (response == nullptr || value == nullptr)
1546 {
1547 return false;
1548 }
1549
1550 const char *terminator = strchr(response, '#');
1551 if (terminator == nullptr)
1552 {
1553 return false;
1554 }
1555
1556 // Calculate the length of the numeric part
1557 int length = terminator - (response + startPos);
1558
1559 if (length <= 0)
1560 {
1561 return false;
1562 }
1563
1564 // Extract the numeric part
1565 char value_str[MAXRBUF] = {0};
1566 strncpy(value_str, response + startPos, length);
1567 *value = atoi(value_str);
1568
1569 return true;
1570}
1571
1572bool GeminiFlatpanelProAdapter::sendCommand(const char *command, char *response, int timeout, bool log)
1573{
1574 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1575
1576 tcflush(portFD, TCIOFLUSH);
1577 if ((rc = tty_write_string(portFD, command, &nbytes_written)) != TTY_OK)
1578 {
1579 if (log)
1580 {
1581 char errstr[MAXRBUF] = {0};
1582 tty_error_msg(rc, errstr, MAXRBUF);
1583 // Note: Cannot use LOGF_ERROR here as we don't have access to logging
1584 // The main driver should handle logging of errors
1585 }
1586 return false;
1587 }
1588
1589 if (response == nullptr)
1590 return true;
1591
1592 if ((rc = tty_nread_section(portFD, response, MAXRBUF, getCommandTerminator(), timeout, &nbytes_read)) != TTY_OK)
1593 {
1594 if (log)
1595 {
1596 char errstr[MAXRBUF] = {0};
1597 tty_error_msg(rc, errstr, MAXRBUF);
1598 // Note: Cannot use LOGF_ERROR here as we don't have access to logging
1599 }
1600 return false;
1601 }
1602
1603 // Remove trailing newline character if present
1604 if (nbytes_read > 0 && response[nbytes_read - 1] == '\n')
1605 {
1606 response[nbytes_read - 1] = '\0';
1607 }
1608 tcflush(portFD, TCIOFLUSH);
1609
1610 if (response[0] != '*' || response[1] != command[1])
1611 {
1612 return false;
1613 }
1614
1615 return true;
1616}
1617
1619// GeminiFlatpanelSimulationAdapter Implementation
1621
1623 : simulateRev2Features(simulateRev2Features)
1624{
1625 // Initialize simulation state
1626 simulationValues[SIM_MOTOR] = GEMINI_MOTOR_STATUS_STOPPED;
1627 simulationValues[SIM_LIGHT] = GEMINI_LIGHT_STATUS_OFF;
1628 simulationValues[SIM_COVER] = GEMINI_COVER_STATUS_CLOSED;
1629 simulationValues[SIM_BRIGHTNESS] = 128;
1630 simulationValues[SIM_CONFIG_STATUS] = GEMINI_CONFIG_READY;
1631 simulationValues[SIM_BEEP_ENABLED] = 0; // Beep off by default
1632 simulationValues[SIM_BRIGHTNESS_MODE] = GEMINI_BRIGHTNESS_MODE_LOW;
1633
1634 // Set simulated revision and firmware version
1635 if (simulateRev2Features)
1636 {
1637 simulatedRevision = GEMINI_REVISION_2;
1638 simulatedFirmwareVersion = 450; // A version >= 402
1639 }
1640 else
1641 {
1642 simulatedRevision = GEMINI_REVISION_1;
1643 simulatedFirmwareVersion = 0; // Rev1 doesn't report version
1644 }
1645}
1646
1648{
1649 // Simulation always responds to ping
1650 return true;
1651}
1652
1654{
1655 return simulatedRevision;
1656}
1657
1659{
1660 if (!simulateRev2Features)
1661 {
1662 // Rev1 doesn't report firmware version
1663 return false;
1664 }
1665
1666 *version = simulatedFirmwareVersion;
1667 return true;
1668}
1669
1671{
1672 return simulateRev2Features;
1673}
1674
1676{
1677 return true; // Both revisions support dust cap
1678}
1679
1681{
1682 return simulateRev2Features;
1683}
1684
1686{
1687 *status = simulationValues[SIM_CONFIG_STATUS];
1688 return true;
1689}
1690
1692{
1693 *brightness = simulationValues[SIM_BRIGHTNESS];
1694 return true;
1695}
1696
1698{
1699 if (value < GEMINI_MIN_BRIGHTNESS || value > GEMINI_MAX_BRIGHTNESS)
1700 {
1701 return false;
1702 }
1703
1704 simulationValues[SIM_BRIGHTNESS] = value;
1705 return true;
1706}
1707
1709{
1710 simulationValues[SIM_LIGHT] = GEMINI_LIGHT_STATUS_ON;
1711 return true;
1712}
1713
1715{
1716 simulationValues[SIM_LIGHT] = GEMINI_LIGHT_STATUS_OFF;
1717 return true;
1718}
1719
1721{
1722 simulationValues[SIM_COVER] = GEMINI_COVER_STATUS_OPEN;
1723 return true;
1724}
1725
1727{
1728 simulationValues[SIM_COVER] = GEMINI_COVER_STATUS_CLOSED;
1729 return true;
1730}
1731
1732bool GeminiFlatpanelSimulationAdapter::getStatus(int *coverStatus, int *lightStatus, int *motorStatus)
1733{
1734 *coverStatus = simulationValues[SIM_COVER];
1735 *lightStatus = simulationValues[SIM_LIGHT];
1736 *motorStatus = simulationValues[SIM_MOTOR];
1737 return true;
1738}
1739
1740bool GeminiFlatpanelSimulationAdapter::move(uint16_t value, int direction)
1741{
1742 // Simulate brief motor activity
1743 simulationValues[SIM_MOTOR] = GEMINI_MOTOR_STATUS_RUNNING;
1744 // In real implementation, motor would stop after movement
1745 // For simulation, we immediately set it back to stopped
1746 simulationValues[SIM_MOTOR] = GEMINI_MOTOR_STATUS_STOPPED;
1747
1748 // Unused parameters
1749 (void)value;
1750 (void)direction;
1751
1752 return true;
1753}
1754
1756{
1757 return true;
1758}
1759
1761{
1762 return true;
1763}
1764
1766{
1767 if (!supportsBeep())
1768 {
1769 return false;
1770 }
1771
1772 simulationValues[SIM_BEEP_ENABLED] = enable ? 1 : 0;
1773 return true;
1774}
1775
1777{
1779 {
1780 return false;
1781 }
1782
1784 {
1785 return false;
1786 }
1787
1788 simulationValues[SIM_BRIGHTNESS_MODE] = mode;
1789 return true;
1790}
1791
1793{
1794 // Use Rev2 terminator for consistency (most features)
1795 return simulateRev2Features ? '#' : '\n';
1796}
1797
1799{
1800 // No actual communication setup needed for simulation
1801}
void log(const std::string &log)
Definition Utils.cpp:161
bool getBrightness(int *brightness) override
Get the current brightness level.
bool setBrightnessMode(int mode) override
Set the brightness mode (high/low)
bool openCover() override
Open the dust cover.
bool lightOff() override
Turn the light off.
bool closeCover() override
Close the dust cover.
bool lightOn() override
Turn the light on.
bool ping() override
Ping the device to check if it responds to this adapter's protocol.
char getCommandTerminator() const override
Get the command terminator character for this revision.
bool getConfigStatus(int *status) override
Get the current configuration status.
bool getStatus(int *coverStatus, int *lightStatus, int *motorStatus) override
Get the current device status.
bool setBrightness(int value) override
Set the brightness level.
bool setBeep(bool enable) override
Enable or disable beep functionality.
bool getFirmwareVersion(int *version) override
Get the firmware version from the device.
bool lightOff() override
Turn the light off.
bool setOpenPosition() override
Set the current position as the open position.
bool openCover() override
Open the dust cover.
bool setClosePosition() override
Set the current position as the closed position.
bool getBrightness(int *brightness) override
Get the current brightness level.
bool move(uint16_t value, int direction) override
Move the cover by a specified amount.
bool setBrightness(int value) override
Set the brightness level.
bool getConfigStatus(int *status) override
Get the current configuration status.
bool setBeep(bool enable) override
Enable or disable beep functionality.
bool lightOn() override
Turn the light on.
bool setBrightnessMode(int mode) override
Set the brightness mode (high/low)
char getCommandTerminator() const override
Get the command terminator character for this revision.
bool getStatus(int *coverStatus, int *lightStatus, int *motorStatus) override
Get the current device status.
bool getFirmwareVersion(int *version) override
Get the firmware version from the device.
bool closeCover() override
Close the dust cover.
bool ping() override
Ping the device to check if it responds to this adapter's protocol.
bool setClosePosition() override
Set the current position as the closed position.
bool getConfigStatus(int *status) override
Get the current configuration status.
bool move(uint16_t value, int direction) override
Move the cover by a specified amount.
bool getFirmwareVersion(int *version) override
Get the firmware version from the device.
bool lightOff() override
Turn the light off.
bool openCover() override
Open the dust cover.
bool lightOn() override
Turn the light on.
bool getBrightness(int *brightness) override
Get the current brightness level.
bool setBrightness(int value) override
Set the brightness level.
char getCommandTerminator() const override
Get the command terminator character for this revision.
bool closeCover() override
Close the dust cover.
bool getStatus(int *coverStatus, int *lightStatus, int *motorStatus) override
Get the current device status.
bool ping() override
Ping the device to check if it responds to this adapter's protocol.
bool setOpenPosition() override
Set the current position as the open position.
bool getStatus(int *coverStatus, int *lightStatus, int *motorStatus) override
Get the current device status.
bool lightOff() override
Turn the light off.
bool getConfigStatus(int *status) override
Get the current configuration status.
bool setBrightness(int value) override
Set the brightness level.
char getCommandTerminator() const override
Get the command terminator character for this revision.
bool setBrightnessMode(int mode) override
Set the brightness mode (high/low)
bool setClosePosition() override
Set the current position as the closed position.
bool setOpenPosition() override
Set the current position as the open position.
bool setBeep(bool enable) override
Enable or disable beep functionality.
bool openCover() override
Open the dust cover.
bool getFirmwareVersion(int *version) override
Get the firmware version from the device.
bool move(uint16_t value, int direction) override
Move the cover by a specified amount.
bool getBrightness(int *brightness) override
Get the current brightness level.
bool ping() override
Ping the device to check if it responds to this adapter's protocol.
bool lightOn() override
Turn the light on.
bool closeCover() override
Close the dust cover.
bool getBrightness(int *brightness) override
Get the current brightness level.
GeminiFlatpanelSimulationAdapter(bool simulateRev2Features=true)
bool supportsBrightnessMode() const override
Check if device supports brightness mode selection (high/low)
bool setBeep(bool enable) override
Enable or disable beep functionality.
bool setBrightnessMode(int mode) override
Set the brightness mode (high/low)
bool closeCover() override
Close the dust cover.
bool supportsDustCap() const override
Check if device supports dust cap functionality.
bool openCover() override
Open the dust cover.
int getRevision() const override
Get the firmware revision this adapter supports.
bool getFirmwareVersion(int *version) override
Get the firmware version from the device.
bool lightOn() override
Turn the light on.
bool getStatus(int *coverStatus, int *lightStatus, int *motorStatus) override
Get the current device status.
bool setClosePosition() override
Set the current position as the closed position.
void setupCommunication(int portFD) override
Set up communication parameters for this revision.
char getCommandTerminator() const override
Get the command terminator character for this revision.
bool setBrightness(int value) override
Set the brightness level.
bool supportsBeep() const override
Check if device supports beep functionality.
bool getConfigStatus(int *status) override
Get the current configuration status.
bool lightOff() override
Turn the light off.
bool move(uint16_t value, int direction) override
Move the cover by a specified amount.
bool setOpenPosition() override
Set the current position as the open position.
bool ping() override
Ping the device to check if it responds to this adapter's protocol.
#define NO_VALUE
#define SERIAL_TIMEOUT_SEC
@ GEMINI_COVER_STATUS_TIMED_OUT
@ GEMINI_COVER_STATUS_CLOSED
@ GEMINI_COVER_STATUS_OPEN
#define SERIAL_TIMEOUT_SEC_LONG
@ GEMINI_BRIGHTNESS_MODE_HIGH
@ GEMINI_BRIGHTNESS_MODE_LOW
@ GEMINI_MOTOR_STATUS_STOPPED
@ GEMINI_MOTOR_STATUS_RUNNING
@ GEMINI_LIGHT_STATUS_ON
@ GEMINI_LIGHT_STATUS_OFF
#define GEMINI_MAX_BRIGHTNESS
#define GEMINI_MIN_BRIGHTNESS
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
#define MAXRBUF
Definition indidriver.c:52