Instrument Neutral Distributed Interface INDI 2.2.5
planewave_efa.cpp
Go to the documentation of this file.
1/*
2 PlaneWave EFA Protocol
3
4 Hendrick Focuser
5
6 Copyright (C) 2020-2027 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*/
23
24#include "planewave_efa.h"
25#include "indicom.h"
27
28#include <cmath>
29#include <memory>
30#include <cstring>
31#include <termios.h>
32#include <unistd.h>
33#include <regex>
34#include <sys/ioctl.h>
35
36static std::unique_ptr<EFA> steelDrive(new EFA());
37
53
55{
57
58 // Focuser Information
59 InfoTP[INFO_VERSION].fill("INFO_VERSION", "Version", "NA");
60 InfoTP.fill(getDeviceName(), "INFO", "Info", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
61
62 // Focuser temperature
63 TemperatureNP[TEMPERATURE_PRIMARY].fill("TEMPERATURE_PRIMARY", "Primary (c)", "%.2f", -50, 70., 0., 0.);
64 TemperatureNP[TEMPERATURE_AMBIENT].fill("TEMPERATURE_AMBIENT", "Ambient (c)", "%.2f", -50, 70., 0., 0.);
65 TemperatureNP.fill(getDeviceName(), "FOCUS_TEMPERATURE", "Temperature",
67
68 // Fan Control
69 FanStateSP[FAN_ON].fill("FAN_ON", "On", ISS_OFF);
70 FanStateSP[FAN_OFF].fill("FAN_OFF", "Off", ISS_ON);
71 FanStateSP.fill(getDeviceName(), "FOCUS_FAN", "Fans", FAN_TAB, IP_RW, ISR_1OFMANY, 0,
72 IPS_IDLE);
73
74 // Fan Control Mode
75 FanControlSP[FAN_MANUAL].fill("FAN_MANUAL", "Manual", ISS_ON);
76 FanControlSP[FAN_AUTOMATIC_ABSOLUTE].fill("FAN_AUTOMATIC_ABSOLUTE", "Auto. Absolute", ISS_OFF);
77 FanControlSP[FAN_AUTOMATIC_RELATIVE].fill("FAN_AUTOMATIC_RELATIVE", "Auto. Relative", ISS_OFF);
78 FanControlSP.fill(getDeviceName(), "FOCUS_FAN_CONTROL", "Control Mode", FAN_TAB, IP_RW,
80
81 // Fan Control Parameters
82 FanControlNP[FAN_MAX_ABSOLUTE].fill("FAN_MAX_ABSOLUTE", "Max Primary (c)", "%.2f", 0, 50., 5., 25.);
83 FanControlNP[FAN_MAX_RELATIVE].fill("FAN_MAX_RELATIVE", "Max Relative (c)", "%.2f", 0., 30., 1., 2.5);
84 FanControlNP[FAN_DEADZONE].fill("FAN_DEADZONE", "Deadzone (c)", "%.2f", 0.1, 10, 0.5, 0.5);
85 FanControlNP.fill(getDeviceName(), "FOCUS_FAN_PARAMS", "Control Params",
86 FAN_TAB, IP_RW, 0, IPS_IDLE);
87
88 // Fan Off on Disconnect
89 FanDisconnectSP[FAN_OFF_ON_DISCONNECT].fill("FAN_OFF_ON_DISCONNECT", "Switch Off", ISS_ON);
90 FanDisconnectSP.fill(getDeviceName(), "FOCUS_FAN_DISCONNECT", "On Disconnect", FAN_TAB,
92
93 // Calibration Control
94 CalibrationStateSP[CALIBRATION_ON].fill("CALIBRATION_ON", "Calibrated", ISS_OFF);
95 CalibrationStateSP[CALIBRATION_OFF].fill("CALIBRATION_OFF", "Not Calibrated", ISS_ON);
96 CalibrationStateSP.fill(getDeviceName(), "FOCUS_CALIBRATION", "Calibration",
98
99 // Approach Direction: determines from which direction the focuser always reaches a target.
100 // This provides consistent positioning and effective hardware-level backlash compensation.
101 ApproachDirectionSP[APPROACH_POSITIVE].fill("APPROACH_POSITIVE", "Positive (Outward)", ISS_ON);
102 ApproachDirectionSP[APPROACH_NEGATIVE].fill("APPROACH_NEGATIVE", "Negative (Inward)", ISS_OFF);
103 ApproachDirectionSP.fill(getDeviceName(), "FOCUS_APPROACH", "Approach Direction",
105
106 // Stop Detection: enables hardware end-of-travel limit-switch detection.
107 StopDetectSP[STOP_DETECT_ON].fill("STOP_DETECT_ON", "Enabled", ISS_ON);
108 StopDetectSP[STOP_DETECT_OFF].fill("STOP_DETECT_OFF", "Disabled", ISS_OFF);
109 StopDetectSP.fill(getDeviceName(), "FOCUS_STOP_DETECT", "Stop Detection",
111
112 // Setup focuser speed limits (rates 1–9 supported by the EFA hardware)
113 FocusSpeedNP[0].setMin(1);
114 FocusSpeedNP[0].setMax(9);
115 FocusSpeedNP[0].setValue(4);
116 FocusSpeedNP[0].setStep(1);
117
118 // Setup limits
119 FocusMaxPosNP[0].setValue(1e7);
120 FocusMaxPosNP[0].setMax(1e7);
121 FocusMaxPosNP[0].setStep(FocusMaxPosNP[0].getMax() / 50);
122
123 FocusAbsPosNP[0].setMax(1e7);
124 FocusAbsPosNP[0].setStep(FocusAbsPosNP[0].getMax() / 50);
125
126 FocusSyncNP[0].setMax(1e7);
127 FocusSyncNP[0].setStep(FocusSyncNP[0].getMax() / 50);
128
129 FocusRelPosNP[0].setMax(FocusAbsPosNP[0].getMax() / 2);
130 FocusRelPosNP[0].setStep(FocusRelPosNP[0].getMax() / 50);
131
135
136 return true;
137}
138
143{
145
146 if (isConnected())
147 {
148 getStartupValues();
149
150 defineProperty(InfoTP);
151 defineProperty(CalibrationStateSP);
152
153 // Fan
154 defineProperty(FanStateSP);
155 defineProperty(FanControlSP);
156 FanControlSP.load();
157 defineProperty(FanDisconnectSP);
158
159 defineProperty(TemperatureNP);
160
161 // Options
162 defineProperty(ApproachDirectionSP);
163 defineProperty(StopDetectSP);
164 }
165 else
166 {
167 deleteProperty(InfoTP);
168 deleteProperty(CalibrationStateSP);
169
170 deleteProperty(FanStateSP);
171 deleteProperty(FanControlSP);
172 deleteProperty(FanControlNP);
173 deleteProperty(FanDisconnectSP);
174
175 deleteProperty(TemperatureNP);
176
177 deleteProperty(ApproachDirectionSP);
178 deleteProperty(StopDetectSP);
179 }
180
181 return true;
182}
183
188{
189 if (FanDisconnectSP[FAN_OFF_ON_DISCONNECT].getState() == ISS_ON)
190 setFanEnabled(false);
191
193}
194
199{
200 std::string version;
201
202 // Lower RTS so the serial port is not monopolized and the hand controller can work.
203 // This must be done AFTER the port is open (i.e. here in Handshake, not in initProperties).
204 int bits = TIOCM_RTS;
205 (void) ioctl(PortFD, TIOCMBIC, &bits);
206
207 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
208
209 cmd[0] = DRIVER_SOM;
210 cmd[1] = 0x03;
211 cmd[2] = DEVICE_PC;
212 cmd[3] = DEVICE_FOC;
213 cmd[4] = GET_VERSION;
214 cmd[5] = calculateCheckSum(cmd, len);
215
216 if (!validateLengths(cmd, len))
217 return false;
218
219 if (!sendCommand(cmd, res, len, DRIVER_LEN))
220 return false;
221
222 version = std::to_string(res[5]) + "." + std::to_string(res[6]);
223 InfoTP[INFO_VERSION].setText(version.c_str());
224
225 LOGF_INFO("Detected version %s", version.c_str());
226
227 return true;
228}
229
234{
235 return "PlaneWave EFA";
236}
237
241bool EFA::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
242{
243 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
244 {
245 // Calibration State
246 if (CalibrationStateSP.isNameMatch(name))
247 {
248 bool enabled = strcmp(CalibrationStateSP[CALIBRATION_ON].getName(), IUFindOnSwitchName(states, names, n)) == 0;
249 if (setCalibrationEnabled(enabled))
250 {
251 CalibrationStateSP.update(states, names, n);
252 CalibrationStateSP.setState(IPS_OK);
253 }
254 else
255 {
256 CalibrationStateSP.setState(IPS_ALERT);
257 }
258
259 CalibrationStateSP.apply();
260 return true;
261 }
262 // Fan State
263 else if (FanStateSP.isNameMatch(name))
264 {
265 if (FanControlSP[FAN_MANUAL].getState() == ISS_OFF)
266 {
267 FanStateSP.setState(IPS_IDLE);
268 LOG_WARN("Cannot control fan while manual control is turned off.");
269 FanControlSP.apply();
270 return true;
271 }
272
273 bool enabled = strcmp(FanStateSP[FAN_ON].getName(), IUFindOnSwitchName(states, names, n)) == 0;
274 if (setFanEnabled(enabled))
275 {
276 FanStateSP.update(states, names, n);
277 FanStateSP.setState(enabled ? IPS_OK : IPS_IDLE);
278 }
279 else
280 {
281 FanStateSP.setState(IPS_ALERT);
282 }
283
284 FanStateSP.apply();
285 return true;
286 }
287 // Fan Control
288 else if (FanControlSP.isNameMatch(name))
289 {
290 FanControlSP.update(states, names, n);
291 if (FanControlSP[FAN_MANUAL].getState() == ISS_ON)
292 {
293 deleteProperty(FanControlNP);
294 LOG_INFO("Fan is now controlled manually.");
295 }
296 else
297 {
298 LOG_INFO("Fan is now controlled automatically per the control parameters.");
299 defineProperty(FanControlNP);
300 }
301
302 FanControlSP.setState(IPS_OK);
303 FanControlSP.apply();
304 return true;
305 }
306 // Fan Disconnect
307 else if (FanDisconnectSP.isNameMatch(name))
308 {
309 FanDisconnectSP.update(states, names, n);
310
311 if (FanDisconnectSP[FAN_OFF_ON_DISCONNECT].getState() == ISS_ON)
312 LOG_INFO("Fan shall be turned off upon device disconnection.");
313 else
314 LOG_INFO("Fan shall left as-is upon device disconnection.");
315
316 FanDisconnectSP.setState(IPS_OK);
317 FanDisconnectSP.apply();
318 return true;
319 }
320 // Approach Direction
321 else if (ApproachDirectionSP.isNameMatch(name))
322 {
323 const bool positiveApproach =
324 strcmp(ApproachDirectionSP[APPROACH_POSITIVE].getName(), IUFindOnSwitchName(states, names, n)) == 0;
325
326 // Skip redundant hardware write if the requested direction already matches
327 // what was read from hardware at startup (getStartupValues→readApproachDirection).
328 const bool currentPositive = (ApproachDirectionSP[APPROACH_POSITIVE].getState() == ISS_ON);
329 if (positiveApproach == currentPositive)
330 {
331 LOGF_DEBUG("Approach direction already %s; skipping hardware command.",
332 positiveApproach ? "Positive (Outward)" : "Negative (Inward)");
333 ApproachDirectionSP.setState(IPS_OK);
334 ApproachDirectionSP.apply();
335 return true;
336 }
337
338 if (setApproachDirection(positiveApproach ? 0 : 1))
339 {
340 ApproachDirectionSP.update(states, names, n);
341 ApproachDirectionSP.setState(IPS_OK);
342 LOGF_INFO("Approach direction set to %s.", positiveApproach ? "Positive (Outward)" : "Negative (Inward)");
343 }
344 else
345 {
346 ApproachDirectionSP.setState(IPS_ALERT);
347 }
348 ApproachDirectionSP.apply();
349 return true;
350 }
351 // Stop Detection
352 else if (StopDetectSP.isNameMatch(name))
353 {
354 const bool enabled =
355 strcmp(StopDetectSP[STOP_DETECT_ON].getName(), IUFindOnSwitchName(states, names, n)) == 0;
356
357 // Skip redundant hardware write if the requested state already matches
358 // what was read from hardware at startup (getStartupValues→readStopDetect).
359 const bool currentEnabled = (StopDetectSP[STOP_DETECT_ON].getState() == ISS_ON);
360 if (enabled == currentEnabled)
361 {
362 LOGF_DEBUG("Stop detection already %s; skipping hardware command.",
363 enabled ? "enabled" : "disabled");
364 StopDetectSP.setState(IPS_OK);
365 StopDetectSP.apply();
366 return true;
367 }
368
369 if (setStopDetect(enabled))
370 {
371 StopDetectSP.update(states, names, n);
372 StopDetectSP.setState(IPS_OK);
373 LOGF_INFO("Stop detection %s.", enabled ? "enabled" : "disabled");
374 }
375 else
376 {
377 StopDetectSP.setState(IPS_ALERT);
378 }
379 StopDetectSP.apply();
380 return true;
381 }
382 }
383
384 return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
385}
386
390bool EFA::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
391{
392 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
393 {
394 // Fan Params
395 if (FanControlNP.isNameMatch(name))
396 {
397 FanControlNP.update(values, names, n);
398 FanControlNP.setState(IPS_OK);
399 FanControlNP.apply();
400 return true;
401 }
402 }
403
404 return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
405}
406
410bool EFA::SyncFocuser(uint32_t ticks)
411{
412 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 9;
413
414 cmd[0] = DRIVER_SOM;
415 cmd[1] = 0x06;
416 cmd[2] = DEVICE_PC;
417 cmd[3] = DEVICE_FOC;
418 cmd[4] = MTR_OFFSET_CNT;
419 cmd[5] = (ticks >> 16) & 0xFF;
420 cmd[6] = (ticks >> 8) & 0xFF;
421 cmd[7] = (ticks >> 0) & 0xFF;
422 cmd[8] = calculateCheckSum(cmd, len);
423
424 if (!validateLengths(cmd, len))
425 return false;
426
427 if (!sendCommand(cmd, res, len, DRIVER_LEN))
428 return false;
429
430 return (res[5] == 1);
431}
432
436IPState EFA::MoveAbsFocuser(uint32_t targetTicks)
437{
438 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 9;
439
440 cmd[0] = DRIVER_SOM;
441 cmd[1] = 0x06;
442 cmd[2] = DEVICE_PC;
443 cmd[3] = DEVICE_FOC;
444 cmd[4] = MTR_GOTO_POS2;
445 cmd[5] = (targetTicks >> 16) & 0xFF;
446 cmd[6] = (targetTicks >> 8) & 0xFF;
447 cmd[7] = (targetTicks >> 0) & 0xFF;
448 cmd[8] = calculateCheckSum(cmd, len);
449
450 if (!validateLengths(cmd, len))
451 return IPS_ALERT;
452
453 if (!sendCommand(cmd, res, len, DRIVER_LEN))
454 return IPS_ALERT;
455
456 return (res[5] == 1) ? IPS_BUSY : IPS_ALERT;
457}
458
463{
464 int direction = (dir == FOCUS_INWARD) ? -1 : 1;
465 int reversed = (FocusReverseSP[INDI_ENABLED].getState() == ISS_ON) ? -1 : 1;
466 int relative = static_cast<int>(ticks);
467 int targetAbsPosition = FocusAbsPosNP[0].getValue() + (relative * direction * reversed);
468
469 targetAbsPosition = std::min(static_cast<uint32_t>(FocusAbsPosNP[0].getMax()),
470 static_cast<uint32_t>(std::max(static_cast<int>(FocusAbsPosNP[0].getMin()), targetAbsPosition)));
471
472 return MoveAbsFocuser(targetAbsPosition);
473}
474
480{
481 // Re-read current position to make sure we have the latest value
482 readPosition();
483 uint32_t currentPos = static_cast<uint32_t>(FocusAbsPosNP[0].getValue());
484 return MoveAbsFocuser(currentPos) != IPS_ALERT;
485}
486
491bool EFA::SetFocuserSpeed(int speed)
492{
493 return setMotorSlewRate(static_cast<uint8_t>(speed));
494}
495
500{
501 if (!isConnected())
502 return;
503
504 IN_TIMER = true;
505
506 readPosition();
507
508 if (readTemperature())
509 {
510 // Send temperature is above threshold
511 bool aboveThreshold = false;
512 for (size_t i = 0; i < TemperatureNP.count(); i++)
513 {
514 // Skip invalid sentinel temperature values
515 if (std::fabs(TemperatureNP[i].getValue() - TEMPERATURE_INVALID) < 0.1)
516 continue;
517
518 if (std::fabs(TemperatureNP[i].getValue() - m_LastTemperature[i]) > TEMPERATURE_THRESHOLD)
519 {
520 aboveThreshold = true;
521 m_LastTemperature[i] = TemperatureNP[i].getValue();
522 }
523 }
524
525 if (aboveThreshold)
526 TemperatureNP.apply();
527
528
529 if (FanControlSP[FAN_MANUAL].getState() == ISS_OFF)
530 {
531 bool turnOn = false, turnOff = false;
532 const bool isFanOn = FanStateSP[FAN_ON].getState() == ISS_ON;
533
534 // Check if we need to do automatic regulation of fan
535 if (FanControlSP[FAN_AUTOMATIC_ABSOLUTE].getState() == ISS_ON)
536 {
537 // Only regulate if primary temperature is valid
538 if (std::fabs(TemperatureNP[TEMPERATURE_PRIMARY].getValue() - TEMPERATURE_INVALID) > 0.1)
539 {
540 // Adjust delta for deadzone
541 double min_delta = FanControlNP[FAN_MAX_ABSOLUTE].getValue() - FanControlNP[FAN_DEADZONE].getValue();
542 double max_delta = FanControlNP[FAN_MAX_ABSOLUTE].getValue() + FanControlNP[FAN_DEADZONE].getValue();
543
544 turnOn = TemperatureNP[TEMPERATURE_PRIMARY].getValue() > max_delta;
545 turnOff = TemperatureNP[TEMPERATURE_PRIMARY].getValue() < min_delta;
546 }
547 }
548 else if (FanControlSP[FAN_AUTOMATIC_RELATIVE].getState() == ISS_ON)
549 {
550 // Only regulate if both temperatures are valid
551 if (std::fabs(TemperatureNP[TEMPERATURE_PRIMARY].getValue() - TEMPERATURE_INVALID) > 0.1 &&
552 std::fabs(TemperatureNP[TEMPERATURE_AMBIENT].getValue() - TEMPERATURE_INVALID) > 0.1)
553 {
554 // Temperature delta
555 double tDiff = TemperatureNP[TEMPERATURE_PRIMARY].getValue() - TemperatureNP[TEMPERATURE_AMBIENT].getValue();
556 // Adjust delta for deadzone
557 double min_delta = FanControlNP[FAN_MAX_RELATIVE].getValue() - FanControlNP[FAN_DEADZONE].getValue();
558 double max_delta = FanControlNP[FAN_MAX_RELATIVE].getValue() + FanControlNP[FAN_DEADZONE].getValue();
559
560 // Check if we need to turn off/on fan
561 turnOn = tDiff > max_delta;
562 turnOff = tDiff < min_delta;
563 }
564 }
565
566 if (isFanOn && turnOff)
567 {
568 setFanEnabled(false);
569 FanStateSP[FAN_ON].setState(ISS_OFF);
570 FanStateSP[FAN_OFF].setState(ISS_ON);
571 FanStateSP.setState(IPS_IDLE);
572 FanStateSP.apply();
573 }
574 else if (!isFanOn && turnOn)
575 {
576 setFanEnabled(true);
577 FanStateSP[FAN_ON].setState(ISS_ON);
578 FanStateSP[FAN_OFF].setState(ISS_OFF);
579 FanStateSP.setState(IPS_OK);
580 FanStateSP.apply();
581 }
582 }
583 }
584
586 {
587 if (isGOTOComplete())
588 {
593 LOG_INFO("Focuser reached requested position.");
594 }
595 else
597 }
598 else if (std::fabs(FocusAbsPosNP[0].getValue() - m_LastPosition) > 0)
599 {
600 m_LastPosition = FocusAbsPosNP[0].getValue();
602 }
603
604 IN_TIMER = false;
605
607}
608
612bool EFA::SetFocuserMaxPosition(uint32_t ticks)
613{
614 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 9;
615
616 cmd[0] = DRIVER_SOM;
617 cmd[1] = 0x06;
618 cmd[2] = DEVICE_PC;
619 cmd[3] = DEVICE_FOC;
621 cmd[5] = (ticks >> 16) & 0xFF;
622 cmd[6] = (ticks >> 8) & 0xFF;
623 cmd[7] = (ticks >> 0) & 0xFF;
624 cmd[8] = calculateCheckSum(cmd, len);
625
626 if (!validateLengths(cmd, len))
627 return false;
628
629 if (!sendCommand(cmd, res, len, DRIVER_LEN))
630 return false;
631
632 return (res[5] == 1);
633}
634
638bool EFA::ReverseFocuser(bool enabled)
639{
640 INDI_UNUSED(enabled);
641 return true;
642}
643
647bool EFA::saveConfigItems(FILE * fp)
648{
650
651 FanControlSP.save(fp);
652 if (FanControlNP.getState() == IPS_OK)
653 FanControlNP.save(fp);
654 FanDisconnectSP.save(fp);
655 // ApproachDirectionSP and StopDetectSP are stored in hardware EEPROM.
656 // They are read back at startup via getStartupValues() and should not
657 // be saved to or restored from config to avoid sending redundant
658 // configuration commands that could trigger focuser movement.
659 return true;
660}
661
665void EFA::getStartupValues()
666{
667 readPosition();
668 readCalibrationState();
669 readFanState();
670 readTemperature();
671 readSlewRate();
672 readApproachDirection();
673 readStopDetect();
674
675 if (readMaxSlewLimit())
676 {
677 FocusAbsPosNP[0].setMax(FocusMaxPosNP[0].getMax());
678 FocusAbsPosNP[0].setStep(FocusAbsPosNP[0].getMax() / 50);
679
680 FocusRelPosNP[0].setValue(FocusAbsPosNP[0].getMax() / 50);
681 FocusRelPosNP[0].setMax(FocusAbsPosNP[0].getMax() / 2);
682 FocusRelPosNP[0].setStep(FocusRelPosNP[0].getMax() / 50);
683
684 FocusMaxPosNP[0].setValue(FocusMaxPosNP[0].getMax());
685 FocusMaxPosNP[0].setStep(FocusMaxPosNP[0].getMax() / 50);
686
690 }
691}
692
696inline int EFA::readByte(int fd, uint8_t *buf, int timeout, int *nbytes_read)
697{
698 return tty_read(fd, (char *)buf, 1, timeout, nbytes_read);
699}
700
704inline int EFA::readBytes(int fd, uint8_t *buf, int nbytes, int timeout, int *nbytes_read)
705{
706 return tty_read(fd, (char *)buf, nbytes, timeout, nbytes_read);
707}
708
712inline int EFA::writeBytes(int fd, const uint8_t *buf, int nbytes, int *nbytes_written)
713{
714 return tty_write(fd, (const char *)buf, nbytes, nbytes_written);
715}
716
720int EFA::readPacket(int fd, uint8_t *buf, int nbytes, int timeout, int *nbytes_read)
721{
722 int len = 0, rc = 0, read_bytes = *nbytes_read = 0;
723
724 if (nbytes < 6) // smallest packet is 6 bytes
725 {
726 LOGF_ERROR("Read needs at least 6 bytes; exceeds supplied buffer size (%d)", nbytes);
727 return TTY_READ_ERROR;
728 }
729
730 for (int i = 0; i < 10; i++)
731 {
732 // look for SOM byte
733 rc = readByte(fd, &buf[0], timeout, &read_bytes);
734 if (rc == TTY_OK && read_bytes == 1)
735 {
736 if (buf[0] == DRIVER_SOM)
737 {
738 break;
739 }
740 else
741 {
742 LOGF_DEBUG("Looking for SOM (%02X); found %d byte (%02X)", DRIVER_SOM, read_bytes, buf[0]);
743 }
744 }
745 else
746 {
747 char errstr[MAXRBUF] = {0};
748 tty_error_msg(rc, errstr, MAXRBUF);
749 LOGF_DEBUG("Looking for SOM (%02X); found %s", DRIVER_SOM, errstr);
750 // Only keep scanning past non-SOM bytes. A timeout means nothing more is coming,
751 // so waiting out the timeout again would just block the driver for longer.
752 break;
753 }
754 }
755 if (rc != TTY_OK || read_bytes != 1 || buf[0] != DRIVER_SOM)
756 {
757 LOGF_DEBUG("%s byte not encountered", "SOM");
758 if (rc == TTY_OK)
759 return TTY_TIME_OUT;
760 return rc;
761 }
762
763 if ((rc = readByte(fd, &buf[1], timeout, &read_bytes)) != TTY_OK)
764 {
765 LOGF_DEBUG("%s byte not encountered", "LEN");
766 return rc;
767 }
768 len = buf[1];
769
770 // read source
771 if ((rc = readByte(fd, &buf[2], timeout, &read_bytes)) != TTY_OK)
772 {
773 LOGF_DEBUG("%s byte not encountered", "SRC");
774 return rc;
775 }
776 // read receiver
777 if ((rc = readByte(fd, &buf[3], timeout, &read_bytes)) != TTY_OK)
778 {
779 LOGF_DEBUG("%s byte not encountered", "RCV");
780 return rc;
781 }
782 // read command
783 if ((rc = readByte(fd, &buf[4], timeout, &read_bytes)) != TTY_OK)
784 {
785 LOGF_DEBUG("%s byte not encountered", "CMD");
786 return rc;
787 }
788
789 if ((len + 3) > nbytes)
790 {
791 LOGF_ERROR("Read (%d) will exceed supplied buffer size (%d) for command %02X", (len + 3), nbytes, buf[4]);
792 return TTY_READ_ERROR;
793 }
794
795 // read data
796 int n;
797 for (n = 0; n < (len - 3); ++n)
798 {
799 if ((rc = readByte(fd, &buf[5 + n], timeout, &read_bytes)) != TTY_OK)
800 {
801 LOGF_DEBUG("%s byte not encountered", "DATA");
802 return rc;
803 }
804 }
805 // read checksum
806 if ((rc = readByte(fd, &buf[5 + n], timeout, &read_bytes)) != TTY_OK)
807 {
808 LOGF_DEBUG("%s byte not encountered", "DATA");
809 return rc;
810 }
811
812 uint8_t chk = calculateCheckSum(buf, (len + 3));
813
814 if (chk != buf[len + 2])
815 {
816 LOG_ERROR("Invalid checksum!");
817 return TTY_OK; // not a tty error, nbytes_read is still zero and it is used to indicate there was this problem
818 }
819
820 *nbytes_read = len + 3;
821
822 return rc;
823}
824
828bool EFA::sendCommand(const uint8_t * cmd, uint8_t *res, uint32_t cmd_len, uint32_t res_len)
829{
830 int nbytes_written = 0, nbytes_read = 0, bits = 0, rc = 0, hexbuflen = (DRIVER_LEN * 3 + 4);
831 char hexbuf[DRIVER_LEN * 3 + 4];
832
833 for (int j = 0; j < 3; usleep(100000), j++)
834 {
835 // make sure RTS is lowered
836 bits = TIOCM_RTS;
837 (void) ioctl(PortFD, TIOCMBIC, &bits);
838 bits = 0;
839
840 // Wait until CTS is cleared.
841 for (int i = 0; i < 10; i++)
842 {
843 if ((rc = ioctl(PortFD, TIOCMGET, &bits)) == 0 && (bits & TIOCM_CTS) == 0)
844 break;
845 usleep(100000);
846 }
847
848 if (rc < 0)
849 {
850 LOGF_ERROR("Failed to read CTS state: %s", strerror(errno));
851 return false;
852 }
853 else if ((bits & TIOCM_CTS) != 0)
854 {
855 LOG_ERROR("CTS timed out: serial bus is still busy.");
856 return false;
857 }
858
859 // Now raise RTS
860 bits = TIOCM_RTS;
861 ioctl(PortFD, TIOCMBIS, &bits); // was TIOCMSET
862
863 if (!IN_TIMER && efaDump(hexbuf, hexbuflen, cmd, cmd_len) != NULL)
864 {
865 LOGF_DEBUG("CMD: %s", hexbuf);
866 }
867 // Discard any stale input so a late response to an earlier command is not taken as ours
868 tcflush(PortFD, TCIFLUSH);
869 rc = writeBytes(PortFD, cmd, cmd_len, &nbytes_written);
870
871 if (rc != TTY_OK)
872 {
873 continue;
874 }
875
876 rc = readPacket(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
877
878 if (rc != TTY_OK || nbytes_read == 0)
879 {
880 continue;
881 }
882
883 if ((int)cmd_len == nbytes_read && memcmp(cmd, res, cmd_len) == 0)
884 {
885 // received an echo
886
887 bits = TIOCM_RTS;
888 ioctl(PortFD, TIOCMBIC, &bits);
889
890 // Next read the actual response from EFA
891 rc = readPacket(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
892
893 if (rc != TTY_OK || nbytes_read == 0)
894 {
895 continue;
896 }
897 }
898 else if (efaDump(hexbuf, hexbuflen, cmd, cmd_len) != NULL)
899 {
900 // expected there to always be an echo, so note this occurrence
901 LOGF_DEBUG("no echo for command packet: %s", hexbuf);
902 }
903
904 if (!IN_TIMER && efaDump(hexbuf, hexbuflen, res, nbytes_read) != NULL)
905 {
906 LOGF_DEBUG("RESP: %s", hexbuf);
907 }
908
909 if (cmd[2] != res[3] || cmd[3] != res[2] || cmd[4] != res[4])
910 {
911 LOGF_DEBUG("Send/Receive mismatch - %s!", "packet not for us");
912 continue;
913 }
914 break;
915 }
916
917 // Extra lowering of RTS to make sure hand controller is made available
918 bits = TIOCM_RTS;
919 ioctl(PortFD, TIOCMBIC, &bits);
920
921 if (rc != TTY_OK)
922 {
923 char errstr[MAXRBUF] = {0};
924 tty_error_msg(rc, errstr, MAXRBUF);
925 LOGF_ERROR("Serial I/O error: %s.", errstr);
926 return false;
927 }
928
929 if (cmd[2] != res[3] || cmd[3] != res[2] || cmd[4] != res[4])
930 {
931 LOGF_ERROR("Send/Receive mismatch and %s", "timeout");
932 return false;
933 }
934
935 return true;
936}
937
941bool EFA::readPosition()
942{
943 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
944
945 cmd[0] = DRIVER_SOM;
946 cmd[1] = 0x03;
947 cmd[2] = DEVICE_PC;
948 cmd[3] = DEVICE_FOC;
949 cmd[4] = MTR_GET_POS;
950 cmd[5] = calculateCheckSum(cmd, len);
951
952 if (!validateLengths(cmd, len))
953 return false;
954
955 if (!sendCommand(cmd, res, len, DRIVER_LEN))
956 return false;
957
958 FocusAbsPosNP[0].setValue(res[5] << 16 | res[6] << 8 | res[7]);
959 return true;
960}
961
965bool EFA::readMaxSlewLimit()
966{
967 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
968
969 cmd[0] = DRIVER_SOM;
970 cmd[1] = 0x03;
971 cmd[2] = DEVICE_PC;
972 cmd[3] = DEVICE_FOC;
974 cmd[5] = calculateCheckSum(cmd, len);
975
976 if (!validateLengths(cmd, len))
977 return false;
978
979 if (!sendCommand(cmd, res, len, DRIVER_LEN))
980 return false;
981
982 uint32_t limit = res[5] << 16 | res[6] << 8 | res[7];
983 if (limit > 0)
984 {
985 FocusMaxPosNP[0].setMax(limit);
986 return true;
987 }
988
989 return false;
990}
991
995bool EFA::readSlewRate()
996{
997 // Read the positive (outward) slew rate and use it as the current speed
998 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
999
1000 cmd[0] = DRIVER_SOM;
1001 cmd[1] = 0x03;
1002 cmd[2] = DEVICE_PC;
1003 cmd[3] = DEVICE_FOC;
1004 cmd[4] = MTR_PMSLEW_RATE;
1005 cmd[5] = calculateCheckSum(cmd, len);
1006
1007 if (!validateLengths(cmd, len))
1008 return false;
1009
1010 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1011 return false;
1012
1013 uint8_t rate = res[5];
1014 if (rate >= 1 && rate <= 9)
1015 {
1016 FocusSpeedNP[0].setValue(rate);
1018 }
1019
1020 return true;
1021}
1022
1027bool EFA::setMotorSlewRate(uint8_t rate)
1028{
1029 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
1030
1031 // Set positive (outward) rate
1032 cmd[0] = DRIVER_SOM;
1033 cmd[1] = 0x04;
1034 cmd[2] = DEVICE_PC;
1035 cmd[3] = DEVICE_FOC;
1036 cmd[4] = MTR_PMSLEW_RATE;
1037 cmd[5] = rate;
1038 cmd[6] = calculateCheckSum(cmd, len);
1039
1040 if (!validateLengths(cmd, len))
1041 return false;
1042
1043 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1044 return false;
1045
1046 if (res[5] != 1)
1047 return false;
1048
1049 // Set negative (inward) rate
1050 memset(cmd, 0, sizeof(cmd));
1051 memset(res, 0, sizeof(res));
1052 cmd[0] = DRIVER_SOM;
1053 cmd[1] = 0x04;
1054 cmd[2] = DEVICE_PC;
1055 cmd[3] = DEVICE_FOC;
1056 cmd[4] = MTR_NMSLEW_RATE;
1057 cmd[5] = rate;
1058 cmd[6] = calculateCheckSum(cmd, len);
1059
1060 if (!validateLengths(cmd, len))
1061 return false;
1062
1063 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1064 return false;
1065
1066 return (res[5] == 1);
1067}
1068
1072bool EFA::readApproachDirection()
1073{
1074 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
1075
1076 cmd[0] = DRIVER_SOM;
1077 cmd[1] = 0x03;
1078 cmd[2] = DEVICE_PC;
1079 cmd[3] = DEVICE_FOC;
1081 cmd[5] = calculateCheckSum(cmd, len);
1082
1083 if (!validateLengths(cmd, len))
1084 return false;
1085
1086 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1087 return false;
1088
1089 // 0 = positive (outward) approach, 1 = negative (inward) approach
1090 bool positiveApproach = (res[5] == 0);
1091 ApproachDirectionSP[APPROACH_POSITIVE].setState(positiveApproach ? ISS_ON : ISS_OFF);
1092 ApproachDirectionSP[APPROACH_NEGATIVE].setState(positiveApproach ? ISS_OFF : ISS_ON);
1093
1094 return true;
1095}
1096
1102bool EFA::setApproachDirection(uint8_t direction)
1103{
1104 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
1105
1106 cmd[0] = DRIVER_SOM;
1107 cmd[1] = 0x04;
1108 cmd[2] = DEVICE_PC;
1109 cmd[3] = DEVICE_FOC;
1111 cmd[5] = direction;
1112 cmd[6] = calculateCheckSum(cmd, len);
1113
1114 if (!validateLengths(cmd, len))
1115 return false;
1116
1117 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1118 return false;
1119
1120 return (res[5] == 1);
1121}
1122
1126bool EFA::readStopDetect()
1127{
1128 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
1129
1130 cmd[0] = DRIVER_SOM;
1131 cmd[1] = 0x03;
1132 cmd[2] = DEVICE_PC;
1133 cmd[3] = DEVICE_FOC;
1135 cmd[5] = calculateCheckSum(cmd, len);
1136
1137 if (!validateLengths(cmd, len))
1138 return false;
1139
1140 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1141 return false;
1142
1143 bool enabled = (res[5] == 1);
1144 StopDetectSP[STOP_DETECT_ON].setState(enabled ? ISS_ON : ISS_OFF);
1145 StopDetectSP[STOP_DETECT_OFF].setState(enabled ? ISS_OFF : ISS_ON);
1146
1147 return true;
1148}
1149
1153bool EFA::setStopDetect(bool enabled)
1154{
1155 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
1156
1157 cmd[0] = DRIVER_SOM;
1158 cmd[1] = 0x04;
1159 cmd[2] = DEVICE_PC;
1160 cmd[3] = DEVICE_FOC;
1161 cmd[4] = MTR_STOP_DETECT;
1162 cmd[5] = enabled ? 1 : 0;
1163 cmd[6] = calculateCheckSum(cmd, len);
1164
1165 if (!validateLengths(cmd, len))
1166 return false;
1167
1168 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1169 return false;
1170
1171 return (res[5] == 1);
1172}
1173
1177bool EFA::isGOTOComplete()
1178{
1179 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
1180
1181 cmd[0] = DRIVER_SOM;
1182 cmd[1] = 0x03;
1183 cmd[2] = DEVICE_PC;
1184 cmd[3] = DEVICE_FOC;
1185 cmd[4] = MTR_GOTO_OVER;
1186 cmd[5] = calculateCheckSum(cmd, len);
1187
1188 if (!validateLengths(cmd, len))
1189 return false;
1190
1191 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1192 return false;
1193
1194 return (res[5] != 0);
1195}
1196
1200bool EFA::setFanEnabled(bool enabled)
1201{
1202 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
1203
1204 cmd[0] = DRIVER_SOM;
1205 cmd[1] = 0x04;
1206 cmd[2] = DEVICE_PC;
1207 cmd[3] = DEVICE_FAN;
1208 cmd[4] = FANS_SET;
1209 cmd[5] = enabled ? 1 : 0;
1210 cmd[6] = calculateCheckSum(cmd, len);
1211
1212 if (!validateLengths(cmd, len))
1213 return false;
1214
1215 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1216 return false;
1217
1218 return (res[5] == 1);
1219}
1220
1225bool EFA::readFanState()
1226{
1227 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
1228
1229 cmd[0] = DRIVER_SOM;
1230 cmd[1] = 0x03;
1231 cmd[2] = DEVICE_PC;
1232 cmd[3] = DEVICE_FAN;
1233 cmd[4] = FANS_GET;
1234 cmd[5] = calculateCheckSum(cmd, len);
1235
1236 if (!validateLengths(cmd, len))
1237 return false;
1238
1239 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1240 return false;
1241
1242 // Protocol: 1 = fan is ON, 0 = fan is OFF (consistent with FANS_SET command)
1243 bool enabled = (res[5] == 1);
1244
1245 FanStateSP[FAN_ON].setState(enabled ? ISS_ON : ISS_OFF);
1246 FanStateSP[FAN_OFF].setState(enabled ? ISS_OFF : ISS_ON);
1247
1248 return true;
1249}
1250
1254bool EFA::setCalibrationEnabled(bool enabled)
1255{
1256 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 8;
1257
1258 cmd[0] = DRIVER_SOM;
1259 cmd[1] = 0x05;
1260 cmd[2] = DEVICE_PC;
1261 cmd[3] = DEVICE_FOC;
1263 cmd[5] = 0x40;
1264 cmd[6] = enabled ? 1 : 0;
1265 cmd[7] = calculateCheckSum(cmd, len);
1266
1267 if (!validateLengths(cmd, len))
1268 return false;
1269
1270 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1271 return false;
1272
1273 return (res[5] == 1);
1274}
1275
1279bool EFA::readCalibrationState()
1280{
1281 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
1282
1283 cmd[0] = DRIVER_SOM;
1284 cmd[1] = 0x04;
1285 cmd[2] = DEVICE_PC;
1286 cmd[3] = DEVICE_FOC;
1288 cmd[5] = 0x40;
1289 cmd[6] = calculateCheckSum(cmd, len);
1290
1291 if (!validateLengths(cmd, len))
1292 return false;
1293
1294 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1295 return false;
1296
1297 bool enabled = (res[5] == 1);
1298
1299 CalibrationStateSP[CALIBRATION_ON].setState(enabled ? ISS_ON : ISS_OFF);
1300 CalibrationStateSP[CALIBRATION_OFF].setState(enabled ? ISS_OFF : ISS_ON);
1301
1302 return true;
1303}
1304
1310bool EFA::readTemperature()
1311{
1312 uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
1313
1314 for (uint8_t i = 0; i < 2; i++)
1315 {
1316 cmd[0] = DRIVER_SOM;
1317 cmd[1] = 0x04;
1318 cmd[2] = DEVICE_PC;
1319 cmd[3] = DEVICE_TEMP;
1320 cmd[4] = TEMP_GET;
1321 cmd[5] = i;
1322 cmd[6] = calculateCheckSum(cmd, len);
1323
1324 if (!validateLengths(cmd, len))
1325 return false;
1326
1327 if (!sendCommand(cmd, res, len, DRIVER_LEN))
1328 return false;
1329
1330 double temperature = calculateTemperature(res[5], res[6]);
1331
1332 if (std::fabs(temperature - TEMPERATURE_INVALID) < 0.1)
1333 {
1334 // Sensor reports invalid data (0x7F 0x7F) — mark as alert but do not update the value
1335 LOGF_DEBUG("Temperature sensor %d returned invalid data.", i);
1336 TemperatureNP.setState(IPS_ALERT);
1337 }
1338 else
1339 {
1340 TemperatureNP[i].setValue(temperature);
1341 }
1342 }
1343
1344 return true;
1345}
1346
1350double EFA::calculateTemperature(uint8_t byte2, uint8_t byte3)
1351{
1352 if (byte2 == 0x7F && byte3 == 0x7F)
1353 return TEMPERATURE_INVALID;
1354
1355 int raw_temperature = byte3 << 8 | byte2;
1356 if (raw_temperature & 0x8000)
1357 raw_temperature = raw_temperature - 0x10000;
1358
1359 return raw_temperature / 16.0;
1360}
1361
1365char * EFA::efaDump(char * buf, int buflen, const uint8_t * data, uint32_t size)
1366{
1367 int needed = 0, idx = 0;
1368
1369 needed = size * 3 + 4; // each byte goes to 2 chars plus 1 space (or trailing null char) plus 4 marker characters
1370
1371 if (needed > buflen)
1372 {
1373 return NULL;
1374 }
1375
1376 for (uint32_t i = 0; i < size; i++)
1377 {
1378 if (i == 4)
1379 {
1380 (void) sprintf(buf + idx, "<%02X> ", data[i]);
1381 idx += 5;
1382 }
1383 else if (i == 5 && i < (size - 1))
1384 {
1385 buf[idx++] = '[';
1386 for (uint32_t j = i, k = 3; j < (size - 1) && k < data[1]; j++, k++)
1387 {
1388 (void) sprintf(buf + idx, "%02X ", data[j]);
1389 idx += 3;
1390 i = j;
1391 }
1392 buf[idx - 1] = ']';
1393 buf[idx++] = ' ';
1394 }
1395 else
1396 {
1397 (void) sprintf(buf + idx, "%02X ", data[i]);
1398 idx += 3;
1399 }
1400 }
1401
1402 buf[idx - 1] = '\0';
1403
1404 return buf;
1405}
1406
1410std::vector<std::string> EFA::split(const std::string &input, const std::string &regex)
1411{
1412 // passing -1 as the submatch index parameter performs splitting
1413 std::regex re(regex);
1414 std::sregex_token_iterator
1415 first{input.begin(), input.end(), re, -1},
1416 last;
1417 return {first, last};
1418}
1419
1423template <typename T>
1424std::string EFA::to_string(const T a_value, const int n)
1425{
1426 std::ostringstream out;
1427 out.precision(n);
1428 out << std::fixed << a_value;
1429 return out.str();
1430}
1431
1435bool EFA::validateLengths(const uint8_t *cmd, uint32_t len)
1436{
1437 if (len < 6)
1438 {
1439 LOGF_ERROR("packet length (%d) is too short for command %02X", len, cmd[4]);
1440 return false;
1441 }
1442 if (cmd[1] + 3 != (int)len)
1443 {
1444 LOGF_ERROR("packet length (%d) and data length (%d) discrepancy for command %02X", len, cmd[1], cmd[4]);
1445 return false;
1446 }
1447
1448 return true;
1449}
1450
1454uint8_t EFA::calculateCheckSum(const uint8_t *cmd, uint32_t len)
1455{
1456 int32_t sum = 0;
1457
1458 for (uint32_t i = 1; i < len - 1; i++)
1459 sum += cmd[i];
1460
1461 return ((-sum) & 0xFF);
1462}
double temperature
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
virtual bool SyncFocuser(uint32_t ticks) override
Sync focuser.
virtual IPState MoveRelFocuser(FocusDirection dir, unsigned int ticks) override
Move Focuser relatively.
virtual bool ReverseFocuser(bool enabled) override
Reverse Focuser Motion.
@ DEVICE_PC
@ DEVICE_FOC
@ DEVICE_TEMP
@ DEVICE_FAN
virtual bool saveConfigItems(FILE *fp) override
virtual bool Handshake() override
Performs a device-specific handshake after a connection is established. This method should be overrid...
virtual bool SetFocuserMaxPosition(uint32_t ticks) override
Set Maximum Position.
@ MTR_SLEWLIMITMAX
@ MTR_SLEWLIMITGETMAX
@ MTR_GET_APPROACH_DIRECTION
@ GET_VERSION
@ TEMP_GET
@ MTR_GET_STOP_DETECT
@ FANS_GET
@ MTR_GOTO_OVER
@ MTR_GOTO_POS2
@ MTR_PMSLEW_RATE
@ MTR_OFFSET_CNT
@ FANS_SET
@ MTR_STOP_DETECT
@ MTR_NMSLEW_RATE
@ MTR_SET_CALIBRATION_STATE
@ MTR_APPROACH_DIRECTION
@ MTR_GET_CALIBRATION_STATE
@ MTR_GET_POS
const char * getDefaultName() override
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
Move Absolute Focuser.
virtual bool AbortFocuser() override
virtual bool SetFocuserSpeed(int speed) override
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
virtual bool Disconnect() override
Disconnect from device.
bool isConnected() const
virtual bool Disconnect()
Disconnect from device.
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
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)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
INDI::PropertyNumber FocusRelPosNP
INDI::PropertySwitch FocusReverseSP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusSyncNP
INDI::PropertyNumber FocusSpeedNP
INDI::PropertyNumber FocusMaxPosNP
INDI::PropertyNumber FocusAbsPosNP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
virtual bool saveConfigItems(FILE *fp) override
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
Connection::Serial * serialConnection
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
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)
bool update(const ISState states[], const char *const names[], int n)
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)
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 errno
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_NOFMANY
Definition indiapi.h:173
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
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
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
@ TTY_READ_ERROR
Definition indicom.h:55
@ TTY_TIME_OUT
Definition indicom.h:58
const char * IUFindOnSwitchName(ISState *states, char *names[], int n)
Returns the name of the first ON switch it finds in the supplied arguments.
Definition indidevapi.c:355
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#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
#define getDeviceName
int fd
uint8_t cmd[4]
Definition pwc-ioctl.h:2