Instrument Neutral Distributed Interface INDI 2.2.5
spectracyber.cpp
Go to the documentation of this file.
1/*
2 Kuwait National Radio Observatory
3 INDI Driver for SpectraCyber Hydrogen Line Spectrometer
4 Communication: RS232 <---> USB
5
6 Copyright (C) 2009 Jasem Mutlaq ([email protected])
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
22 Change Log:
23
24 Format of BLOB data is:
25
26 ########### ####### ########## ## ###
27 Julian_Date Voltage Freqnuency RA DEC
28
29*/
30
31#include "spectracyber.h"
32
33#include "config.h"
34
35#include <indicom.h>
36
37#include <libnova/julian_day.h>
38
39#include <memory>
40#include <stdlib.h>
41#include <string.h>
42#include <termios.h>
43#include <unistd.h>
44
45#define mydev "SpectraCyber"
46#define BASIC_GROUP "Main Control"
47#define OPTIONS_GROUP "Options"
48
49#define CONT_CHANNEL 0
50#define SPEC_CHANNEL 1
51
52//const int SPECTROMETER_READ_BUFFER = 16;
56
57//const double SPECTROMETER_MIN_FREQ = 46.4;
58const double SPECTROMETER_REST_FREQ = 48.6;
59//const double SPECTROMETER_MAX_FREQ = 51.2;
60const double SPECTROMETER_RF_FREQ = 1371.805;
61
62const unsigned int SPECTROMETER_OFFSET = 0x050;
63
64/* 90 Khz Rest Correction */
65const double SPECTROMETER_REST_CORRECTION = 0.090;
66
67static const char *contFMT = ".ascii_cont";
68static const char *specFMT = ".ascii_spec";
69
70// We declare an auto pointer to spectrometer.
71std::unique_ptr<SpectraCyber> spectracyber(new SpectraCyber());
72
73/****************************************************************
74**
75**
76*****************************************************************/
78{
79 // Command pre-limiter
80 command[0] = '!';
81
82 telescopeID = nullptr;
83
84 srand(time(nullptr));
85
86 buildSkeleton("indi_spectracyber_sk.xml");
87
88 // Optional: Add aux controls for configuration, debug & simulation
90
91 setVersion(1, 3);
92}
93
94/****************************************************************
95**
96**
97*****************************************************************/
98void SpectraCyber::ISGetProperties(const char *dev)
99{
100 static int propInit = 0;
101
103
104 if (propInit == 0)
105 {
106 loadConfig();
107
108 propInit = 1;
109
110 auto tProp = getText("ACTIVE_DEVICES");
111
112 if (tProp)
113 {
114 telescopeID = tProp.findWidgetByName("ACTIVE_TELESCOPE");
115
116 if (telescopeID && strlen(telescopeID->text) > 0)
117 {
118 IDSnoopDevice(telescopeID->text, "EQUATORIAL_EOD_COORD");
119 }
120 }
121 }
122}
123
124/****************************************************************
125**
126**
127*****************************************************************/
129{
130 if (IUSnoopNumber(root, &EquatorialCoordsRNP) != 0)
131 {
132 LOG_WARN("Error processing snooped EQUATORIAL_EOD_COORD_REQUEST value! No RA/DEC information available.");
133
134 return true;
135 }
136 //else
137 // IDLog("Received RA: %g - DEC: %g\n", EquatorialCoordsRNP.np[0].value, EquatorialCoordsRNP.np[1].value);
138
139 return false;
140}
141
142/****************************************************************
143**
144**
145*****************************************************************/
146bool SpectraCyber::initProperties()
147{
148
150
151 FreqNP = getNumber("Freq (Mhz)");
152 if (!FreqNP)
153 LOG_ERROR("Error: Frequency property is missing. Spectrometer cannot be operated.");
154
155 ScanNP = getNumber("Scan Parameters");
156 if (!ScanNP)
157 LOG_ERROR("Error: Scan parameters property is missing. Spectrometer cannot be operated.");
158
159 ChannelSP = getSwitch("Channels");
160 if (!ChannelSP)
161 LOG_ERROR("Error: Channel property is missing. Spectrometer cannot be operated.");
162
163 ScanSP = getSwitch("Scan");
164 if (!ScanSP)
165 LOG_ERROR("Error: Channel property is missing. Spectrometer cannot be operated.");
166
167 DataStreamBP = getBLOB("Data");
168 if (!DataStreamBP)
169 LOG_ERROR("Error: BLOB data property is missing. Spectrometer cannot be operated.");
170
171 if (DataStreamBP)
172 DataStreamBP[0].setBlob((char *)malloc(MAXBLEN * sizeof(char)));
173
174 /**************************************************************************/
175 // Equatorial Coords - SET
176 IUFillNumber(&EquatorialCoordsRN[0], "RA", "RA H:M:S", "%10.6m", 0., 24., 0., 0.);
177 IUFillNumber(&EquatorialCoordsRN[1], "DEC", "Dec D:M:S", "%10.6m", -90., 90., 0., 0.);
178 IUFillNumberVector(&EquatorialCoordsRNP, EquatorialCoordsRN, NARRAY(EquatorialCoordsRN), "", "EQUATORIAL_EOD_COORD",
179 "Equatorial AutoSet", "", IP_RW, 0, IPS_IDLE);
180 /**************************************************************************/
181
183
184 return true;
185}
186
187/****************************************************************
188**
189**
190*****************************************************************/
192{
193 auto tProp = getText("DEVICE_PORT");
194
195 if (isConnected())
196 return true;
197
198 if (!tProp)
199 return false;
200
201 if (isSimulation())
202 {
203 LOGF_INFO("%s Spectrometer: Simulating connection to port %s.", type_name.c_str(),
204 tProp[0].getText());
206 return true;
207 }
208
209 if (tty_connect(tProp[0].getText(), 2400, 8, 0, 1, &fd) != TTY_OK)
210 {
211 LOGF_ERROR("Error connecting to port %s. Make sure you have BOTH read and write permission to the port.",
212 tProp[0].getText());
213 return false;
214 }
215
216 // We perform initial handshake check by resetting all parameter and watching for echo reply
217 if (reset() == true)
218 {
219 LOG_INFO("Spectrometer is online. Retrieving preliminary data...");
221 return init_spectrometer();
222 }
223 else
224 {
226 "Spectrometer echo test failed. Please recheck connection to spectrometer and try again.");
227 return false;
228 }
229}
230
231/****************************************************************
232**
233**
234*****************************************************************/
235bool SpectraCyber::init_spectrometer()
236{
237 // Enable speed mode
238 if (isSimulation())
239 {
240 LOGF_INFO("%s Spectrometer: Simulating spectrometer init.", type_name.c_str());
241 return true;
242 }
243
244 return true;
245}
246
247/****************************************************************
248**
249**
250*****************************************************************/
252{
253 tty_disconnect(fd);
254
255 return true;
256}
257
258/****************************************************************
259**
260**
261*****************************************************************/
262bool SpectraCyber::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
263{
264 if (strcmp(dev, getDeviceName()) != 0)
265 return false;
266
267 static double cont_offset = 0;
268 static double spec_offset = 0;
269
270 auto nProp = getNumber(name);
271
272 if (!nProp)
273 return false;
274
275 if (isConnected() == false)
276 {
278 LOG_ERROR("Spectrometer is offline. Connect before issuing any commands.");
279 return false;
280 }
281
282 // IF Gain
283 if (nProp.isNameMatch("70 Mhz IF"))
284 {
285 double last_value = nProp[0].getValue();
286
287 if (!nProp.update(values, names, n))
288 return false;
289
290 if (dispatch_command(IF_GAIN) == false)
291 {
292 nProp[0].setValue(last_value);
293 nProp.setState(IPS_ALERT);
294 nProp.apply("Error dispatching IF gain command to spectrometer. Check logs.");
295 return false;
296 }
297
298 nProp.setState(IPS_OK);
299 nProp.apply();
300
301 return true;
302 }
303
304 // DC Offset
305 if (nProp.isNameMatch("DC Offset"))
306 {
307 if (!nProp.update(values, names, n))
308 return false;
309
310 // Check which offset change, if none, return gracefully
311 if (nProp[CONTINUUM_CHANNEL].getValue() != cont_offset)
312 {
313 if (dispatch_command(CONT_OFFSET) == false)
314 {
315 nProp[CONTINUUM_CHANNEL].setValue(cont_offset);
316 nProp.setState(IPS_ALERT);
317 nProp.apply("Error dispatching continuum DC offset command to spectrometer. Check logs.");
318 return false;
319 }
320
321 cont_offset = nProp[CONTINUUM_CHANNEL].getValue();
322 }
323
324 if (nProp[SPECTRAL_CHANNEL].getValue() != spec_offset)
325 {
326 if (dispatch_command(SPEC_OFFSET) == false)
327 {
328 nProp[SPECTRAL_CHANNEL].setValue(spec_offset);
329 nProp.setState(IPS_ALERT);
330 nProp.apply("Error dispatching spectral DC offset command to spectrometer. Check logs.");
331 return false;
332 }
333
334 spec_offset = nProp[SPECTRAL_CHANNEL].getValue();
335 }
336
337 // No Change, return
338 nProp.setState(IPS_OK);
339 nProp.apply();
340 return true;
341 }
342
343 // Freq Change
344 if (nProp.isNameMatch("Freq (Mhz)"))
345 return update_freq(values[0]);
346
347 // Scan Options
348 if (nProp.isNameMatch("Scan Parameters"))
349 {
350 if (!nProp.update(values, names, n))
351 return false;
352
353 nProp.setState(IPS_OK);
354 nProp.apply();
355 return true;
356 }
357 return true;
358}
359
360/****************************************************************
361**
362**
363*****************************************************************/
364bool SpectraCyber::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
365{
366 if (strcmp(dev, getDeviceName()) != 0)
367 return false;
368
369 auto tProp = getText(name);
370
371 if (!tProp)
372 return false;
373
374 // Device Port Text
375 if (tProp.isNameMatch("DEVICE_PORT"))
376 {
377 if (!tProp.update(texts, names, n))
378 return false;
379
380 tProp.setState(IPS_OK);
381 tProp.apply("Port updated.");
382
383 return true;
384 }
385
386 // Telescope Source
387 if (tProp.isNameMatch("ACTIVE_DEVICES"))
388 {
389 telescopeID = tProp.findWidgetByName("ACTIVE_TELESCOPE");
390
391 if (telescopeID && strcmp(texts[0], telescopeID->text))
392 {
393 if (!tProp.update(texts, names, n))
394 return false;
395
396 snprintf(EquatorialCoordsRNP.device, MAXINDIDEVICE, "%s", tProp[0].getText());
397
398 LOGF_INFO("Active telescope updated to %s. Please save configuration.", telescopeID->text);
399
400 IDSnoopDevice(telescopeID->text, "EQUATORIAL_EOD_COORD");
401 }
402
403 tProp.setState(IPS_OK);
404 tProp.apply();
405 return true;
406 }
407
408 return true;
409}
410
411/****************************************************************
412**
413**
414*****************************************************************/
415bool SpectraCyber::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
416{
417 if (strcmp(dev, getDeviceName()) != 0)
418 return false;
419
420 // First process parent!
421 if (INDI::DefaultDevice::ISNewSwitch(getDeviceName(), name, states, names, n) == true)
422 return true;
423
424 auto sProp = getSwitch(name);
425
426 if (!sProp)
427 return false;
428
429 if (isConnected() == false)
430 {
432 LOG_ERROR("Spectrometer is offline. Connect before issuing any commands.");
433 return false;
434 }
435
436 // Scan
437 if (sProp.isNameMatch("Scan"))
438 {
439 if (!FreqNP || !DataStreamBP)
440 return false;
441
442 if (!sProp.update(states, names, n))
443 return false;
444
445 if (sProp[1].getState() == ISS_ON)
446 {
447 if (sProp.getState() == IPS_BUSY)
448 {
449 sProp.setState(IPS_IDLE);
450 FreqNP.setState(IPS_IDLE);
451 DataStreamBP.setState(IPS_IDLE);
452
453 FreqNP.apply();
454 DataStreamBP.apply();
455 sProp.apply("Scan stopped.");
456 return false;
457 }
458
459 sProp.setState(IPS_OK);
460 sProp.apply();
461 return true;
462 }
463
464 sProp.setState(IPS_BUSY);
465 DataStreamBP.setState(IPS_BUSY);
466
467 // Compute starting freq = base_freq - low
468 if (sProp[SPEC_CHANNEL].getState() == ISS_ON)
469 {
470 start_freq = (SPECTROMETER_RF_FREQ + SPECTROMETER_REST_FREQ) - abs((int)ScanNP[0].getValue()) / 1000.;
471 target_freq = (SPECTROMETER_RF_FREQ + SPECTROMETER_REST_FREQ) + abs((int)ScanNP[1].getValue()) / 1000.;
472 sample_rate = ScanNP[2].getValue() * 5;
473 FreqNP[0].setValue(start_freq);
474 FreqNP.setState(IPS_BUSY);
475 FreqNP.apply();
476 sProp.apply("Starting spectral scan from %g MHz to %g MHz in steps of %g KHz...", start_freq,
477 target_freq, sample_rate);
478 }
479 else
480 sProp.apply("Starting continuum scan @ %g MHz...", FreqNP[0].getValue());
481
482 return true;
483 }
484
485 // Continuum Gain Control
486 if (sProp.isNameMatch("Continuum Gain"))
487 {
488 int last_switch = sProp.findOnSwitchIndex();
489
490 if (!sProp.update(states, names, n))
491 return false;
492
493 if (dispatch_command(CONT_GAIN) == false)
494 {
495 sProp.setState(IPS_ALERT);
496 sProp.reset();
497 sProp[last_switch].setState(ISS_ON);
498 sProp.apply("Error dispatching continuum gain command to spectrometer. Check logs.");
499 return false;
500 }
501
502 sProp.setState(IPS_OK);
503 sProp.apply();
504
505 return true;
506 }
507
508 // Spectral Gain Control
509 if (sProp.isNameMatch("Spectral Gain"))
510 {
511 int last_switch = sProp.findOnSwitchIndex();
512
513 if (!sProp.update(states, names, n))
514 return false;
515
516 if (dispatch_command(SPEC_GAIN) == false)
517 {
518 sProp.setState(IPS_ALERT);
519 sProp.reset();
520 sProp[last_switch].setState(ISS_ON);
521 sProp.apply("Error dispatching spectral gain command to spectrometer. Check logs.");
522 return false;
523 }
524
525 sProp.setState(IPS_OK);
526 sProp.apply();
527
528 return true;
529 }
530
531 // Continuum Integration Control
532 if (sProp.isNameMatch("Continuum Integration (s)"))
533 {
534 int last_switch = sProp.findOnSwitchIndex();
535
536 if (!sProp.update(states, names, n))
537 return false;
538
539 if (dispatch_command(CONT_TIME) == false)
540 {
541 sProp.setState(IPS_ALERT);
542 sProp.reset();
543 sProp[last_switch].setState(ISS_ON);
544 sProp.apply("Error dispatching continuum integration command to spectrometer. Check logs.");
545 return false;
546 }
547
548 sProp.setState(IPS_OK);
549 sProp.apply();
550
551 return true;
552 }
553
554 // Spectral Integration Control
555 if (sProp.isNameMatch("Spectral Integration (s)"))
556 {
557 int last_switch = sProp.findOnSwitchIndex();
558
559 if (!sProp.update(states, names, n))
560 return false;
561
562 if (dispatch_command(SPEC_TIME) == false)
563 {
564 sProp.setState(IPS_ALERT);
565 sProp.reset();
566 sProp[last_switch].setState(ISS_ON);
567 sProp.apply("Error dispatching spectral integration command to spectrometer. Check logs.");
568 return false;
569 }
570
571 sProp.setState(IPS_OK);
572 sProp.apply();
573
574 return true;
575 }
576
577 // Bandwidth Control
578 if (sProp.isNameMatch("Bandwidth (Khz)"))
579 {
580 int last_switch = sProp.findOnSwitchIndex();
581
582 if (!sProp.update(states, names, n))
583 return false;
584
585 if (dispatch_command(BANDWIDTH) == false)
586 {
587 sProp.setState(IPS_ALERT);
588 sProp.reset();
589 sProp[last_switch].setState(ISS_ON);
590 sProp.apply("Error dispatching bandwidth change command to spectrometer. Check logs.");
591 return false;
592 }
593
594 sProp.setState(IPS_OK);
595 sProp.apply();
596
597 return true;
598 }
599
600 // Channel selection
601 if (sProp.isNameMatch("Channels"))
602 {
603 static int lastChannel;
604
605 lastChannel = sProp.findOnSwitchIndex();
606
607 if (!sProp.update(states, names, n))
608 return false;
609
610 sProp.setState(IPS_OK);
611 if (ScanSP.getState() == IPS_BUSY && lastChannel != sProp.findOnSwitchIndex())
612 {
613 abort_scan();
614 sProp.apply("Scan aborted due to change of channel selection.");
615 }
616 else
617 sProp.apply();
618
619 return true;
620 }
621
622 // Reset
623 if (sProp.isNameMatch("Reset"))
624 {
625 if (reset() == true)
626 {
627 sProp.setState(IPS_OK);
628 sProp.apply();
629 }
630 else
631 {
632 sProp.setState(IPS_ALERT);
633 sProp.apply("Error dispatching reset parameter command to spectrometer. Check logs.");
634 return false;
635 }
636 return true;
637 }
638
639 return true;
640}
641
642bool SpectraCyber::dispatch_command(SpectrometerCommand command_type)
643{
644 char spectrometer_error[SPECTROMETER_ERROR_BUFFER];
645 int err_code = 0, nbytes_written = 0, final_value = 0;
646 // Maximum of 3 hex digits in addition to null terminator
647 char hex[5];
648
649 tcflush(fd, TCIOFLUSH);
650
651 switch (command_type)
652 {
653 // Intermediate Frequency Gain
654 case IF_GAIN: {
655 auto prop = getNumber("70 Mhz IF");
656 if (!prop)
657 return false;
658 command[1] = 'A';
659 command[2] = '0';
660 // Equation is
661 // Value = ((X - 10) * 63) / 15.75, where X is the user selection (10dB to 25.75dB)
662 final_value = (int)((prop[0].getValue() - 10) * 63) / 15.75;
663 sprintf(hex, "%02X", (uint16_t)final_value);
664 command[3] = hex[0];
665 command[4] = hex[1];
666 break;
667 }
668
669 // Continuum Gain
670 case CONT_GAIN: {
671 auto prop = getSwitch("Continuum Gain");
672 if (!prop)
673 return false;
674 command[1] = 'G';
675 command[2] = '0';
676 command[3] = '0';
677 final_value = prop.findOnSwitchIndex();
678 sprintf(hex, "%d", (uint8_t)final_value);
679 command[4] = hex[0];
680 break;
681 }
682
683 // Continuum Integration
684 case CONT_TIME: {
685 auto prop = getSwitch("Continuum Integration (s)");
686 if (!prop)
687 return false;
688 command[1] = 'I';
689 command[2] = '0';
690 command[3] = '0';
691 final_value = prop.findOnSwitchIndex();
692 sprintf(hex, "%d", (uint8_t)final_value);
693 command[4] = hex[0];
694 break;
695 }
696
697 // Spectral Gain
698 case SPEC_GAIN: {
699 auto prop = getSwitch("Spectral Gain");
700 if (!prop)
701 return false;
702 command[1] = 'K';
703 command[2] = '0';
704 command[3] = '0';
705 final_value = prop.findOnSwitchIndex();
706 sprintf(hex, "%d", (uint8_t)final_value);
707 command[4] = hex[0];
708 break;
709 }
710
711 // Spectral Integration
712 case SPEC_TIME: {
713 auto prop = getSwitch("Spectral Integration (s)");
714 if (!prop)
715 return false;
716 command[1] = 'L';
717 command[2] = '0';
718 command[3] = '0';
719 final_value = prop.findOnSwitchIndex();
720 sprintf(hex, "%d", (uint8_t)final_value);
721 command[4] = hex[0];
722 break;
723 }
724
725 // Continuum DC Offset
726 case CONT_OFFSET: {
727 auto prop = getNumber("DC Offset");
728 if (!prop)
729 return false;
730 command[1] = 'O';
731 final_value = (int)prop[CONTINUUM_CHANNEL].getValue() / 0.001;
732 sprintf(hex, "%03X", (uint32_t)final_value);
733 command[2] = hex[0];
734 command[3] = hex[1];
735 command[4] = hex[2];
736 break;
737 }
738
739 // Spectral DC Offset
740 case SPEC_OFFSET: {
741 auto prop = getNumber("DC Offset");
742 if (!prop)
743 return false;
744 command[1] = 'J';
745 final_value = (int)prop[SPECTRAL_CHANNEL].getValue() / 0.001;
746 sprintf(hex, "%03X", (uint32_t)final_value);
747 command[2] = hex[0];
748 command[3] = hex[1];
749 command[4] = hex[2];
750 break;
751 }
752
753 // FREQ
754 case RECV_FREQ: {
755 command[1] = 'F';
756 // Each value increment is 5 Khz. Range is 050h to 3e8h.
757 // 050h corresponds to 46.4 Mhz (min), 3e8h to 51.2 Mhz (max)
758 // To compute the desired received freq, we take the diff (target - min) / 0.005
759 // 0.005 Mhz = 5 Khz
760 // Then we add the diff to 050h (80 in decimal) to get the final freq.
761 // e.g. To set 50.00 Mhz, diff = 50 - 46.4 = 3.6 / 0.005 = 800 = 320h
762 // Freq = 320h + 050h (or 800 + 80) = 370h = 880 decimal
763
764 final_value = (int)((FreqNP[0].getValue() + SPECTROMETER_REST_CORRECTION - FreqNP[0].getMin()) / 0.005 +
766 sprintf(hex, "%03X", (uint32_t)final_value);
767 if (isDebug())
768 IDLog("Required Freq is: %.3f --- Min Freq is: %.3f --- Spec Offset is: %d -- Final Value (Dec): %d "
769 "--- Final Value (Hex): %s\n",
770 FreqNP[0].getValue(), FreqNP[0].getMin(), SPECTROMETER_OFFSET, final_value, hex);
771 command[2] = hex[0];
772 command[3] = hex[1];
773 command[4] = hex[2];
774 break;
775 }
776
777 // Read Channel
778 case READ_CHANNEL: {
779 command[1] = 'D';
780 command[2] = '0';
781 command[3] = '0';
782 final_value = ChannelSP.findOnSwitchIndex();
783 command[4] = (final_value == 0) ? '0' : '1';
784 break;
785 }
786
787 // Bandwidth
788 case BANDWIDTH: {
789 auto prop = getSwitch("Bandwidth (Khz)");
790 if (!prop)
791 return false;
792 command[1] = 'B';
793 command[2] = '0';
794 command[3] = '0';
795 final_value = prop.findOnSwitchIndex();
796 //sprintf(hex, "%x", final_value);
797 command[4] = (final_value == 0) ? '0' : '1';
798 break;
799 }
800
801 // Reset
802 case RESET:
803 command[1] = 'R';
804 command[2] = '0';
805 command[3] = '0';
806 command[4] = '0';
807 break;
808
809 // Noise source
810 case NOISE_SOURCE:
811 // TODO: Do something here?
812 break;
813 }
814
815 if (isDebug())
816 IDLog("Dispatching command #%s#\n", command);
817
818 if (isSimulation())
819 return true;
820
821 if ((err_code = tty_write(fd, command, SPECTROMETER_CMD_LEN, &nbytes_written) != TTY_OK))
822 {
823 tty_error_msg(err_code, spectrometer_error, SPECTROMETER_ERROR_BUFFER);
824 if (isDebug())
825 IDLog("TTY error detected: %s\n", spectrometer_error);
826 return false;
827 }
828
829 return true;
830}
831
832int SpectraCyber::get_on_switch(ISwitchVectorProperty *sp)
833{
834 for (int i = 0; i < sp->nsp; i++)
835 if (sp->sp[i].s == ISS_ON)
836 return i;
837
838 return -1;
839}
840
842{
843 double last_value = FreqNP[0].getValue();
844
845 if (nFreq < FreqNP[0].getMin() || nFreq > FreqNP[0].getMax())
846 return false;
847
848 FreqNP[0].setValue(nFreq);
849
850 if (dispatch_command(RECV_FREQ) == false)
851 {
852 FreqNP[0].setValue(last_value);
853 FreqNP.setState(IPS_ALERT);
854 FreqNP.apply("Error dispatching RECV FREQ command to spectrometer. Check logs.");
855 return false;
856 }
857
858 if (ScanSP.getState() != IPS_BUSY)
859 FreqNP.setState(IPS_OK);
860
861 FreqNP.apply();
862
863 // Delay 0.5s for INT
864 usleep(500000);
865 return true;
866}
867
868bool SpectraCyber::reset()
869{
870 int err_code = 0, nbytes_read = 0;
871 char response[4];
872 char err_msg[SPECTROMETER_ERROR_BUFFER];
873
874 if (isDebug())
875 IDLog("Attempting to write to spectrometer....\n");
876
877 dispatch_command(RESET);
878
879 if (isDebug())
880 IDLog("Attempting to read from spectrometer....\n");
881
882 // Read echo from spectrometer, we're expecting R000
883 if ((err_code = tty_read(fd, response, SPECTROMETER_CMD_REPLY, 5, &nbytes_read)) != TTY_OK)
884 {
885 tty_error_msg(err_code, err_msg, 32);
886 if (isDebug())
887 IDLog("TTY error detected: %s\n", err_msg);
888 return false;
889 }
890
891 if (isDebug())
892 IDLog("Response from Spectrometer: #%c# #%c# #%c# #%c#\n", response[0], response[1], response[2], response[3]);
893
894 if (strstr(response, "R000"))
895 {
896 if (isDebug())
897 IDLog("Echo test passed.\n");
898
900
901 return true;
902 }
903
904 if (isDebug())
905 IDLog("Echo test failed.\n");
906 return false;
907}
908
910{
911 if (!isConnected())
912 return;
913
914 char RAStr[16], DecStr[16];
915
916 switch (ScanSP.getState())
917 {
918 case IPS_BUSY:
919 if (ChannelSP[CONT_CHANNEL].getState() == ISS_ON)
920 break;
921
922 if (FreqNP[0].getValue() >= target_freq)
923 {
924 ScanSP.setState(IPS_OK);
925 FreqNP.setState(IPS_OK);
926
927 FreqNP.apply();
928 ScanSP.apply("Scan complete.");
930 return;
931 }
932
933 if (update_freq(FreqNP[0].getValue()) == false)
934 {
935 abort_scan();
937 return;
938 }
939
940 FreqNP[0].setValue(FreqNP[0].getValue() + sample_rate / 1000.);
941 break;
942
943 default:
944 break;
945 }
946
947 switch (DataStreamBP.getState())
948 {
949 case IPS_BUSY: {
950 if (ScanSP.getState() != IPS_BUSY)
951 {
952 DataStreamBP.setState(IPS_IDLE);
953 DataStreamBP.apply();
954 break;
955 }
956
957 if (read_channel() == false)
958 {
959 DataStreamBP.setState(IPS_ALERT);
960
961 if (ScanSP.getState() == IPS_BUSY)
962 abort_scan();
963
964 DataStreamBP.apply();
965 }
966
967 JD = ln_get_julian_from_sys();
968
969 // Continuum
970 if (ChannelSP[0].getState() == ISS_ON)
971 DataStreamBP[0].setFormat(contFMT);
972 else
973 DataStreamBP[0].setFormat(specFMT);
974
975 fs_sexa(RAStr, EquatorialCoordsRN[0].value, 2, 3600);
976 fs_sexa(DecStr, EquatorialCoordsRN[1].value, 2, 3600);
977
978 if (telescopeID && strlen(telescopeID->text) > 0)
979 snprintf(bLine, MAXBLEN, "%.8f %.3f %.3f %s %s", JD, chanValue, FreqNP[0].getValue(), RAStr, DecStr);
980 else
981 snprintf(bLine, MAXBLEN, "%.8f %.3f %.3f", JD, chanValue, FreqNP[0].getValue());
982
983 auto bLineSize = strlen(bLine);
984 DataStreamBP[0].setBlobLen(bLineSize);
985 DataStreamBP[0].setSize(bLineSize);
986 memcpy(DataStreamBP[0].getBlob(), bLine, bLineSize);
987
988 //IDLog("\nSTRLEN: %d -- BLOB:'%s'\n", strlen(bLine), (char *) DataStreamBP->bp[0].blob);
989
990 DataStreamBP.apply();
991
992 break;
993 }
994 default:
995 break;
996 }
997
999}
1000
1001void SpectraCyber::abort_scan()
1002{
1003 FreqNP.setState(IPS_IDLE);
1004 ScanSP.setState(IPS_ALERT);
1005
1006 ScanSP.reset();
1007 ScanSP[1].setState(ISS_ON);
1008
1009 FreqNP.apply();
1010 ScanSP.apply("Scan aborted due to errors.");
1011}
1012
1013bool SpectraCyber::read_channel()
1014{
1015 int err_code = 0, nbytes_read = 0;
1016 char response[SPECTROMETER_CMD_REPLY];
1017 char err_msg[SPECTROMETER_ERROR_BUFFER];
1018
1019 if (isSimulation())
1020 {
1021 chanValue = ((double)rand()) / ((double)RAND_MAX) * 10.0;
1022 return true;
1023 }
1024
1025 dispatch_command(READ_CHANNEL);
1026 if ((err_code = tty_read(fd, response, SPECTROMETER_CMD_REPLY, 5, &nbytes_read)) != TTY_OK)
1027 {
1028 tty_error_msg(err_code, err_msg, 32);
1029 if (isDebug())
1030 IDLog("TTY error detected: %s\n", err_msg);
1031 return false;
1032 }
1033
1034 if (isDebug())
1035 IDLog("Response from Spectrometer: #%s#\n", response);
1036
1037 int result = 0;
1038 sscanf(response, "D%x", &result);
1039 // We divide by 409.5 to scale the value to 0 - 10 VDC range
1040 chanValue = result / 409.5;
1041
1042 return true;
1043}
1044
1046{
1047 return mydev;
1048}
bool isConnected() const
INDI::PropertyNumber getNumber(const char *name) const
bool buildSkeleton(const char *filename)
Build driver properties from a skeleton file.
const char * getDeviceName() const
INDI::PropertySwitch getSwitch(const char *name) const
INDI::PropertyBlob getBLOB(const char *name) const
INDI::PropertyText getText(const char *name) const
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
bool isSimulation() const
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
virtual bool loadDefaultConfig()
Load the default configuration file.
void resetProperties()
Set all properties to IDLE state.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool update_freq(double nFreq)
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool Disconnect() override
Disconnect from device.
virtual bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual const char * getDefaultName() override
ISState
Switch state.
Definition indiapi.h:148
@ ISS_ON
Definition indiapi.h:150
#define MAXINDIDEVICE
Definition indiapi.h:191
#define NARRAY(a)
Handy macro to find the number of elements in array a[]. Must be used with actual array,...
Definition indiapi.h:498
@ IP_RW
Definition indiapi.h:184
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
int tty_connect(const char *device, int bit_rate, int word_size, int parity, int stop_bits, int *fd)
Establishes a tty connection to a terminal device.
Definition indicom.c:1022
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_disconnect(int fd)
Closes a tty connection and flushes the bus.
Definition indicom.c:1224
void IDLog(const char *fmt,...)
Definition indicom.c:305
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
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:490
int IUSnoopNumber(XMLEle *root, INumberVectorProperty *nvp)
Update a snooped number vector property from the given XML root element.
Definition indidevapi.c:555
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition indidevapi.c:398
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition indidriver.c:143
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
Definition indilogger.h:56
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
const double SPECTROMETER_REST_CORRECTION
const double SPECTROMETER_REST_FREQ
#define SPEC_CHANNEL
const unsigned int SPECTROMETER_OFFSET
const int SPECTROMETER_CMD_REPLY
const int SPECTROMETER_CMD_LEN
const double SPECTROMETER_RF_FREQ
const int SPECTROMETER_ERROR_BUFFER
#define CONT_CHANNEL
std::unique_ptr< SpectraCyber > spectracyber(new SpectraCyber())
#define MAXBLEN
char device[MAXINDIDEVICE]
Definition indiapi.h:319
Switch vector property descriptor.
Definition indiapi.h:365
ISState s
Definition indiapi.h:353
#define mydev
Definition tcfs.cpp:32