32#define WANDERER_TIMEOUT 5
34#define WANDERER_STATUS_TIMEOUT 12
36#define WANDERER_CALIB_REPLY_TIMEOUT 10
37#define WANDERER_MAX_FILTERS 8
39#define WANDERER_CMD_ZERO_DETECT "1002"
51 return "Wanderer Snowflake Filter Wheel";
69 IUFillNumber(&DeviceIDN[0],
"DEVICE_ID",
"ID",
"%0.0f", 0, 10, 1, 0);
73 IUFillText(&DeviceInfoT[0],
"MODEL",
"Model",
"");
74 IUFillText(&DeviceInfoT[1],
"FIRMWARE",
"Firmware",
"");
91 DeviceIDN[0].value = mDeviceID;
110 sendAutomaticCalibration();
114 tcflush(
PortFD, TCIOFLUSH);
117 if (!readCurrentFilterFromStatus(current))
119 LOG_ERROR(
"Wanderer Snowflake model (WSFW508/WSFW368) not found in status. Wrong port or device.");
129 char field[64] = {0};
142 while (nbytes > 0 && (field[nbytes - 1] ==
'A' || field[nbytes - 1] ==
'\r' || field[nbytes - 1] ==
'\n'))
145 field[nbytes] =
'\0';
147 mDeviceID = std::atoi(field);
157 if (!sendAutomaticCalibration())
158 LOG_WARN(
"Automatic calibration (1500002) failed at connect; use Calibration → Auto calibrate.");
160 LOG_INFO(
"Automatic calibration (1500002) sent at connection.");
165bool WandererSnowflakeFW::sendAutomaticCalibration()
169 LOG_INFO(
"Automatic calibration (1500002, simulation).");
175 LOG_ERROR(
"Failed to send automatic calibration command (1500002).");
178 LOG_INFO(
"Automatic calibration command (1500002) sent.");
182bool WandererSnowflakeFW::sendZeroDetection()
201 constexpr int maxAttempts = 80;
202 for (
int attempts = 0; attempts < maxAttempts; ++attempts)
205 if (readCurrentFilterFromStatus(current) && current == 1)
216 LOG_WARN(
"Zero detection: timed out waiting for filter 1; wheel may still be moving.");
222 if (dev !=
nullptr && std::strcmp(dev,
getDeviceName()) == 0)
224 if (std::strcmp(CalibrationCmdSP.
name, name) == 0)
227 const bool wantCal = (CalibrationCmdS[0].s ==
ISS_ON);
228 const bool wantZero = (CalibrationCmdS[1].s ==
ISS_ON);
236 ok = sendAutomaticCalibration();
238 ok = sendZeroDetection();
251 if (dev !=
nullptr && std::strcmp(dev,
getDeviceName()) == 0)
254 if (std::strcmp(DeviceIDNP.
name, name) == 0)
257 int id =
static_cast<int>(DeviceIDN[0].value);
258 if (id < 0 || id > 10)
266 snprintf(
cmd,
sizeof(
cmd),
"%d", 1900000 +
id);
320 LOG_ERROR(
"Failed to send move command to Wanderer Snowflake filter wheel.");
324 tcflush(
PortFD, TCIFLUSH);
327 constexpr int maxAttempts = 80;
329 while (attempts++ < maxAttempts)
332 if (readCurrentFilterFromStatus(current) && current ==
TargetFilter)
344 LOG_ERROR(
"Timed out waiting for Wanderer Snowflake filter wheel to reach target position.");
348bool WandererSnowflakeFW::sendCommand(
const char *command,
char *response,
int responseLen,
int timeoutSeconds)
350 int nbytes_written = 0;
358 const int linelen = std::snprintf(line,
sizeof(line),
"%s\r", command);
359 if (linelen <= 0 || linelen >=
static_cast<int>(
sizeof(line)))
361 LOG_ERROR(
"Wanderer Snowflake command too long for transmit buffer.");
365 if (response !=
nullptr && responseLen > 1)
366 tcflush(
PortFD, TCIFLUSH);
372 LOGF_ERROR(
"Error sending command '%s' to Wanderer Snowflake filter wheel: %s", command, errstr);
376 if (response ==
nullptr || responseLen <= 1)
386 LOGF_DEBUG(
"No CR/LF line after command '%s' (status stream may omit it while moving): %s", command, errstr);
391 while (nbytes_read > 0 && (response[nbytes_read - 1] ==
'\r' || response[nbytes_read - 1] ==
'\n'))
394 response[nbytes_read] =
'\0';
396 if (nbytes_read >= responseLen)
397 nbytes_read = responseLen - 1;
398 response[nbytes_read] =
'\0';
415void wandererStripFieldDelimiter(
char *field,
int &nbytes_read,
char delimiter)
417 while (nbytes_read > 0)
419 char c = field[nbytes_read - 1];
420 if (c == delimiter || c ==
'\r' || c ==
'\n')
423 field[nbytes_read] =
'\0';
428 field[nbytes_read] =
'\0';
431char *wandererTrimLeft(
char *s)
434 while (*p ==
' ' || *p ==
'\t' || *p ==
'\r' || *p ==
'\n')
437 std::memmove(s, p, std::strlen(p) + 1);
441bool wandererFieldLooksLikeModel(
char *field,
int nbytes_read)
443 if (nbytes_read <= 0)
445 field[nbytes_read] =
'\0';
446 wandererTrimLeft(field);
447 return (strncasecmp(field,
"WSFW508", 7) == 0 || strncasecmp(field,
"WSFW368", 7) == 0);
451bool WandererSnowflakeFW::readCurrentFilterFromStatus(
int &position)
455 char field[64] = {0};
460 constexpr int maxResync = 32;
461 bool haveModel =
false;
463 for (
int skip = 0; skip < maxResync; ++skip)
469 LOGF_DEBUG(
"Failed to read Wanderer Snowflake status field: %s", errstr);
472 wandererStripFieldDelimiter(field, nbytes_read,
'A');
474 if (wandererFieldLooksLikeModel(field,
static_cast<int>(std::strlen(field))))
476 snprintf(mModel,
sizeof(mModel),
"%s", field);
481 LOGF_DEBUG(
"Skipping Wanderer Snowflake status field while resyncing: '%s'", field);
490 LOGF_DEBUG(
"Failed to read firmware field from Wanderer Snowflake status: %s", errstr);
493 wandererStripFieldDelimiter(field, nbytes_read,
'A');
494 snprintf(mFirmware,
sizeof(mFirmware),
"%s", field);
496 double fwVersion = std::atof(field);
497 if (fwVersion < 20260124.0)
499 LOGF_ERROR(
"Wanderer Snowflake firmware %.0f is too old (minimum 20260124). "
500 "Please update the firmware in WandererEmpire.", fwVersion);
508 LOGF_DEBUG(
"Failed to read current filter field from Wanderer Snowflake status: %s", errstr);
511 wandererStripFieldDelimiter(field, nbytes_read,
'A');
513 int current = std::atoi(field);
519 LOGF_DEBUG(
"Received invalid current filter value '%s' from Wanderer Snowflake status.", field);
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
bool isSimulation() const
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
INDI::PropertyNumber FilterSlotNP
INDI::PropertyText FilterNameTP
void SelectFilterDone(int newpos)
The child class calls this function when the hardware successfully finished selecting a new filter wh...
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Connection::Serial * serialConnection
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
void setFilterConnection(const uint8_t &value)
setFilterConnection Set Filter connection mode. Child class should call this in the constructor befor...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
int PortFD
For Serial & TCP connections.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
const char * getDefaultName() override
int QueryFilter() override
Return current filter position.
bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
bool SelectFilter(int position) override
Select a new filter position.
bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
bool Handshake() override
perform handshake with device to check communication
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
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.
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
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 IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
#define LOGF_INFO(fmt,...)
#define LOGF_DEBUG(fmt,...)
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)
#define WANDERER_MAX_FILTERS
#define WANDERER_STATUS_TIMEOUT
#define WANDERER_CMD_ZERO_DETECT