37#include <libnova/julian_day.h>
45#define mydev "SpectraCyber"
46#define BASIC_GROUP "Main Control"
47#define OPTIONS_GROUP "Options"
67static const char *contFMT =
".ascii_cont";
68static const char *specFMT =
".ascii_spec";
82 telescopeID =
nullptr;
100 static int propInit = 0;
110 auto tProp =
getText(
"ACTIVE_DEVICES");
114 telescopeID = tProp.findWidgetByName(
"ACTIVE_TELESCOPE");
116 if (telescopeID && strlen(telescopeID->text) > 0)
132 LOG_WARN(
"Error processing snooped EQUATORIAL_EOD_COORD_REQUEST value! No RA/DEC information available.");
146bool SpectraCyber::initProperties()
153 LOG_ERROR(
"Error: Frequency property is missing. Spectrometer cannot be operated.");
157 LOG_ERROR(
"Error: Scan parameters property is missing. Spectrometer cannot be operated.");
161 LOG_ERROR(
"Error: Channel property is missing. Spectrometer cannot be operated.");
165 LOG_ERROR(
"Error: Channel property is missing. Spectrometer cannot be operated.");
167 DataStreamBP =
getBLOB(
"Data");
169 LOG_ERROR(
"Error: BLOB data property is missing. Spectrometer cannot be operated.");
172 DataStreamBP[0].setBlob((
char *)malloc(
MAXBLEN *
sizeof(
char)));
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",
193 auto tProp =
getText(
"DEVICE_PORT");
203 LOGF_INFO(
"%s Spectrometer: Simulating connection to port %s.", type_name.c_str(),
211 LOGF_ERROR(
"Error connecting to port %s. Make sure you have BOTH read and write permission to the port.",
219 LOG_INFO(
"Spectrometer is online. Retrieving preliminary data...");
221 return init_spectrometer();
226 "Spectrometer echo test failed. Please recheck connection to spectrometer and try again.");
235bool SpectraCyber::init_spectrometer()
240 LOGF_INFO(
"%s Spectrometer: Simulating spectrometer init.", type_name.c_str());
267 static double cont_offset = 0;
268 static double spec_offset = 0;
278 LOG_ERROR(
"Spectrometer is offline. Connect before issuing any commands.");
283 if (nProp.isNameMatch(
"70 Mhz IF"))
285 double last_value = nProp[0].getValue();
287 if (!nProp.update(values, names, n))
290 if (dispatch_command(
IF_GAIN) ==
false)
292 nProp[0].setValue(last_value);
294 nProp.apply(
"Error dispatching IF gain command to spectrometer. Check logs.");
305 if (nProp.isNameMatch(
"DC Offset"))
307 if (!nProp.update(values, names, n))
317 nProp.apply(
"Error dispatching continuum DC offset command to spectrometer. Check logs.");
330 nProp.apply(
"Error dispatching spectral DC offset command to spectrometer. Check logs.");
344 if (nProp.isNameMatch(
"Freq (Mhz)"))
348 if (nProp.isNameMatch(
"Scan Parameters"))
350 if (!nProp.update(values, names, n))
375 if (tProp.isNameMatch(
"DEVICE_PORT"))
377 if (!tProp.update(texts, names, n))
381 tProp.apply(
"Port updated.");
387 if (tProp.isNameMatch(
"ACTIVE_DEVICES"))
389 telescopeID = tProp.findWidgetByName(
"ACTIVE_TELESCOPE");
391 if (telescopeID && strcmp(texts[0], telescopeID->text))
393 if (!tProp.update(texts, names, n))
398 LOGF_INFO(
"Active telescope updated to %s. Please save configuration.", telescopeID->text);
432 LOG_ERROR(
"Spectrometer is offline. Connect before issuing any commands.");
437 if (sProp.isNameMatch(
"Scan"))
439 if (!FreqNP || !DataStreamBP)
442 if (!sProp.update(states, names, n))
445 if (sProp[1].getState() ==
ISS_ON)
454 DataStreamBP.
apply();
455 sProp.apply(
"Scan stopped.");
472 sample_rate = ScanNP[2].getValue() * 5;
473 FreqNP[0].setValue(start_freq);
476 sProp.apply(
"Starting spectral scan from %g MHz to %g MHz in steps of %g KHz...", start_freq,
477 target_freq, sample_rate);
480 sProp.apply(
"Starting continuum scan @ %g MHz...", FreqNP[0].getValue());
486 if (sProp.isNameMatch(
"Continuum Gain"))
488 int last_switch = sProp.findOnSwitchIndex();
490 if (!sProp.update(states, names, n))
493 if (dispatch_command(
CONT_GAIN) ==
false)
497 sProp[last_switch].setState(
ISS_ON);
498 sProp.apply(
"Error dispatching continuum gain command to spectrometer. Check logs.");
509 if (sProp.isNameMatch(
"Spectral Gain"))
511 int last_switch = sProp.findOnSwitchIndex();
513 if (!sProp.update(states, names, n))
516 if (dispatch_command(
SPEC_GAIN) ==
false)
520 sProp[last_switch].setState(
ISS_ON);
521 sProp.apply(
"Error dispatching spectral gain command to spectrometer. Check logs.");
532 if (sProp.isNameMatch(
"Continuum Integration (s)"))
534 int last_switch = sProp.findOnSwitchIndex();
536 if (!sProp.update(states, names, n))
539 if (dispatch_command(
CONT_TIME) ==
false)
543 sProp[last_switch].setState(
ISS_ON);
544 sProp.apply(
"Error dispatching continuum integration command to spectrometer. Check logs.");
555 if (sProp.isNameMatch(
"Spectral Integration (s)"))
557 int last_switch = sProp.findOnSwitchIndex();
559 if (!sProp.update(states, names, n))
562 if (dispatch_command(
SPEC_TIME) ==
false)
566 sProp[last_switch].setState(
ISS_ON);
567 sProp.apply(
"Error dispatching spectral integration command to spectrometer. Check logs.");
578 if (sProp.isNameMatch(
"Bandwidth (Khz)"))
580 int last_switch = sProp.findOnSwitchIndex();
582 if (!sProp.update(states, names, n))
585 if (dispatch_command(
BANDWIDTH) ==
false)
589 sProp[last_switch].setState(
ISS_ON);
590 sProp.apply(
"Error dispatching bandwidth change command to spectrometer. Check logs.");
601 if (sProp.isNameMatch(
"Channels"))
603 static int lastChannel;
605 lastChannel = sProp.findOnSwitchIndex();
607 if (!sProp.update(states, names, n))
611 if (ScanSP.
getState() ==
IPS_BUSY && lastChannel != sProp.findOnSwitchIndex())
614 sProp.apply(
"Scan aborted due to change of channel selection.");
623 if (sProp.isNameMatch(
"Reset"))
633 sProp.apply(
"Error dispatching reset parameter command to spectrometer. Check logs.");
642bool SpectraCyber::dispatch_command(SpectrometerCommand command_type)
645 int err_code = 0, nbytes_written = 0, final_value = 0;
649 tcflush(fd, TCIOFLUSH);
651 switch (command_type)
662 final_value = (int)((prop[0].getValue() - 10) * 63) / 15.75;
663 sprintf(hex,
"%02X", (uint16_t)final_value);
677 final_value = prop.findOnSwitchIndex();
678 sprintf(hex,
"%d", (uint8_t)final_value);
685 auto prop =
getSwitch(
"Continuum Integration (s)");
691 final_value = prop.findOnSwitchIndex();
692 sprintf(hex,
"%d", (uint8_t)final_value);
705 final_value = prop.findOnSwitchIndex();
706 sprintf(hex,
"%d", (uint8_t)final_value);
713 auto prop =
getSwitch(
"Spectral Integration (s)");
719 final_value = prop.findOnSwitchIndex();
720 sprintf(hex,
"%d", (uint8_t)final_value);
732 sprintf(hex,
"%03X", (uint32_t)final_value);
746 sprintf(hex,
"%03X", (uint32_t)final_value);
766 sprintf(hex,
"%03X", (uint32_t)final_value);
768 IDLog(
"Required Freq is: %.3f --- Min Freq is: %.3f --- Spec Offset is: %d -- Final Value (Dec): %d "
769 "--- Final Value (Hex): %s\n",
783 command[4] = (final_value == 0) ?
'0' :
'1';
789 auto prop =
getSwitch(
"Bandwidth (Khz)");
795 final_value = prop.findOnSwitchIndex();
797 command[4] = (final_value == 0) ?
'0' :
'1';
816 IDLog(
"Dispatching command #%s#\n", command);
825 IDLog(
"TTY error detected: %s\n", spectrometer_error);
834 for (
int i = 0; i < sp->
nsp; i++)
843 double last_value = FreqNP[0].getValue();
845 if (nFreq < FreqNP[0].getMin() || nFreq > FreqNP[0].getMax())
848 FreqNP[0].setValue(nFreq);
850 if (dispatch_command(
RECV_FREQ) ==
false)
852 FreqNP[0].setValue(last_value);
854 FreqNP.
apply(
"Error dispatching RECV FREQ command to spectrometer. Check logs.");
868bool SpectraCyber::reset()
870 int err_code = 0, nbytes_read = 0;
875 IDLog(
"Attempting to write to spectrometer....\n");
877 dispatch_command(
RESET);
880 IDLog(
"Attempting to read from spectrometer....\n");
887 IDLog(
"TTY error detected: %s\n", err_msg);
892 IDLog(
"Response from Spectrometer: #%c# #%c# #%c# #%c#\n", response[0], response[1], response[2], response[3]);
894 if (strstr(response,
"R000"))
897 IDLog(
"Echo test passed.\n");
905 IDLog(
"Echo test failed.\n");
914 char RAStr[16], DecStr[16];
922 if (FreqNP[0].getValue() >= target_freq)
928 ScanSP.
apply(
"Scan complete.");
940 FreqNP[0].setValue(FreqNP[0].getValue() + sample_rate / 1000.);
953 DataStreamBP.
apply();
957 if (read_channel() ==
false)
964 DataStreamBP.
apply();
967 JD = ln_get_julian_from_sys();
970 if (ChannelSP[0].getState() ==
ISS_ON)
971 DataStreamBP[0].setFormat(contFMT);
973 DataStreamBP[0].setFormat(specFMT);
975 fs_sexa(RAStr, EquatorialCoordsRN[0].value, 2, 3600);
976 fs_sexa(DecStr, EquatorialCoordsRN[1].value, 2, 3600);
978 if (telescopeID && strlen(telescopeID->text) > 0)
979 snprintf(bLine,
MAXBLEN,
"%.8f %.3f %.3f %s %s", JD, chanValue, FreqNP[0].getValue(), RAStr, DecStr);
981 snprintf(bLine,
MAXBLEN,
"%.8f %.3f %.3f", JD, chanValue, FreqNP[0].getValue());
983 auto bLineSize = strlen(bLine);
984 DataStreamBP[0].setBlobLen(bLineSize);
985 DataStreamBP[0].setSize(bLineSize);
986 memcpy(DataStreamBP[0].getBlob(), bLine, bLineSize);
990 DataStreamBP.
apply();
1001void SpectraCyber::abort_scan()
1010 ScanSP.
apply(
"Scan aborted due to errors.");
1013bool SpectraCyber::read_channel()
1015 int err_code = 0, nbytes_read = 0;
1021 chanValue = ((double)rand()) / ((
double)RAND_MAX) * 10.0;
1030 IDLog(
"TTY error detected: %s\n", err_msg);
1035 IDLog(
"Response from Spectrometer: #%s#\n", response);
1038 sscanf(response,
"D%x", &result);
1040 chanValue = result / 409.5;
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
int findOnSwitchIndex() const
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
#define NARRAY(a)
Handy macro to find the number of elements in array a[]. Must be used with actual array,...
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.
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
int tty_disconnect(int fd)
Closes a tty connection and flushes the bus.
void IDLog(const char *fmt,...)
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
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[].
Implementations for common driver routines.
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.
int IUSnoopNumber(XMLEle *root, INumberVectorProperty *nvp)
Update a snooped number vector property from the given XML root element.
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.
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...
#define LOGF_INFO(fmt,...)
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)
const double SPECTROMETER_REST_CORRECTION
const double SPECTROMETER_REST_FREQ
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
std::unique_ptr< SpectraCyber > spectracyber(new SpectraCyber())
char device[MAXINDIDEVICE]
Switch vector property descriptor.