Instrument Neutral Distributed Interface INDI 2.2.5
sestosenso3.cpp
Go to the documentation of this file.
1/*
2 SestoSenso 3 Focuser
3 Copyright (C) 2026 Jasem Mutlaq
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*/
20
21#include "sestosenso3.h"
22
23#include "indicom.h"
24
25#include <cmath>
26#include <cstring>
27#include <memory>
28#include <algorithm>
29
30#include <assert.h>
31#include <termios.h>
32#include <unistd.h>
34#include <sys/ioctl.h>
35
36static std::unique_ptr<SestoSenso3> sesto3(new SestoSenso3());
37
38static const char *MOTOR_TAB = "Motor";
39static const char *ENVIRONMENT_TAB = "Environment";
40const char *MOTOR_PRESET_NAMES[] = { "light", "medium", "slow" };
41
43{
44 setVersion(1, 0);
45
47
48 m_MotionProgressTimer.callOnTimeout(std::bind(&SestoSenso3::checkMotionProgressCallback, this));
49 m_MotionProgressTimer.setSingleShot(true);
50}
51
56{
58
59 FocusBacklashNP[0].setMin(0);
60 FocusBacklashNP[0].setMax(10000);
61 FocusBacklashNP[0].setStep(1);
62 FocusBacklashNP[0].setValue(0);
63
64 setConnectionParams();
65
66 // Firmware information
67 FirmwareTP[FIRMWARE_SN].fill("SERIALNUMBER", "Serial Number", "");
68 FirmwareTP[FIRMWARE_VERSION].fill("VERSION", "Version", "");
69 FirmwareTP.fill(getDeviceName(), "FOCUS_FIRMWARE", "Firmware", CONNECTION_TAB, IP_RO, 0, IPS_IDLE);
70
71 // Voltage Information
72 VoltageInNP[0].fill("VOLTAGEIN", "Volts", "%.2f", 0, 100, 0., 0.);
73 VoltageInNP.fill(getDeviceName(), "VOLTAGE_IN", "Voltage in", ENVIRONMENT_TAB, IP_RO, 0, IPS_IDLE);
74
75 // Focuser temperature
76 TemperatureNP[TEMPERATURE_MOTOR].fill("TEMPERATURE", "Motor (c)", "%.2f", -50, 70., 0., 0.);
77 TemperatureNP[TEMPERATURE_EXTERNAL].fill("TEMPERATURE_ETX", "External (c)", "%.2f", -50, 70., 0., 0.);
78 TemperatureNP.fill(getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", ENVIRONMENT_TAB, IP_RO, 0, IPS_IDLE);
79
80 // Calibration message
81 CalibrationMessageTP[0].fill("CALIBRATION", "Calibration stage", "Press START to begin the Calibration.");
82 CalibrationMessageTP.fill(getDeviceName(), "CALIBRATION_MESSAGE", "Calibration", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
83
84 // Calibration switches (SC only has START, others have START + NEXT)
85 CalibrationSP[CALIBRATION_START].fill("CALIBRATION_START", "Start", ISS_OFF);
86 CalibrationSP[CALIBRATION_NEXT].fill("CALIBRATION_NEXT", "Next", ISS_OFF);
87 CalibrationSP.fill(getDeviceName(), "FOCUS_CALIBRATION", "Calibration", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
88
89 // Recovery delay
90 RecoveryDelayNP[RECOVERY_DELAY_VALUE].fill("RECOVERY_DELAY", "Recovery Delay (s)", "%.0f", -1, 120, 1, 0);
91 RecoveryDelayNP.fill(getDeviceName(), "RECOVERY_DELAY_PROP", "Recovery Delay", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
92
93 // Fast move switches
94 FastMoveSP[FASTMOVE_IN].fill("FASTMOVE_IN", "Move In", ISS_OFF);
95 FastMoveSP[FASTMOVE_OUT].fill("FASTMOVE_OUT", "Move out", ISS_OFF);
96 FastMoveSP[FASTMOVE_STOP].fill("FASTMOVE_STOP", "Stop", ISS_OFF);
97 FastMoveSP.fill(getDeviceName(), "FAST_MOVE", "Calibration Move", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
98
99 // Semi-automatic calibration move switches
100 MoveInOut100SP[MOVE_IN_100].fill("MOVE_IN_100", "Move In 100", ISS_OFF);
101 MoveInOut100SP[MOVE_OUT_100].fill("MOVE_OUT_100", "Move Out 100", ISS_OFF);
102 MoveInOut100SP.fill(getDeviceName(), "SEMI_AUTO_MOVE_100", "Move 100 Steps", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 0,
103 IPS_IDLE);
104
105 MoveInOut500SP[MOVE_IN_500].fill("MOVE_IN_500", "Move In 500", ISS_OFF);
106 MoveInOut500SP[MOVE_OUT_500].fill("MOVE_OUT_500", "Move Out 500", ISS_OFF);
107 MoveInOut500SP.fill(getDeviceName(), "SEMI_AUTO_MOVE_500", "Move 500 Steps", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 0,
108 IPS_IDLE);
109
110 MoveInOut1000SP[MOVE_IN_1000].fill("MOVE_IN_1000", "Move In 1000", ISS_OFF);
111 MoveInOut1000SP[MOVE_OUT_1000].fill("MOVE_OUT_1000", "Move Out 1000", ISS_OFF);
112 MoveInOut1000SP.fill(getDeviceName(), "SEMI_AUTO_MOVE_1000", "Move 1000 Steps", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 0,
113 IPS_IDLE);
114
115 // Motor hold
116 MotorHoldSP[MOTOR_HOLD_ON].fill("HOLD_ON", "Hold On", ISS_OFF);
117 MotorHoldSP[MOTOR_HOLD_OFF].fill("HOLD_OFF", "Hold Off", ISS_OFF);
118 MotorHoldSP.fill(getDeviceName(), "MOTOR_HOLD", "Motor Hold", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
119
120 // Max position (read-only)
121 FocusMaxPosNP.fill(getDeviceName(), "FOCUS_MAX", "Max. Position", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
122
123 // Motor rate
124 MotorRateNP[MOTOR_RATE_ACC].fill("ACC", "Acceleration", "%.f", 1, 10, 1, 1);
125 MotorRateNP[MOTOR_RATE_RUN].fill("RUN", "Run Speed", "%.f", 1, 10, 1, 2);
126 MotorRateNP[MOTOR_RATE_DEC].fill("DEC", "Deceleration", "%.f", 1, 10, 1, 1);
127 MotorRateNP.fill(getDeviceName(), "MOTOR_RATE", "Motor Rate", MOTOR_TAB, IP_RW, 0, IPS_IDLE);
128
129 // Motor current
130 MotorCurrentNP[MOTOR_CURR_ACC].fill("CURR_ACC", "Acceleration", "%.f", 1, 10, 1, 7);
131 MotorCurrentNP[MOTOR_CURR_RUN].fill("CURR_RUN", "Run", "%.f", 1, 10, 1, 7);
132 MotorCurrentNP[MOTOR_CURR_DEC].fill("CURR_DEC", "Deceleration", "%.f", 1, 10, 1, 7);
133 MotorCurrentNP[MOTOR_CURR_HOLD].fill("CURR_HOLD", "Hold", "%.f", 0, 5, 1, 3);
134 MotorCurrentNP.fill(getDeviceName(), "MOTOR_CURRENT", "Current", MOTOR_TAB, IP_RW, 0, IPS_IDLE);
135
136 // Motor presets
137 MotorApplyPresetSP[MOTOR_APPLY_LIGHT].fill("MOTOR_APPLY_LIGHT", "Light", ISS_OFF);
138 MotorApplyPresetSP[MOTOR_APPLY_MEDIUM].fill("MOTOR_APPLY_MEDIUM", "Medium", ISS_OFF);
139 MotorApplyPresetSP[MOTOR_APPLY_HEAVY].fill("MOTOR_APPLY_HEAVY", "Heavy", ISS_OFF);
140 MotorApplyPresetSP.fill(getDeviceName(), "MOTOR_APPLY_PRESET", "Apply Preset", MOTOR_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
141
142 // Relative and absolute movement
143 FocusRelPosNP[0].setMin(0.);
144 FocusRelPosNP[0].setMax(50000.);
145 FocusRelPosNP[0].setValue(0);
146 FocusRelPosNP[0].setStep(1000);
147
148 FocusAbsPosNP[0].setMin(0.);
149 FocusAbsPosNP[0].setMax(200000.);
150 FocusAbsPosNP[0].setValue(0);
151 FocusAbsPosNP[0].setStep(1000);
152
153 FocusMaxPosNP[0].setValue(2097152);
154 PresetNP[0].setMax(FocusMaxPosNP[0].getValue());
155 PresetNP[1].setMax(FocusMaxPosNP[0].getValue());
156 PresetNP[2].setMax(FocusMaxPosNP[0].getValue());
157
160
161 return true;
162}
163
168{
169 if (isConnected() && updateMaxLimit() == false)
170 LOGF_WARN("Check you have the latest %s firmware. Focuser requires calibration.", getDeviceName());
171
173
174 if (isConnected())
175 {
176 defineProperty(CalibrationMessageTP);
177
178 // SC model only has START (automatic), others have START + NEXT
179 if (m_SubModel == SESTOSENSO3_SC)
180 {
181 CalibrationSP.resize(1);
182 }
183 defineProperty(CalibrationSP);
184
185 defineProperty(RecoveryDelayNP);
186 int32_t delay = 0;
187 if (m_SestoSenso3->getRecoveryDelay(delay))
188 {
189 RecoveryDelayNP[RECOVERY_DELAY_VALUE].setValue(delay);
190 RecoveryDelayNP.setState(IPS_OK);
191 }
192 else
193 {
194 RecoveryDelayNP.setState(IPS_ALERT);
195 }
196 RecoveryDelayNP.apply();
197
198 defineProperty(MotorRateNP);
199 defineProperty(MotorCurrentNP);
200 defineProperty(MotorHoldSP);
201 defineProperty(MotorApplyPresetSP);
202 defineProperty(FirmwareTP);
203
204 if (updateTemperature())
205 defineProperty(TemperatureNP);
206
207 if (updateVoltageIn())
208 defineProperty(VoltageInNP);
209
210 if (getStartupValues())
211 LOG_INFO("Parameters updated, focuser ready for use.");
212 else
213 LOG_WARN("Failed to inquire parameters. Check logs.");
214 }
215 else
216 {
217 if (TemperatureNP.getState() == IPS_OK)
218 deleteProperty(TemperatureNP);
219 deleteProperty(FirmwareTP);
220 deleteProperty(VoltageInNP);
221 deleteProperty(CalibrationMessageTP);
222 deleteProperty(CalibrationSP);
223 deleteProperty(RecoveryDelayNP);
224 deleteProperty(MotorRateNP);
225 deleteProperty(MotorCurrentNP);
226 deleteProperty(MotorHoldSP);
227 deleteProperty(MotorApplyPresetSP);
228 }
229
230 return true;
231}
232
237{
238 if (Ack())
239 {
240 LOGF_INFO("%s is online. Getting focus parameters...", getDeviceName());
241 return true;
242 }
243
244 LOG_INFO("Error retrieving data from device, please ensure focuser is powered and the port is correct.");
245 return false;
246}
247
252{
253 return m_SestoSenso3->setBacklash(steps);
254}
255
260{
261 return "Sesto Senso 3";
262}
263
267bool SestoSenso3::updateTemperature()
268{
269 double temperature = 0;
270
271 if (isSimulation())
272 temperature = 23.5;
273 else if (m_SestoSenso3->getMotorTemp(temperature) == false)
274 return false;
275
276 if (temperature > 90)
277 return false;
278
279 TemperatureNP[TEMPERATURE_MOTOR].setValue(temperature);
280 TemperatureNP.setState(IPS_OK);
281
282 if (m_SestoSenso3->getExternalTemp(temperature))
283 {
284 if (temperature < 90)
285 TemperatureNP[TEMPERATURE_EXTERNAL].setValue(temperature);
286 else
287 TemperatureNP[TEMPERATURE_EXTERNAL].setValue(-273.15);
288 }
289
290 return true;
291}
292
296bool SestoSenso3::updateMaxLimit()
297{
298 uint32_t maxLimit = 0;
299
300 if (isSimulation())
301 return true;
302
303 if (m_SestoSenso3->getMaxPosition(maxLimit) == false)
304 return false;
305
306 FocusMaxPosNP[0].setMax(maxLimit);
307 if (FocusMaxPosNP[0].getValue() > maxLimit)
308 FocusMaxPosNP[0].setValue(maxLimit);
309
310 FocusAbsPosNP[0].setMin(0);
311 FocusAbsPosNP[0].setMax(maxLimit);
312 FocusAbsPosNP[0].setValue(0);
313 FocusAbsPosNP[0].setStep((FocusAbsPosNP[0].getMax() - FocusAbsPosNP[0].getMin()) / 50.0);
314
315 FocusRelPosNP[0].setMin(0.);
316 FocusRelPosNP[0].setMax(FocusAbsPosNP[0].getStep() * 10);
317 FocusRelPosNP[0].setValue(0);
318 FocusRelPosNP[0].setStep(FocusAbsPosNP[0].getStep());
319
320 PresetNP[0].setMax(maxLimit);
321 PresetNP[0].setStep((FocusAbsPosNP[0].getMax() - FocusAbsPosNP[0].getMin()) / 50.0);
322 PresetNP[1].setMax(maxLimit);
323 PresetNP[1].setStep((FocusAbsPosNP[0].getMax() - FocusAbsPosNP[0].getMin()) / 50.0);
324 PresetNP[2].setMax(maxLimit);
325 PresetNP[2].setStep((FocusAbsPosNP[0].getMax() - FocusAbsPosNP[0].getMin()) / 50.0);
326
328 return true;
329}
330
334bool SestoSenso3::updatePosition()
335{
336 uint32_t steps = 0;
337 if (isSimulation())
338 steps = static_cast<uint32_t>(FocusAbsPosNP[0].getValue());
339 else if (m_SestoSenso3->getAbsolutePosition(steps) == false)
340 return false;
341
342 FocusAbsPosNP[0].setValue(steps);
343 return true;
344}
345
349bool SestoSenso3::updateVoltageIn()
350{
351 double voltageIn = 0;
352
353 if (isSimulation())
354 voltageIn = 12.0;
355 else if (m_SestoSenso3->getVoltage12v(voltageIn) == false)
356 return false;
357
358 if (voltageIn > 24)
359 return false;
360
361 VoltageInNP[0].setValue(voltageIn);
362 VoltageInNP.setState((voltageIn >= 11.0) ? IPS_OK : IPS_ALERT);
363
364 return true;
365}
366
370bool SestoSenso3::fetchMotorSettings()
371{
374 bool motorHoldActive = false;
375
376 if (isSimulation())
377 {
378 ms.accRate = 1;
379 ms.runSpeed = 2;
380 ms.decRate = 1;
381 mc.accCurrent = 3;
382 mc.runCurrent = 4;
383 mc.decCurrent = 3;
384 mc.holdCurrent = 2;
385 }
386 else
387 {
388 if (!m_SestoSenso3->getMotorSettings(ms, mc, motorHoldActive))
389 {
390 MotorRateNP.setState(IPS_IDLE);
391 MotorCurrentNP.setState(IPS_IDLE);
392 MotorHoldSP.setState(IPS_IDLE);
393 return false;
394 }
395 }
396
397 MotorRateNP[MOTOR_RATE_ACC].setValue(ms.accRate);
398 MotorRateNP[MOTOR_RATE_RUN].setValue(ms.runSpeed);
399 MotorRateNP[MOTOR_RATE_DEC].setValue(ms.decRate);
400 MotorRateNP.setState(IPS_OK);
401 MotorRateNP.apply();
402
403 MotorCurrentNP[MOTOR_CURR_ACC].setValue(mc.accCurrent);
404 MotorCurrentNP[MOTOR_CURR_RUN].setValue(mc.runCurrent);
405 MotorCurrentNP[MOTOR_CURR_DEC].setValue(mc.decCurrent);
406 MotorCurrentNP[MOTOR_CURR_HOLD].setValue(mc.holdCurrent);
407 MotorCurrentNP.setState(IPS_OK);
408 MotorCurrentNP.apply();
409
410 auto activeSwitchID = motorHoldActive ? "HOLD_ON" : "HOLD_OFF";
411 auto sp = MotorHoldSP.findWidgetByName(activeSwitchID);
412 assert(sp != nullptr && "Motor hold switch not found");
413 if (sp)
414 {
415 MotorHoldSP.reset();
416 sp->setState(ISS_ON);
417 MotorHoldSP.setState(motorHoldActive ? IPS_OK : IPS_ALERT);
418 MotorHoldSP.apply();
419 }
420
421 if (motorHoldActive && mc.holdCurrent == 0)
422 LOG_WARN("Motor hold current set to 0, motor hold setting will have no effect");
423
424 return true;
425}
426
430bool SestoSenso3::applyMotorRates()
431{
432 if (isSimulation())
433 return true;
434
436 mr.accRate = static_cast<uint32_t>(MotorRateNP[MOTOR_RATE_ACC].getValue());
437 mr.runSpeed = static_cast<uint32_t>(MotorRateNP[MOTOR_RATE_RUN].getValue());
438 mr.decRate = static_cast<uint32_t>(MotorRateNP[MOTOR_RATE_DEC].getValue());
439
440 if (!m_SestoSenso3->setMotorRates(mr))
441 {
442 LOG_ERROR("Failed to apply motor rates");
443 return false;
444 }
445
446 LOGF_INFO("Motor rates applied: Acc: %u Run: %u Dec: %u", mr.accRate, mr.runSpeed, mr.decRate);
447 return true;
448}
449
453bool SestoSenso3::applyMotorCurrents()
454{
455 if (isSimulation())
456 return true;
457
459 mc.accCurrent = static_cast<uint32_t>(MotorCurrentNP[MOTOR_CURR_ACC].getValue());
460 mc.runCurrent = static_cast<uint32_t>(MotorCurrentNP[MOTOR_CURR_RUN].getValue());
461 mc.decCurrent = static_cast<uint32_t>(MotorCurrentNP[MOTOR_CURR_DEC].getValue());
462 mc.holdCurrent = static_cast<uint32_t>(MotorCurrentNP[MOTOR_CURR_HOLD].getValue());
463
464 if (!m_SestoSenso3->setMotorCurrents(mc))
465 {
466 LOG_ERROR("Failed to apply motor currents");
467 return false;
468 }
469
470 LOGF_INFO("Motor currents applied: Acc: %u Run: %u Dec: %u Hold: %u", mc.accCurrent, mc.runCurrent, mc.decCurrent,
471 mc.holdCurrent);
472 return true;
473}
474
478bool SestoSenso3::isMotionComplete()
479{
480 if (isSimulation())
481 {
482 int32_t nextPos = FocusAbsPosNP[0].getValue();
483 int32_t targPos = static_cast<int32_t>(targetPos);
484
485 if (targPos > nextPos)
486 nextPos += 250;
487 else if (targPos < nextPos)
488 nextPos -= 250;
489
490 if (abs(nextPos - targPos) < 250)
491 nextPos = targetPos;
492 else if (nextPos < 0)
493 nextPos = 0;
494 else if (nextPos > FocusAbsPosNP[0].getMax())
495 nextPos = FocusAbsPosNP[0].getMax();
496
497 FocusAbsPosNP[0].setValue(nextPos);
498 return (std::abs(nextPos - static_cast<int32_t>(targetPos)) == 0);
499 }
500
501 return !m_SestoSenso3->isBusy();
502}
503
507bool SestoSenso3::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
508{
509 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
510 {
511 // Calibration
512 if (CalibrationSP.isNameMatch(name))
513 {
514 CalibrationSP.update(states, names, n);
515 auto current_switch = CalibrationSP.findOnSwitchIndex();
516 CalibrationSP[current_switch].setState(ISS_ON);
517 CalibrationSP.apply();
518
519 if (current_switch == CALIBRATION_START)
520 {
521 if (cStage == Idle || cStage == Complete)
522 {
523 LOG_INFO("Start Calibration");
524 CalibrationSP.setState(IPS_BUSY);
525 CalibrationSP.apply();
526
527 if (m_SubModel == SESTOSENSO3_SC)
528 {
529 if (m_SestoSenso3->startAutoCalibration() == false)
530 return false;
531 CalibrationMessageTP[0].setText("Automatic Calibration Started. Please wait...");
532 CalibrationMessageTP.apply();
533 cStage = GoToMiddle;
534 }
535 else
536 {
537 // STANDARD and LS: Semi-Automatic Calibration
538 if (m_SestoSenso3->initCalibrationSemiAuto() == false)
539 return false;
540 CalibrationMessageTP[0].setText("Semi-Automatic Calibration: Move focuser to MIN position and then press NEXT.");
541 CalibrationMessageTP.apply();
542 fetchMotorSettings();
543 cStage = GoToMiddle;
544
545 defineProperty(MoveInOut100SP);
546 defineProperty(MoveInOut500SP);
547 defineProperty(MoveInOut1000SP);
548 }
549 }
550 else
551 {
552 LOG_INFO("Already started calibration. Proceed to next step.");
553 CalibrationMessageTP[0].setText("Already started. Proceed to NEXT.");
554 CalibrationMessageTP.apply();
555 }
556 }
557 else if (current_switch == CALIBRATION_NEXT)
558 {
559 if (m_SubModel == SESTOSENSO3_SC)
560 {
561 LOG_ERROR("CALIBRATION_NEXT not applicable for SestoSenso3 SC Automatic Calibration.");
562 CalibrationMessageTP[0].setText("Error: NEXT not applicable for Automatic Calibration.");
563 CalibrationMessageTP.apply();
564 CalibrationSP.setState(IPS_ALERT);
565 CalibrationSP.apply();
566 return false;
567 }
568
569 if (cStage == GoToMiddle)
570 {
571 defineProperty(FastMoveSP);
572 if (m_SestoSenso3->storeAsMinPosition() == false)
573 return false;
574 CalibrationMessageTP[0].setText("Semi-Automatic Calibration: Use Fast Move OUT to find MAX position, then press STOP, then NEXT.");
575 CalibrationMessageTP.apply();
576 cStage = GoMinimum;
577 }
578 else if (cStage == GoMinimum)
579 {
580 if (m_SestoSenso3->storeAsMaxPosition() == false)
581 return false;
582 CalibrationMessageTP[0].setText("Calibration: Press NEXT to finish.");
583 CalibrationMessageTP.apply();
584 cStage = GoMaximum;
585 }
586 else if (cStage == GoMaximum)
587 {
588 uint32_t maxLimit = 0;
589 if (m_SestoSenso3->getMaxPosition(maxLimit) == false)
590 return false;
591
592 LOGF_INFO("MAX setting is %d", maxLimit);
593
594 FocusMaxPosNP[0].setMax(maxLimit);
595 FocusMaxPosNP[0].setValue(maxLimit);
596
597 FocusAbsPosNP[0].setMin(0);
598 FocusAbsPosNP[0].setMax(maxLimit);
599 FocusAbsPosNP[0].setValue(maxLimit);
600 FocusAbsPosNP[0].setStep((FocusAbsPosNP[0].getMax() - FocusAbsPosNP[0].getMin()) / 50.0);
601
602 FocusRelPosNP[0].setMin(0.);
603 FocusRelPosNP[0].setMax(FocusAbsPosNP[0].getStep() * 10);
604 FocusRelPosNP[0].setValue(0);
605 FocusRelPosNP[0].setStep(FocusAbsPosNP[0].getStep());
606
607 PresetNP[0].setMax(maxLimit);
608 PresetNP[0].setStep((FocusAbsPosNP[0].getMax() - FocusAbsPosNP[0].getMin()) / 50.0);
609 PresetNP[1].setMax(maxLimit);
610 PresetNP[1].setStep((FocusAbsPosNP[0].getMax() - FocusAbsPosNP[0].getMin()) / 50.0);
611 PresetNP[2].setMax(maxLimit);
612 PresetNP[2].setStep((FocusAbsPosNP[0].getMax() - FocusAbsPosNP[0].getMin()) / 50.0);
613
619
620 CalibrationMessageTP[0].setText("Calibration Completed.");
621 CalibrationMessageTP.apply();
622
623 deleteProperty(FastMoveSP);
624 deleteProperty(MoveInOut100SP);
625 deleteProperty(MoveInOut500SP);
626 deleteProperty(MoveInOut1000SP);
627
628 cStage = Complete;
629
630 LOG_INFO("Calibration completed");
631 CalibrationSP.setState(IPS_OK);
632 CalibrationSP.apply();
633 CalibrationSP[current_switch].setState(ISS_OFF);
634 CalibrationSP.apply();
635
636 fetchMotorSettings();
637 }
638 else
639 {
640 CalibrationMessageTP[0].setText("Calibration not in progress.");
641 CalibrationMessageTP.apply();
642 }
643 }
644 return true;
645 }
646 // Fast motion
647 else if (FastMoveSP.isNameMatch(name))
648 {
649 FastMoveSP.update(states, names, n);
650 auto current_switch = FastMoveSP.findOnSwitchIndex();
651
652 switch (current_switch)
653 {
654 case FASTMOVE_IN:
655 if (m_SestoSenso3->fastMoveIn() == false)
656 return false;
657 break;
658 case FASTMOVE_OUT:
659 if (m_SestoSenso3->goOutToFindMaxPos() == false)
660 return false;
661 CalibrationMessageTP[0].setText("Semi-Automatic Calibration: Press STOP when focuser is almost at MAX position.");
662 CalibrationMessageTP.apply();
663 fetchMotorSettings();
664 break;
665 case FASTMOVE_STOP:
666 if (m_SestoSenso3->stopMotor() == false)
667 return false;
668 CalibrationMessageTP[0].setText("Press NEXT to store max limit.");
669 CalibrationMessageTP.apply();
670 break;
671 default:
672 break;
673 }
674
675 FastMoveSP.setState(IPS_BUSY);
676 FastMoveSP.apply();
677 return true;
678 }
679 // Semi-automatic calibration move switches
680 else if (MoveInOut100SP.isNameMatch(name))
681 {
682 MoveInOut100SP.update(states, names, n);
683 auto current_move_switch = MoveInOut100SP.findOnSwitchIndex();
684 if (current_move_switch == MOVE_IN_100)
685 {
686 if (m_SestoSenso3->moveIn(100) == false) return false;
687 }
688 else if (current_move_switch == MOVE_OUT_100)
689 {
690 if (m_SestoSenso3->moveOut(100) == false) return false;
691 }
692 MoveInOut100SP.setState(IPS_IDLE);
693 MoveInOut100SP.apply();
694 return true;
695 }
696 else if (MoveInOut500SP.isNameMatch(name))
697 {
698 MoveInOut500SP.update(states, names, n);
699 auto current_move_switch = MoveInOut500SP.findOnSwitchIndex();
700 if (current_move_switch == MOVE_IN_500)
701 {
702 if (m_SestoSenso3->moveIn(500) == false) return false;
703 }
704 else if (current_move_switch == MOVE_OUT_500)
705 {
706 if (m_SestoSenso3->moveOut(500) == false) return false;
707 }
708 MoveInOut500SP.setState(IPS_IDLE);
709 MoveInOut500SP.apply();
710 return true;
711 }
712 else if (MoveInOut1000SP.isNameMatch(name))
713 {
714 MoveInOut1000SP.update(states, names, n);
715 auto current_move_switch = MoveInOut1000SP.findOnSwitchIndex();
716 if (current_move_switch == MOVE_IN_1000)
717 {
718 if (m_SestoSenso3->moveIn(1000) == false) return false;
719 }
720 else if (current_move_switch == MOVE_OUT_1000)
721 {
722 if (m_SestoSenso3->moveOut(1000) == false) return false;
723 }
724 MoveInOut1000SP.setState(IPS_IDLE);
725 MoveInOut1000SP.apply();
726 return true;
727 }
728 // Motor hold
729 else if (MotorHoldSP.isNameMatch(name))
730 {
731 MotorHoldSP.update(states, names, n);
732 auto sp = MotorHoldSP.findOnSwitch();
733 assert(sp != nullptr);
734
735 if (!strcmp(sp->name, "HOLD_OFF"))
736 {
737 m_SestoSenso3->setMotorHold(false);
738 MotorHoldSP.setState(IPS_ALERT);
739 LOG_INFO("Motor hold OFF. You may now manually adjust the focuser. Remember to enable motor hold once done.");
740 }
741 else
742 {
743 m_SestoSenso3->setMotorHold(true);
744 MotorHoldSP.setState(IPS_OK);
745 LOG_INFO("Motor hold ON. Do NOT attempt to manually adjust the focuser!");
746 if (MotorCurrentNP[MOTOR_CURR_HOLD].getValue() < 2.0)
747 {
748 LOGF_WARN("Motor hold current set to %.1f: This may be insufficient to hold focus",
749 MotorCurrentNP[MOTOR_CURR_HOLD].getValue());
750 }
751 }
752
753 MotorHoldSP.apply();
754 return true;
755 }
756 else if (MotorApplyPresetSP.isNameMatch(name))
757 {
758 MotorApplyPresetSP.update(states, names, n);
759 auto index = MotorApplyPresetSP.findOnSwitchIndex();
760 assert(index >= 0 && index < 3);
761
762 const char *presetName = MOTOR_PRESET_NAMES[index];
763
764 if (m_SestoSenso3->applyMotorPreset(presetName))
765 {
766 LOGF_INFO("Loaded motor preset: %s", presetName);
767 MotorApplyPresetSP.setState(IPS_IDLE);
768 }
769 else
770 {
771 LOGF_ERROR("Failed to load motor preset: %s", presetName);
772 MotorApplyPresetSP.setState(IPS_ALERT);
773 }
774
775 MotorApplyPresetSP[index].setState(ISS_OFF);
776 MotorApplyPresetSP.apply();
777
778 fetchMotorSettings();
779 return true;
780 }
781 }
782 return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
783}
784
788bool SestoSenso3::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
789{
790 if (dev == nullptr || strcmp(dev, getDeviceName()) != 0)
791 return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
792
793 if (MotorRateNP.isNameMatch(name))
794 {
795 MotorRateNP.update(values, names, n);
796 MotorRateNP.setState(IPS_OK);
797 applyMotorRates();
798 MotorRateNP.apply();
799 return true;
800 }
801 else if (MotorCurrentNP.isNameMatch(name))
802 {
803 MotorCurrentNP.update(values, names, n);
804 MotorCurrentNP.setState(IPS_OK);
805 applyMotorCurrents();
806 MotorCurrentNP.apply();
807 return true;
808 }
809 else if (RecoveryDelayNP.isNameMatch(name))
810 {
811 RecoveryDelayNP.update(values, names, n);
812 int32_t delay = static_cast<int32_t>(RecoveryDelayNP[RECOVERY_DELAY_VALUE].getValue());
813 if (m_SestoSenso3->setRecoveryDelay(delay))
814 {
815 RecoveryDelayNP.setState(IPS_OK);
816 LOGF_INFO("Recovery Delay set to %d seconds.", delay);
817 }
818 else
819 {
820 RecoveryDelayNP.setState(IPS_ALERT);
821 LOG_ERROR("Failed to set Recovery Delay.");
822 }
823 saveConfig(RecoveryDelayNP);
824 RecoveryDelayNP.apply();
825 return true;
826 }
827
828 return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
829}
830
835{
836 targetPos = targetTicks;
837
838 if (isSimulation() == false)
839 {
840 backlashDirection = targetTicks < lastPos ? FOCUS_INWARD : FOCUS_OUTWARD;
841 if (backlashDirection == FOCUS_INWARD)
842 targetPos -= backlashTicks;
843 else
844 targetPos += backlashTicks;
845
846 if (m_SestoSenso3->goAbsolutePosition(targetPos) == false)
847 return IPS_ALERT;
848 }
849
850 m_MotionProgressTimer.start(10);
851 return IPS_BUSY;
852}
853
858{
859 int reversed = (FocusReverseSP.findOnSwitchIndex() == INDI_ENABLED) ? -1 : 1;
860 int relativeTicks = ((dir == FOCUS_INWARD) ? -ticks : ticks) * reversed;
861 double newPosition = FocusAbsPosNP[0].getValue() + relativeTicks;
862
863 bool rc = MoveAbsFocuser(newPosition);
864
865 return (rc ? IPS_BUSY : IPS_ALERT);
866}
867
872{
873 m_MotionProgressTimer.stop();
874
875 if (isSimulation())
876 return true;
877
878 return m_SestoSenso3->stop();
879}
880
884void SestoSenso3::checkMotionProgressCallback()
885{
887 lastPos = FocusAbsPosNP[0].getValue();
888 if (isMotionComplete())
889 {
891
892 if (CalibrationSP.getState() == IPS_BUSY)
893 {
894 ISState states[2] = { ISS_OFF, ISS_ON };
895 const char *names[2] = { CalibrationSP[CALIBRATION_START].getName(), CalibrationSP[CALIBRATION_NEXT].getName() };
896 ISNewSwitch(getDeviceName(), CalibrationSP.getName(), states, const_cast<char **>(names), CalibrationSP.count());
897 }
898 else
899 LOG_INFO("Focuser reached requested position.");
902 return;
903 }
904
905 m_MotionProgressTimer.start(500);
906}
907
912{
913 if (!isConnected() ||
916 CalibrationSP.getState() == IPS_BUSY)
917 {
919 return;
920 }
921
922 bool rc = updatePosition();
923 if (rc)
924 {
925 if (fabs(lastPos - FocusAbsPosNP[0].getValue()) > 0)
926 {
928 lastPos = FocusAbsPosNP[0].getValue();
929 }
930 }
931
932 if (m_TemperatureCounter++ == SESTO_TEMPERATURE_FREQ)
933 {
934 rc = updateTemperature();
935 if (rc)
936 {
937 if (fabs(lastTemperature - TemperatureNP[0].getValue()) >= 0.1)
938 {
939 TemperatureNP.apply();
940 lastTemperature = TemperatureNP[0].getValue();
941 }
942 }
943
944 rc = updateVoltageIn();
945 if (rc)
946 {
947 if (fabs(lastVoltageIn - VoltageInNP[0].getValue()) >= 0.1)
948 {
949 VoltageInNP.apply();
950 lastVoltageIn = VoltageInNP[0].getValue();
951
952 if (VoltageInNP[0].getValue() < 11.0)
953 LOG_WARN("Please check 12v DC power supply is connected.");
954 }
955 }
956
957 m_TemperatureCounter = 0;
958 }
959
961}
962
966bool SestoSenso3::getStartupValues()
967{
968 bool rc = updatePosition();
969 if (rc)
971
972 rc &= fetchMotorSettings();
973
974 return rc;
975}
976
981{
982 INDI_UNUSED(enable);
983 return false;
984}
985
989bool SestoSenso3::Ack()
990{
991 std::string response;
992
993 if (isSimulation())
994 response = "1.0 Simulation";
995 else
996 {
997 if (initCommandSet() == false)
998 {
999 LOG_ERROR("Failed setting attributes on serial port and init command sets");
1000 return false;
1001 }
1002 if (m_SestoSenso3->getSerialNumber(response))
1003 {
1004 LOGF_INFO("Serial number: %s", response.c_str());
1005 }
1006 else
1007 {
1008 return false;
1009 }
1010
1011 std::string modelName;
1012 if (m_SestoSenso3->getModel(modelName))
1013 {
1014 LOGF_INFO("Model name: %s", modelName.c_str());
1015
1016 std::string subModel;
1017 if (m_SestoSenso3->getSubModel(subModel))
1018 {
1019 LOGF_INFO("SubModel: %s", subModel.c_str());
1020
1021 if (subModel.find("SESTOSENSO3SC") != std::string::npos)
1022 {
1023 m_SubModel = SESTOSENSO3_SC;
1024 LOG_INFO("Detected: SestoSenso3 SC (Automatic Calibration)");
1025 }
1026 else if (subModel.find("SESTOSENSO3LS") != std::string::npos)
1027 {
1028 m_SubModel = SESTOSENSO3_LS;
1029 LOG_INFO("Detected: SestoSenso3 LS (Semi-Automatic Calibration)");
1030 }
1031 else
1032 {
1033 m_SubModel = SESTOSENSO3_STANDARD;
1034 LOG_INFO("Detected: SestoSenso3 Standard (Semi-Automatic Calibration)");
1035 }
1036 }
1037 else
1038 {
1039 m_SubModel = SESTOSENSO3_STANDARD;
1040 LOG_WARN("Failed to query submodel, defaulting to SestoSenso3 Standard");
1041 }
1042 }
1043 }
1044
1045 FirmwareTP[FIRMWARE_SN].setText(response.c_str());
1046
1047 if (m_SestoSenso3->getFirmwareVersion(response))
1048 {
1049 LOGF_INFO("Firmware version: %s", response.c_str());
1050 IUSaveText(&FirmwareTP[FIRMWARE_VERSION], response.c_str());
1051 }
1052 else
1053 {
1054 return false;
1055 }
1056
1057 return true;
1058}
1059
1063void SestoSenso3::setConnectionParams()
1064{
1067}
1068
1072bool SestoSenso3::initCommandSet()
1073{
1074 m_SestoSenso3.reset(new PrimalucaLabs::SestoSenso3(getDeviceName(), PortFD));
1075
1076 struct termios tty_setting;
1077 if (tcgetattr(PortFD, &tty_setting) == -1)
1078 {
1079 LOG_ERROR("setTTYFlags: failed getting tty attributes.");
1080 return false;
1081 }
1082 tty_setting.c_lflag |= ICANON;
1083 if (tcsetattr(PortFD, TCSANOW, &tty_setting))
1084 {
1085 LOG_ERROR("setTTYFlags: failed setting attributes on serial port.");
1086 return false;
1087 }
1088 return true;
1089}
1090
1095{
1096 Focuser::saveConfigItems(fp);
1097 MotorRateNP.save(fp);
1098 MotorCurrentNP.save(fp);
1099 return true;
1100}
double temperature
void setWordSize(const uint8_t &value)
setWordSize Set word size to be used in the serial connection. Default 8
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
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.
bool isSimulation() const
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 FocusMaxPosNP
INDI::PropertyNumber FocusBacklashNP
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 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
INDI::PropertyNumber PresetNP
void setState(IPState state)
WidgetView< T > * findWidgetByName(const char *name) const
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
void resize(size_t size)
const char * getName() const
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)
INDI::WidgetViewSwitch * findOnSwitch() const
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)
void setSingleShot(bool singleShot)
Set whether the timer is a single-shot timer.
void callOnTimeout(const std::function< void()> &callback)
Definition inditimer.cpp:76
void start()
Starts or restarts the timer with the timeout specified in interval.
Definition inditimer.cpp:82
void stop()
Stops the timer.
Definition inditimer.cpp:97
virtual bool ReverseFocuser(bool enabled) override
ReverseFocuser Reverse focuser motion direction.
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 IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
const char * getDefaultName() override
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
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 void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
virtual bool saveConfigItems(FILE *fp) override
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
virtual bool Handshake() override
Performs a device-specific handshake after a connection is established. This method should be overrid...
const char * CONNECTION_TAB
CONNECTION_TAB Where all device connection settings (serial, usb, ethernet) are defined and controlle...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
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_ATMOST1
Definition indiapi.h:172
Implementations for common driver routines.
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition indidevapi.c:44
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOG_WARN(txt)
Definition indilogger.h:73
#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
#define ENVIRONMENT_TAB
const char * MOTOR_PRESET_NAMES[]
const char * MOTOR_PRESET_NAMES[]
struct termios tty_setting
Definition stvdriver.c:51