1 /*
2 SestoSenso Focuser
3 Copyright (C) 2018 Jasem Mutlaq (mutlaqja@ikarustech.com)
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19 Commands and responses:
20
21 Only use the SM/Sm commands during calibration. Will cause direction reversal!
22 #Sm;xxxxxxx! Set xxxxxxx as min value
23 #SM! Set current position as max
24 #SM;xxxxxxx! Set xxxxxxx as max value (xxxxxxx between 0 to 2097152)
25
26 #SPxxxx! Set_current_position as xxxx
27 #SC;HOLD;RUN;ACC;DEC! Shell_set_current_supply in HOLD, RUN, ACC, DEC situations (Value must be from 0 to 24, maximum hold value 10)
28 #QM! Query max value
29 #Qm! Query min value
30 #QT! Qeury temperature
31 #QF! Query firmware version
32 #QN! Read the device name -> reply QN;SESTOSENSO!
33 #QP! Query_position
34 #FI! Fast_inward
35 #FO! Fast_outward
36 #SI! Slow_inward
37 #SO! Slow_outward
38 #GTxxxx! Go_to absolute position xxxx
39 #MA! Motion_abort and hold position
40 #MF! Motor free
41 #PS! param_save save current position for next power ON and currents supply
42 #PD! param_to_default , and position to zero
43
44 Response examples:
45
46 #QF! 14.06\r
47 #QT! -10.34\r
48 #FI! FIok!\r
49 #FO! FOok!\r
50 #SI! SIok!\r
51 #SO! SOok!\r
52 #GTxxxx! 100\r 200\r 300\r xxxx\r GTok!\r
53 #MA! MAok!\r
54 #MF! MFok!\r
55 #QP! 1530\r
56 #SPxxxx! SPok!\r
57 #SC;HOLD;RUN;ACC;DEC! SCok!\r
58 #PS! PSok!\r
59 #PD! PDok!\r
60
61 Before to disconnect the COM port, send the #PS! command in order to save the position on internal memory
62
63 */
64
65 #include "sestosenso.h"
66
67 #include "indicom.h"
68
69 #include <cmath>
70 #include <cstring>
71 #include <memory>
72
73 #include <termios.h>
74 #include <unistd.h>
75
76 static std::unique_ptr<SestoSenso> sesto(new SestoSenso());
77
SestoSenso()78 SestoSenso::SestoSenso()
79 {
80 setVersion(1, 4);
81 // Can move in Absolute & Relative motions, can AbortFocuser motion.
82 FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE | FOCUSER_CAN_ABORT);
83 }
84
initProperties()85 bool SestoSenso::initProperties()
86 {
87 INDI::Focuser::initProperties();
88
89 // Firmware Information
90 IUFillText(&FirmwareT[0], "VERSION", "Version", "");
91 IUFillTextVector(&FirmwareTP, FirmwareT, 1, getDeviceName(), "FOCUS_FIRMWARE", "Firmware", MAIN_CONTROL_TAB, IP_RO, 0,
92 IPS_IDLE);
93
94 // Focuser temperature
95 IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Celsius", "%6.2f", -50, 70., 0., 0.);
96 IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", MAIN_CONTROL_TAB,
97 IP_RO, 0, IPS_IDLE);
98
99 // Focuser calibration
100 IUFillText(&CalibrationMessageT[0], "CALIBRATION", "Calibration stage", "");
101 IUFillTextVector(&CalibrationMessageTP, CalibrationMessageT, 1, getDeviceName(), "CALIBRATION_MESSAGE", "Calibration",
102 MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
103
104 IUFillSwitch(&CalibrationS[CALIBRATION_START], "CALIBRATION_START", "Start", ISS_OFF);
105 IUFillSwitch(&CalibrationS[CALIBRATION_NEXT], "CALIBRATION_NEXT", "Next", ISS_OFF);
106 IUFillSwitchVector(&CalibrationSP, CalibrationS, 2, getDeviceName(), "FOCUS_CALIBRATION", "Calibration", MAIN_CONTROL_TAB,
107 IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
108
109 IUFillSwitch(&FastMoveS[FASTMOVE_IN], "FASTMOVE_IN", "Move In", ISS_OFF);
110 IUFillSwitch(&FastMoveS[FASTMOVE_OUT], "FASTMOVE_OUT", "Move out", ISS_OFF);
111 IUFillSwitch(&FastMoveS[FASTMOVE_STOP], "FASTMOVE_STOP", "Stop", ISS_OFF);
112 IUFillSwitchVector(&FastMoveSP, FastMoveS, 3, getDeviceName(), "FAST_MOVE", "Calibration Move", MAIN_CONTROL_TAB, IP_RW,
113 ISR_1OFMANY, 0, IPS_IDLE);
114
115 //
116 // Override the default Max. Position to make it Read-Only
117 IUFillNumberVector(&FocusMaxPosNP, FocusMaxPosN, 1, getDeviceName(), "FOCUS_MAX", "Max. Position", MAIN_CONTROL_TAB, IP_RO,
118 0, IPS_IDLE);
119
120 // Relative and absolute movement
121 FocusRelPosN[0].min = 0.;
122 FocusRelPosN[0].max = 50000.;
123 FocusRelPosN[0].value = 0;
124 FocusRelPosN[0].step = 1000;
125
126 FocusAbsPosN[0].min = 0.;
127 FocusAbsPosN[0].max = 200000.;
128 FocusAbsPosN[0].value = 0;
129 FocusAbsPosN[0].step = 1000;
130
131 FocusMaxPosN[0].value = 2097152;
132
133 addAuxControls();
134
135 setDefaultPollingPeriod(500);
136
137 m_MotionProgressTimer.callOnTimeout(std::bind(&SestoSenso::checkMotionProgressCallback, this));
138 m_MotionProgressTimer.setSingleShot(true);
139
140 return true;
141 }
142
updateProperties()143 bool SestoSenso::updateProperties()
144 {
145 INDI::Focuser::updateProperties();
146
147 if (isConnected())
148 {
149 // Only define temperature if there is a probe
150 if (updateTemperature())
151 defineProperty(&TemperatureNP);
152 defineProperty(&FirmwareTP);
153 IUSaveText(&CalibrationMessageT[0], "Press START to begin the Calibration");
154 defineProperty(&CalibrationMessageTP);
155 defineProperty(&CalibrationSP);
156
157 if (getStartupValues())
158 LOG_INFO("SestoSenso parameters updated, focuser ready for use.");
159 else
160 LOG_WARN("Failed to inquire parameters. Check logs.");
161 }
162 else
163 {
164 if (TemperatureNP.s == IPS_OK)
165 deleteProperty(TemperatureNP.name);
166 deleteProperty(FirmwareTP.name);
167 deleteProperty(CalibrationMessageTP.name);
168 deleteProperty(CalibrationSP.name);
169 }
170
171 return true;
172 }
173
Handshake()174 bool SestoSenso::Handshake()
175 {
176 if (Ack())
177 {
178 LOG_INFO("SestoSenso is online. Getting focus parameters...");
179 return true;
180 }
181
182 LOG_INFO(
183 "Error retrieving data from SestoSenso, please ensure SestoSenso controller is powered and the port is correct.");
184 return false;
185 }
186
Disconnect()187 bool SestoSenso::Disconnect()
188 {
189 // Save current position to memory.
190 if (isSimulation() == false)
191 sendCommand("#PS!");
192
193 return INDI::Focuser::Disconnect();
194 }
195
getDefaultName()196 const char *SestoSenso::getDefaultName()
197 {
198 return "Sesto Senso";
199 }
200
Ack()201 bool SestoSenso::Ack()
202 {
203 char res[SESTO_LEN] = {0};
204
205 if (isSimulation())
206 strncpy(res, "1.0 Simulation", SESTO_LEN);
207 else if (sendCommand("#QF!", res) == false)
208 return false;
209
210 IUSaveText(&FirmwareT[0], res);
211
212 return true;
213 }
214
updateTemperature()215 bool SestoSenso::updateTemperature()
216 {
217 char res[SESTO_LEN] = {0};
218 double temperature = 0;
219
220 if (isSimulation())
221 strncpy(res, "23.45", SESTO_LEN);
222 else if (sendCommand("#QT!", res) == false)
223 return false;
224
225 try
226 {
227 temperature = std::stod(res);
228 }
229 catch(...)
230 {
231 LOGF_WARN("Failed to process temperature response: %s (%d bytes)", res, strlen(res));
232 return false;
233 }
234
235 if (temperature > 90)
236 return false;
237
238 TemperatureN[0].value = temperature;
239 TemperatureNP.s = IPS_OK;
240
241 return true;
242 }
243
updateMaxLimit()244 bool SestoSenso::updateMaxLimit()
245 {
246 char res[SESTO_LEN] = {0};
247
248 if (isSimulation())
249 return true;
250
251 if (sendCommand("#QM!", res) == false)
252 return false;
253
254 int maxLimit = 0;
255
256 sscanf(res, "QM;%d!", &maxLimit);
257
258 if (maxLimit > 0)
259 {
260 FocusMaxPosN[0].max = maxLimit;
261 if (FocusMaxPosN[0].value > maxLimit)
262 FocusMaxPosN[0].value = maxLimit;
263
264 FocusAbsPosN[0].min = 0;
265 FocusAbsPosN[0].max = maxLimit;
266 FocusAbsPosN[0].value = 0;
267 FocusAbsPosN[0].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
268
269 FocusRelPosN[0].min = 0.;
270 FocusRelPosN[0].max = FocusAbsPosN[0].step * 10;
271 FocusRelPosN[0].value = 0;
272 FocusRelPosN[0].step = FocusAbsPosN[0].step;
273
274 IUUpdateMinMax(&FocusAbsPosNP);
275 IUUpdateMinMax(&FocusRelPosNP);
276
277 FocusMaxPosNP.s = IPS_OK;
278 IUUpdateMinMax(&FocusMaxPosNP);
279 return true;
280 }
281
282 FocusMaxPosNP.s = IPS_ALERT;
283 return false;
284 }
285
updatePosition()286 bool SestoSenso::updatePosition()
287 {
288 char res[SESTO_LEN] = {0};
289 if (isSimulation())
290 snprintf(res, SESTO_LEN, "%d", static_cast<uint32_t>(FocusAbsPosN[0].value));
291 else if (sendCommand("#QP!", res) == false)
292 return false;
293
294 try
295 {
296 FocusAbsPosN[0].value = std::stoi(res);
297 FocusAbsPosNP.s = IPS_OK;
298 return true;
299 }
300 catch(...)
301 {
302 LOGF_WARN("Failed to process position response: %s (%d bytes)", res, strlen(res));
303 FocusAbsPosNP.s = IPS_ALERT;
304 return false;
305 }
306 }
307
isMotionComplete()308 bool SestoSenso::isMotionComplete()
309 {
310 char res[SESTO_LEN] = {0};
311
312 if (isSimulation())
313 {
314 int32_t nextPos = FocusAbsPosN[0].value;
315 int32_t targPos = static_cast<int32_t>(targetPos);
316
317 if (targPos > nextPos)
318 nextPos += 250;
319 else if (targPos < nextPos)
320 nextPos -= 250;
321
322 if (abs(nextPos - targPos) < 250)
323 nextPos = targetPos;
324 else if (nextPos < 0)
325 nextPos = 0;
326 else if (nextPos > FocusAbsPosN[0].max)
327 nextPos = FocusAbsPosN[0].max;
328
329 snprintf(res, SESTO_LEN, "%d", nextPos);
330 }
331 else
332 {
333 int nbytes_read = 0;
334
335 //while (rc != TTY_TIME_OUT)
336 //{
337 int rc = tty_read_section(PortFD, res, SESTO_STOP_CHAR, 1, &nbytes_read);
338 if (rc == TTY_OK)
339 {
340 res[nbytes_read - 1] = 0;
341
342
343 if (!strcmp(res, "GTok!"))
344 return true;
345
346 try
347 {
348 uint32_t newPos = std::stoi(res);
349 FocusAbsPosN[0].value = newPos;
350 }
351 catch (...)
352 {
353 LOGF_WARN("Failed to process motion response: %s (%d bytes)", res, strlen(res));
354 }
355 }
356 //}
357 }
358
359 return false;
360 }
361
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)362 bool SestoSenso::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
363 {
364 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
365 {
366
367 // Calibrate focuser
368 if (!strcmp(name, CalibrationSP.name))
369 {
370 char res[SESTO_LEN] = {0};
371 int current_switch = 0;
372
373 CalibrationSP.s = IPS_BUSY;
374 //IDSetSwitch(&CalibrationSP, nullptr);
375 IUUpdateSwitch(&CalibrationSP, states, names, n);
376
377 current_switch = IUFindOnSwitchIndex(&CalibrationSP);
378 CalibrationS[current_switch].s = ISS_ON;
379 IDSetSwitch(&CalibrationSP, nullptr);
380
381 if (current_switch == CALIBRATION_START)
382 {
383 if (cStage == Idle || cStage == Complete )
384 {
385 // Start the calibration process
386 LOG_INFO("Start Calibration");
387 CalibrationSP.s = IPS_BUSY;
388 IDSetSwitch(&CalibrationSP, nullptr);
389
390 //
391 // Unlock the motor to allow manual movement of the focuser
392 //
393 if (sendCommand("#MF!") == false)
394 return false;
395
396 IUSaveText(&CalibrationMessageT[0], "Move focuser manually to the middle then press NEXT");
397 IDSetText(&CalibrationMessageTP, nullptr);
398
399 // Set next step
400 cStage = GoToMiddle;
401 }
402 else
403 {
404 LOG_INFO("Already started calibration. Proceed to next step.");
405 IUSaveText(&CalibrationMessageT[0], "Already started. Proceed to NEXT.");
406 IDSetText(&CalibrationMessageTP, nullptr);
407 }
408 }
409 else if (current_switch == CALIBRATION_NEXT)
410 {
411 if (cStage == GoToMiddle)
412 {
413 defineProperty(&FastMoveSP);
414 IUSaveText(&CalibrationMessageT[0], "Move In/Move Out/Stop to MIN position then press NEXT");
415 IDSetText(&CalibrationMessageTP, nullptr);
416 cStage = GoMinimum;
417 }
418 else if (cStage == GoMinimum)
419 {
420 // Minimum position needs setting
421 if (sendCommand("#Sm;0!") == false)
422 return false;
423
424 IUSaveText(&CalibrationMessageT[0], "Move In/Move Out/Stop to MAX position then press NEXT");
425 IDSetText(&CalibrationMessageTP, nullptr);
426 cStage = GoMaximum;
427 }
428 else if (cStage == GoMaximum)
429 {
430 // Maximum position needs setting and save
431 // Do not split these commands.
432
433 if (sendCommand("#SM!", res) == false)
434 return false;
435 if (sendCommand("#PS!") == false)
436 return false;
437 //
438 // MAX value is in maxLimit
439 // MIN value is 0
440 //
441 int maxLimit = 0;
442 sscanf(res, "SM;%d!", &maxLimit);
443 LOGF_INFO("MAX setting is %d", maxLimit);
444
445 FocusMaxPosN[0].max = maxLimit;
446 FocusMaxPosN[0].value = maxLimit;
447
448 FocusAbsPosN[0].min = 0;
449 FocusAbsPosN[0].max = maxLimit;
450 FocusAbsPosN[0].value = maxLimit;
451 FocusAbsPosN[0].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
452
453 FocusRelPosN[0].min = 0.;
454 FocusRelPosN[0].max = FocusAbsPosN[0].step * 10;
455 FocusRelPosN[0].value = 0;
456 FocusRelPosN[0].step = FocusAbsPosN[0].step;
457
458 IUUpdateMinMax(&FocusAbsPosNP);
459 IUUpdateMinMax(&FocusRelPosNP);
460 FocusMaxPosNP.s = IPS_OK;
461 IUUpdateMinMax(&FocusMaxPosNP);
462
463 IUSaveText(&CalibrationMessageT[0], "Calibration Completed.");
464 IDSetText(&CalibrationMessageTP, nullptr);
465
466 deleteProperty(FastMoveSP.name);
467 cStage = Complete;
468
469 LOG_INFO("Calibration completed");
470 CalibrationSP.s = IPS_OK;
471 IDSetSwitch(&CalibrationSP, nullptr);
472 CalibrationS[current_switch].s = ISS_OFF;
473 IDSetSwitch(&CalibrationSP, nullptr);
474 }
475 else
476 {
477 IUSaveText(&CalibrationMessageT[0], "Calibration not in process");
478 IDSetText(&CalibrationMessageTP, nullptr);
479 }
480
481 }
482 return true;
483 }
484 else if (!strcmp(name, FastMoveSP.name))
485 {
486 IUUpdateSwitch(&FastMoveSP, states, names, n);
487 int current_switch = IUFindOnSwitchIndex(&FastMoveSP);
488
489 switch (current_switch)
490 {
491 case FASTMOVE_IN:
492 if (sendCommand("#FI!") == false)
493 {
494 return false;
495 }
496 break;
497 case FASTMOVE_OUT:
498 if (sendCommand("#FO!") == false)
499 {
500 return false;
501 }
502 break;
503 case FASTMOVE_STOP:
504 if (sendCommand("#MA!") == false)
505 {
506 return false;
507 }
508 break;
509 default:
510 break;
511 }
512
513 FastMoveSP.s = IPS_BUSY;
514 IDSetSwitch(&FastMoveSP, nullptr);
515 return true;
516 }
517
518 }
519 return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
520 }
521
MoveAbsFocuser(uint32_t targetTicks)522 IPState SestoSenso::MoveAbsFocuser(uint32_t targetTicks)
523 {
524 targetPos = targetTicks;
525
526 char cmd[SESTO_LEN] = {0};
527 snprintf(cmd, 16, "#GT%u!", targetTicks);
528 if (isSimulation() == false)
529 {
530 if (sendCommand(cmd) == false)
531 return IPS_ALERT;
532 }
533
534 m_MotionProgressTimer.start(10);
535 return IPS_BUSY;
536 }
537
MoveRelFocuser(FocusDirection dir,uint32_t ticks)538 IPState SestoSenso::MoveRelFocuser(FocusDirection dir, uint32_t ticks)
539 {
540 int reversed = (IUFindOnSwitchIndex(&FocusReverseSP) == INDI_ENABLED) ? -1 : 1;
541 int relativeTicks = ((dir == FOCUS_INWARD) ? -ticks : ticks) * reversed;
542 double newPosition = FocusAbsPosN[0].value + relativeTicks;
543
544 bool rc = MoveAbsFocuser(newPosition);
545
546 return (rc ? IPS_BUSY : IPS_ALERT);
547 }
548
AbortFocuser()549 bool SestoSenso::AbortFocuser()
550 {
551 m_MotionProgressTimer.stop();
552
553 if (isSimulation())
554 return true;
555
556 return sendCommand("#MA!");
557 }
558
559 //
560 // This timer function is initiated when a GT command has been issued
561 // A timer will call this function on a regular interval during the motion
562 // Modified the code to exit when motion is complete
563 //
checkMotionProgressCallback()564 void SestoSenso::checkMotionProgressCallback()
565 {
566 if (isMotionComplete())
567 {
568 FocusAbsPosNP.s = IPS_OK;
569 FocusRelPosNP.s = IPS_OK;
570 IDSetNumber(&FocusRelPosNP, nullptr);
571 IDSetNumber(&FocusAbsPosNP, nullptr);
572 lastPos = FocusAbsPosN[0].value;
573 LOG_INFO("Focuser reached requested position.");
574 return;
575 }
576 else
577 IDSetNumber(&FocusAbsPosNP, nullptr);
578
579 lastPos = FocusAbsPosN[0].value;
580
581 m_MotionProgressTimer.start(250);
582 }
583
TimerHit()584 void SestoSenso::TimerHit()
585 {
586 if (!isConnected() || FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY || CalibrationSP.s == IPS_BUSY)
587 {
588 SetTimer(getCurrentPollingPeriod());
589 return;
590 }
591
592 bool rc = updatePosition();
593 if (rc)
594 {
595 if (fabs(lastPos - FocusAbsPosN[0].value) > 0)
596 {
597 IDSetNumber(&FocusAbsPosNP, nullptr);
598 lastPos = FocusAbsPosN[0].value;
599 }
600 }
601
602 if (m_TemperatureCounter++ == SESTO_TEMPERATURE_FREQ)
603 {
604 rc = updateTemperature();
605 if (rc)
606 {
607 if (fabs(lastTemperature - TemperatureN[0].value) >= 0.1)
608 {
609 IDSetNumber(&TemperatureNP, nullptr);
610 lastTemperature = TemperatureN[0].value;
611 }
612 }
613 m_TemperatureCounter = 0; // Reset the counter
614 }
615
616 SetTimer(getCurrentPollingPeriod());
617 }
618
getStartupValues()619 bool SestoSenso::getStartupValues()
620 {
621 bool rc1 = updatePosition();
622 if (rc1)
623 IDSetNumber(&FocusAbsPosNP, nullptr);
624
625 if (updateMaxLimit() == false)
626 LOG_WARN("Check you have the latest SestoSenso firmware. Focuser requires calibration.");
627
628 return (rc1);
629 }
630
sendCommand(const char * cmd,char * res,int cmd_len,int res_len)631 bool SestoSenso::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
632 {
633 int nbytes_written = 0, nbytes_read = 0, rc = -1;
634
635 tcflush(PortFD, TCIOFLUSH);
636
637 if (cmd_len > 0)
638 {
639 char hex_cmd[SESTO_LEN * 3] = {0};
640 hexDump(hex_cmd, cmd, cmd_len);
641 LOGF_DEBUG("CMD <%s>", hex_cmd);
642 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
643 }
644 else
645 {
646 LOGF_DEBUG("CMD <%s>", cmd);
647 rc = tty_write_string(PortFD, cmd, &nbytes_written);
648 }
649
650 if (rc != TTY_OK)
651 {
652 char errstr[MAXRBUF] = {0};
653 tty_error_msg(rc, errstr, MAXRBUF);
654 LOGF_ERROR("Serial write error: %s.", errstr);
655 return false;
656 }
657
658 if (res == nullptr)
659 return true;
660
661 if (res_len > 0)
662 rc = tty_read(PortFD, res, res_len, SESTO_TIMEOUT, &nbytes_read);
663 else
664 {
665 rc = tty_nread_section(PortFD, res, SESTO_LEN, SESTO_STOP_CHAR, SESTO_TIMEOUT, &nbytes_read);
666 res[nbytes_read - 1] = 0;
667 }
668
669 if (rc != TTY_OK)
670 {
671 char errstr[MAXRBUF] = {0};
672 tty_error_msg(rc, errstr, MAXRBUF);
673 LOGF_ERROR("Serial read error: %s.", errstr);
674 return false;
675 }
676
677 if (res_len > 0)
678 {
679 char hex_res[SESTO_LEN * 3] = {0};
680 hexDump(hex_res, res, res_len);
681 LOGF_DEBUG("RES <%s>", hex_res);
682 }
683 else
684 {
685 LOGF_DEBUG("RES <%s>", res);
686 }
687
688 tcflush(PortFD, TCIOFLUSH);
689
690 return true;
691 }
692
hexDump(char * buf,const char * data,int size)693 void SestoSenso::hexDump(char * buf, const char * data, int size)
694 {
695 for (int i = 0; i < size; i++)
696 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
697
698 if (size > 0)
699 buf[3 * size - 1] = '\0';
700 }
701
ReverseFocuser(bool enable)702 bool SestoSenso::ReverseFocuser(bool enable)
703 {
704 INDI_UNUSED(enable);
705 return false;
706 }
707