38static std::unique_ptr<WandererETA> wandererETA(
new WandererETA());
53 return "Wanderer ETA M54";
67 Position1NP[0].
fill(
"POINT_1",
"Position (mm)",
"%.3f", 0.000, 1.200, 0.001, 0.000);
70 Position2NP[0].
fill(
"POINT_2",
"Position (mm)",
"%.3f", 0.000, 1.200, 0.001, 0.000);
73 Position3NP[0].
fill(
"POINT_3",
"Position (mm)",
"%.3f", 0.000, 1.200, 0.001, 0.000);
77 PositionReadNP[POINT_1].
fill(
"POINT_1_READ",
"Point 1 (mm)",
"%.3f", 0.000, 1.200, 0.000, 0.000);
78 PositionReadNP[POINT_2].
fill(
"POINT_2_READ",
"Point 2 (mm)",
"%.3f", 0.000, 1.200, 0.000, 0.000);
79 PositionReadNP[POINT_3].
fill(
"POINT_3_READ",
"Point 3 (mm)",
"%.3f", 0.000, 1.200, 0.000, 0.000);
83 FirmwareTP[FIRMWARE_VERSION].
fill(
"FIRMWARE_VERSION",
"Firmware Version",
"Unknown");
87 ZeroAllSP[ZERO_ALL].
fill(
"ZERO_ALL",
"Zero All Points",
ISS_OFF);
91 BackfocusOffsetNP[0].
fill(
"OFFSET_MM",
"Offset (mm)",
"%.3f", -1.200, 1.200, 0.010, 0.000);
94 ApplyOffsetSP[APPLY_OFFSET].
fill(
"APPLY_OFFSET",
"Apply Offset",
ISS_OFF);
120 char firmwareStr[16];
121 snprintf(firmwareStr,
sizeof(firmwareStr),
"%d", firmware);
122 FirmwareTP[FIRMWARE_VERSION].setText(firmwareStr);
123 LOGF_INFO(
"Firmware version: %d", firmware);
152bool WandererETA::getData()
154 if (!serialPortMutex.try_lock_for(std::chrono::milliseconds(500)))
156 LOG_DEBUG(
"Serial port is busy, skipping status update");
160 std::lock_guard<std::timed_mutex> lock(serialPortMutex, std::adopt_lock);
172 tcflush(PortFD, TCIOFLUSH);
177 char firstBuffer[512] = {0};
179 for (
int attempt = 0; attempt < 3; attempt++)
181 memset(firstBuffer, 0,
sizeof(firstBuffer));
184 if (firstRc ==
TTY_OK && parseDeviceData(firstBuffer))
186 LOG_INFO(
"Wanderer ETA M54 is online.");
191 tcflush(PortFD, TCIOFLUSH);
193 LOG_WARN(
"Handshake: could not get valid data after 3 attempts");
198 int nbytes_read = 0, rc = -1;
204 LOG_DEBUG(
"Timeout reading from device, will try again later");
210 LOGF_ERROR(
"Failed to read data from device. Error: %s", errorMessage);
214 return parseDeviceData(buffer);
216 catch (std::exception &e)
218 LOG_ERROR(
"Exception occurred while reading data from device");
228bool WandererETA::parseDeviceData(
const char *data)
232 std::vector<std::string> tokens;
234 std::istringstream tokenStream(data);
238 while (std::getline(tokenStream, token,
'A'))
241 tokens.push_back(token);
245 if (tokens.size() < 5)
247 LOGF_DEBUG(
"Insufficient tokens: %d (need 5)",
static_cast<int>(tokens.size()));
252 if (tokens[0] !=
"WandererTilterM54")
256 LOGF_DEBUG(
"Handshake: skipping invalid data: %s", tokens[0].c_str());
258 LOGF_WARN(
"Unknown device: %s (expected WandererTilterM54)", tokens[0].c_str());
263 firmware = std::atoi(tokens[1].c_str());
265 char firmwareStr[16];
266 snprintf(firmwareStr,
sizeof(firmwareStr),
"%d", firmware);
267 FirmwareTP[FIRMWARE_VERSION].setText(firmwareStr);
272 double pos1 = std::strtod(tokens[2].c_str(),
nullptr);
273 double pos2 = std::strtod(tokens[3].c_str(),
nullptr);
274 double pos3 = std::strtod(tokens[4].c_str(),
nullptr);
276 PositionReadNP[POINT_1].setValue(pos1);
277 PositionReadNP[POINT_2].setValue(pos2);
278 PositionReadNP[POINT_3].setValue(pos3);
280 PositionReadNP.
apply();
284 catch (std::exception &e)
286 LOG_ERROR(
"Failed to parse device data");
294void WandererETA::setPositionNP(
int index,
double values[],
char *names[],
int n,
IPState state)
299 Position1NP.
update(values, names, n);
304 Position2NP.
update(values, names, n);
309 Position3NP.
update(values, names, n);
319void WandererETA::setPositionState(
int index,
IPState state)
348 BackfocusOffsetNP.
update(values, names, n);
350 BackfocusOffsetNP.
apply();
366 double target = values[0];
371 setPositionNP(pointIndex, values, names, n,
IPS_OK);
376 if (target < 0.0 || target > 1.2)
378 LOGF_ERROR(
"Position value %.3f out of range (0.000 - 1.200 mm)", target);
379 setPositionNP(pointIndex, values, names, n,
IPS_ALERT);
386 LOG_WARN(
"A move is already in progress. Please wait for it to complete.");
387 setPositionState(pointIndex,
IPS_IDLE);
391 setPositionNP(pointIndex, values, names, n,
IPS_BUSY);
392 startMove(pointIndex, target);
412 LOG_WARN(
"A move is already in progress. Please wait for it to complete.");
416 double offset = BackfocusOffsetNP[0].getValue();
418 if (std::abs(offset) < 0.001)
420 LOG_WARN(
"Offset is zero, nothing to apply");
423 ApplyOffsetSP.
apply();
428 double pos1 = PositionReadNP[POINT_1].getValue();
429 double pos2 = PositionReadNP[POINT_2].getValue();
430 double pos3 = PositionReadNP[POINT_3].getValue();
433 double targets[3] = {pos1 + offset, pos2 + offset, pos3 + offset};
436 if (targets[0] < 0.0 || targets[0] > 1.2 ||
437 targets[1] < 0.0 || targets[1] > 1.2 ||
438 targets[2] < 0.0 || targets[2] > 1.2)
440 LOGF_ERROR(
"Offset %.3f mm would move one or more points out of range (0.000 - 1.200 mm). "
441 "Current positions: P1=%.3f, P2=%.3f, P3=%.3f",
442 offset, pos1, pos2, pos3);
445 ApplyOffsetSP.
apply();
449 LOGF_INFO(
"Applying backfocus offset %.3f mm to all points (P1: %.3f→%.3f, P2: %.3f→%.3f, P3: %.3f→%.3f)",
450 offset, pos1, targets[0], pos2, targets[1], pos3, targets[2]);
453 ApplyOffsetSP.
apply();
454 startMultiPointMove(targets,
false,
true);
462 LOG_WARN(
"A move is already in progress. Please wait for it to complete.");
466 double targets[3] = {0.0, 0.0, 0.0};
467 LOG_INFO(
"Moving all points to zero position");
471 startMultiPointMove(targets,
true,
false);
482void WandererETA::startMove(
int pointIndex,
double target)
484 m_Move.active =
true;
485 m_Move.isZeroAll =
false;
486 m_Move.isApplyOffset =
false;
487 m_Move.target[pointIndex] = target;
488 m_Move.pending[0] = m_Move.pending[1] = m_Move.pending[2] =
false;
489 m_Move.pending[pointIndex] =
true;
490 m_Move.graceCount = 3;
493 double distance = std::abs(target - PositionReadNP[pointIndex].getValue());
494 m_Move.timeoutCount[pointIndex] =
static_cast<int>((distance / MOTOR_SPEED_MMS + TIMEOUT_SAFETY_S) / 2.0);
495 m_Move.startPosition[pointIndex] = PositionReadNP[pointIndex].getValue();
497 sendPointCommand(pointIndex, target);
498 LOGF_INFO(
"Moving Point %d to %.3f mm", pointIndex + 1, target);
504void WandererETA::startMultiPointMove(
double targets[3],
bool isZeroAll,
bool isApplyOffset)
506 m_Move.active =
true;
507 m_Move.isZeroAll = isZeroAll;
508 m_Move.isApplyOffset = isApplyOffset;
509 m_Move.currentPoint = 0;
510 m_Move.graceCount = 3;
512 for (
int i = 0; i < 3; i++)
514 m_Move.target[i] = targets[i];
515 m_Move.pending[i] =
true;
516 m_Move.startPosition[i] = PositionReadNP[i].getValue();
517 double distance = std::abs(targets[i] - m_Move.startPosition[i]);
518 m_Move.timeoutCount[i] =
static_cast<int>((distance / MOTOR_SPEED_MMS + TIMEOUT_SAFETY_S) / 2.0);
527 sendPointCommand(0, targets[0]);
528 LOGF_INFO(
"Moving Point 1 to %.3f mm", targets[0]);
534void WandererETA::sendPointCommand(
int pointIndex,
double target)
537 snprintf(
cmd,
sizeof(
cmd),
"%d%.3f\n", pointIndex + 1, target);
541 tcflush(PortFD, TCIFLUSH);
544 int nbytes_written = 0, rc = -1;
549 LOGF_ERROR(
"Serial write error on Point %d: %s", pointIndex + 1, errorMessage);
550 m_Move.pending[pointIndex] =
false;
559void WandererETA::checkMoveProgress()
565 if (m_Move.graceCount > 0)
573 for (
int i = 0; i < 3; i++)
575 if (!m_Move.pending[i])
579 double currentPos = PositionReadNP[i].getValue();
582 if (std::abs(currentPos - m_Move.target[i]) < POSITION_TOLERANCE)
584 m_Move.pending[i] =
false;
585 setPositionState(i,
IPS_OK);
586 LOGF_INFO(
"Point %d reached target %.3f mm", i + 1, m_Move.target[i]);
589 if ((m_Move.isZeroAll || m_Move.isApplyOffset) && i == m_Move.currentPoint)
591 m_Move.currentPoint++;
592 if (m_Move.currentPoint < 3 && m_Move.pending[m_Move.currentPoint])
594 sendPointCommand(m_Move.currentPoint, m_Move.target[m_Move.currentPoint]);
595 m_Move.graceCount = 3;
596 double dist = std::abs(m_Move.target[m_Move.currentPoint] - m_Move.startPosition[m_Move.currentPoint]);
597 m_Move.timeoutCount[m_Move.currentPoint] =
static_cast<int>((dist / MOTOR_SPEED_MMS + TIMEOUT_SAFETY_S) / 2.0);
598 LOGF_INFO(
"Moving Point %d to %.3f mm", m_Move.currentPoint + 1,
599 m_Move.target[m_Move.currentPoint]);
607 if (m_Move.timeoutCount[i] > 0)
609 m_Move.timeoutCount[i]--;
614 double finalPos = PositionReadNP[i].getValue();
615 m_Move.pending[i] =
false;
616 if (std::abs(finalPos - m_Move.target[i]) < POSITION_TOLERANCE)
618 setPositionState(i,
IPS_OK);
619 LOGF_INFO(
"Point %d reached target %.3f mm", i + 1, m_Move.target[i]);
623 LOGF_WARN(
"Point %d timeout: reported %.3f mm (target %.3f mm) — motor likely completed, verify by reconnecting",
624 i + 1, finalPos, m_Move.target[i]);
625 setPositionState(i,
IPS_OK);
629 if ((m_Move.isZeroAll || m_Move.isApplyOffset) && i == m_Move.currentPoint)
631 m_Move.currentPoint++;
632 if (m_Move.currentPoint < 3 && m_Move.pending[m_Move.currentPoint])
634 sendPointCommand(m_Move.currentPoint, m_Move.target[m_Move.currentPoint]);
635 m_Move.graceCount = 3;
636 double dist = std::abs(m_Move.target[m_Move.currentPoint] - m_Move.startPosition[m_Move.currentPoint]);
637 m_Move.timeoutCount[m_Move.currentPoint] =
static_cast<int>((dist / MOTOR_SPEED_MMS + TIMEOUT_SAFETY_S) / 2.0);
638 LOGF_INFO(
"Moving Point %d to %.3f mm", m_Move.currentPoint + 1,
639 m_Move.target[m_Move.currentPoint]);
646 if (allDone || (!m_Move.pending[0] && !m_Move.pending[1] && !m_Move.pending[2]))
648 m_Move.active =
false;
650 if (m_Move.isZeroAll)
652 updateAllTargets(m_Move.target);
653 LOG_INFO(
"All points moved to zero position successfully");
658 else if (m_Move.isApplyOffset)
660 updateAllTargets(m_Move.target);
661 BackfocusOffsetNP[0].setValue(0.0);
663 BackfocusOffsetNP.
apply();
664 LOGF_INFO(
"Backfocus offset applied successfully. New positions: P1=%.3f, P2=%.3f, P3=%.3f",
665 m_Move.target[0], m_Move.target[1], m_Move.target[2]);
668 ApplyOffsetSP.
apply();
676void WandererETA::updateAllTargets(
double targets[3])
678 Position1NP[0].setValue(targets[0]);
681 Position2NP[0].setValue(targets[1]);
684 Position3NP[0].setValue(targets[2]);
692bool WandererETA::sendCommand(std::string command)
694 std::lock_guard<std::timed_mutex> lock(serialPortMutex);
696 int nbytes_written = 0, rc = -1;
697 std::string command_termination =
"\n";
702 tcflush(PortFD, TCIFLUSH);
708 LOGF_ERROR(
"Serial write error: %s", errorMessage);
731 m_Initializing =
false;
747 Position1NP.
save(fp);
748 Position2NP.
save(fp);
749 Position3NP.
save(fp);
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
const char * getDeviceName() const
virtual bool updateProperties()
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)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
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 deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
uint32_t getPollingPeriod() const
getPollingPeriod Return the polling period.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
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
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
virtual bool saveConfigItems(FILE *fp) override
Save config items.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Handle new number values (position targets)
WandererETA()
Constructor.
const char * getDefaultName() override
Device name.
virtual bool initProperties() override
Init properties.
virtual void TimerHit() override
Timer callback - poll device status.
virtual bool updateProperties() override
Update properties on connect/disconnect.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Handle new switch values (Zero All)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
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_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string 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.
#define LOGF_INFO(fmt,...)
#define LOGF_WARN(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,...)
std::vector< uint8_t > buffer