Instrument Neutral Distributed Interface INDI  2.0.2
sestosenso2.cpp
Go to the documentation of this file.
1 /*
2  SestoSenso 2 Focuser
3  Copyright (C) 2020 Piotr Zyziuk
4  Copyright (C) 2020 Jasem Mutlaq (Added Esatto support)
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Lesser General Public
8  License as published by the Free Software Foundation; either
9  version 2.1 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public
17  License along with this library; if not, write to the Free Software
18  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 
20 */
21 
22 #include "sestosenso2.h"
23 
24 #include "indicom.h"
25 
26 #include <cmath>
27 #include <cstring>
28 #include <memory>
29 #include <algorithm>
30 
31 #include <assert.h>
32 #include <termios.h>
33 #include <unistd.h>
35 #include <sys/ioctl.h>
36 
37 static std::unique_ptr<SestoSenso2> sesto(new SestoSenso2());
38 
39 static const char *MOTOR_TAB = "Motor";
40 static const char *ENVIRONMENT_TAB = "Environment";
41 // Settings names for the default motor settings presets
42 const char *MOTOR_PRESET_NAMES[] = { "light", "medium", "slow" };
43 
45 {
46  setVersion(1, 0);
47 
48  // Can move in Absolute & Relative motions, can AbortFocuser motion.
50 
51  m_MotionProgressTimer.callOnTimeout(std::bind(&SestoSenso2::checkMotionProgressCallback, this));
52  m_MotionProgressTimer.setSingleShot(true);
53 
54  // m_HallSensorTimer.callOnTimeout(std::bind(&SestoSenso2::checkHallSensorCallback, this));
55  // m_HallSensorTimer.setSingleShot(true);
56  // m_HallSensorTimer.setInterval(1000);
57 }
58 
63 {
64 
66 
67  FocusBacklashN[0].min = 0;
68  FocusBacklashN[0].max = 10000;
69  FocusBacklashN[0].step = 1;
70  FocusBacklashN[0].value = 0;
71 
72  setConnectionParams();
73 
74  // Firmware information
75  FirmwareTP[FIRMWARE_SN].fill("SERIALNUMBER", "Serial Number", "");
76  FirmwareTP[FIRMWARE_VERSION].fill("VERSION", "Version", "");
77  FirmwareTP.fill(getDeviceName(), "FOCUS_FIRMWARE", "Firmware", CONNECTION_TAB, IP_RO, 0, IPS_IDLE);
78 
79  // Voltage Information
80  VoltageInNP[0].fill("VOLTAGEIN", "Volts", "%.2f", 0, 100, 0., 0.);
81  VoltageInNP.fill(getDeviceName(), "VOLTAGE_IN", "Voltage in", ENVIRONMENT_TAB, IP_RO, 0, IPS_IDLE);
82 
83  // Focuser temperature
84  TemperatureNP[TEMPERATURE_MOTOR].fill("TEMPERATURE", "Motor (c)", "%.2f", -50, 70., 0., 0.);
85  TemperatureNP[TEMPERATURE_EXTERNAL].fill("TEMPERATURE_ETX", "External (c)", "%.2f", -50, 70., 0., 0.);
86  TemperatureNP.fill(getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", ENVIRONMENT_TAB, IP_RO, 0, IPS_IDLE);
87 
88  // Focuser calibration
89  CalibrationMessageTP[0].fill("CALIBRATION", "Calibration stage", "Press START to begin the Calibration.");
90  CalibrationMessageTP.fill(getDeviceName(), "CALIBRATION_MESSAGE", "Calibration", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
91 
92  // Calibration
93  CalibrationSP[CALIBRATION_START].fill("CALIBRATION_START", "Start", ISS_OFF);
94  CalibrationSP[CALIBRATION_NEXT].fill("CALIBRATION_NEXT", "Next", ISS_OFF);
95  CalibrationSP.fill(getDeviceName(), "FOCUS_CALIBRATION", "Calibration", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
96 
97  // Speed Moves
98  FastMoveSP[FASTMOVE_IN].fill("FASTMOVE_IN", "Move In", ISS_OFF);
99  FastMoveSP[FASTMOVE_OUT].fill("FASTMOVE_OUT", "Move out", ISS_OFF);
100  FastMoveSP[FASTMOVE_STOP].fill("FASTMOVE_STOP", "Stop", ISS_OFF);
101  FastMoveSP.fill(getDeviceName(), "FAST_MOVE", "Calibration Move", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
102 
103  // Hold state
104  MotorHoldSP[MOTOR_HOLD_ON].fill("HOLD_ON", "Hold On", ISS_OFF);
105  MotorHoldSP[MOTOR_HOLD_OFF].fill("HOLD_OFF", "Hold Off", ISS_OFF);
106  MotorHoldSP.fill(getDeviceName(), "MOTOR_HOLD", "Motor Hold", MAIN_CONTROL_TAB,
107  IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
108 
109  // Override the default Max. Position to make it Read-Only
110  IUFillNumberVector(&FocusMaxPosNP, FocusMaxPosN, 1, getDeviceName(), "FOCUS_MAX", "Max. Position", MAIN_CONTROL_TAB, IP_RO,
111  0, IPS_IDLE);
112 
113  // Motor rate
114  MotorRateNP[MOTOR_RATE_ACC].fill("ACC", "Acceleration", "%.f", 1, 10, 1, 1);
115  MotorRateNP[MOTOR_RATE_RUN].fill("RUN", "Run Speed", "%.f", 1, 10, 1, 2);
116  MotorRateNP[MOTOR_RATE_DEC].fill("DEC", "Deceleration", "%.f", 1, 10, 1, 1);
117  MotorRateNP.fill(getDeviceName(), "MOTOR_RATE", "Motor Rate", MOTOR_TAB, IP_RW, 0, IPS_IDLE);
118 
119  // Motor current
120  MotorCurrentNP[MOTOR_CURR_ACC].fill("CURR_ACC", "Acceleration", "%.f", 1, 10, 1, 7);
121  MotorCurrentNP[MOTOR_CURR_RUN].fill("CURR_RUN", "Run", "%.f", 1, 10, 1, 7);
122  MotorCurrentNP[MOTOR_CURR_DEC].fill("CURR_DEC", "Deceleration", "%.f", 1, 10, 1, 7);
123  MotorCurrentNP[MOTOR_CURR_HOLD].fill("CURR_HOLD", "Hold", "%.f", 0, 5, 1, 3);
124  MotorCurrentNP.fill(getDeviceName(), "MOTOR_CURRENT", "Current", MOTOR_TAB, IP_RW, 0, IPS_IDLE);
125 
126  // Load motor preset
127  MotorApplyPresetSP[MOTOR_APPLY_LIGHT].fill("MOTOR_APPLY_LIGHT", "Light", ISS_OFF);
128  MotorApplyPresetSP[MOTOR_APPLY_MEDIUM].fill("MOTOR_APPLY_MEDIUM", "Medium", ISS_OFF);
129  MotorApplyPresetSP[MOTOR_APPLY_HEAVY].fill("MOTOR_APPLY_HEAVY", "Heavy", ISS_OFF);
130  MotorApplyPresetSP.fill(getDeviceName(), "MOTOR_APPLY_PRESET", "Apply Preset", MOTOR_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
131 
132  // Load user preset
133  MotorApplyUserPresetSP[MOTOR_APPLY_USER1].fill("MOTOR_APPLY_USER1", "User 1", ISS_OFF);
134  MotorApplyUserPresetSP[MOTOR_APPLY_USER2].fill("MOTOR_APPLY_USER2", "User 2", ISS_OFF);
135  MotorApplyUserPresetSP[MOTOR_APPLY_USER3].fill("MOTOR_APPLY_USER3", "User 3", ISS_OFF);
136  MotorApplyUserPresetSP.fill(getDeviceName(), "MOTOR_APPLY_USER_PRESET", "Apply Custom", MOTOR_TAB, IP_RW, ISR_ATMOST1, 0,
137  IPS_IDLE);
138 
139  // Save user preset
140  MotorSaveUserPresetSP[MOTOR_SAVE_USER1].fill("MOTOR_SAVE_USER1", "User 1", ISS_OFF);
141  MotorSaveUserPresetSP[MOTOR_SAVE_USER2].fill("MOTOR_SAVE_USER2", "User 2", ISS_OFF);
142  MotorSaveUserPresetSP[MOTOR_SAVE_USER3].fill("MOTOR_SAVE_USER3", "User 3", ISS_OFF);
143  MotorSaveUserPresetSP.fill(getDeviceName(), "MOTOR_SAVE_USER_PRESET", "Save Custom", MOTOR_TAB, IP_RW, ISR_ATMOST1, 0,
144  IPS_IDLE);
145 
146  // Relative and absolute movement
147  FocusRelPosN[0].min = 0.;
148  FocusRelPosN[0].max = 50000.;
149  FocusRelPosN[0].value = 0;
150  FocusRelPosN[0].step = 1000;
151 
152  FocusAbsPosN[0].min = 0.;
153  FocusAbsPosN[0].max = 200000.;
154  FocusAbsPosN[0].value = 0;
155  FocusAbsPosN[0].step = 1000;
156 
157  FocusMaxPosN[0].value = 2097152;
158  PresetN[0].max = FocusMaxPosN[0].value;
159  PresetN[1].max = FocusMaxPosN[0].value;
160  PresetN[2].max = FocusMaxPosN[0].value;
161 
162  addAuxControls();
163 
165 
166  return true;
167 }
168 
173 {
174  if (isConnected() && updateMaxLimit() == false)
175  LOGF_WARN("Check you have the latest %s firmware. Focuser requires calibration.", getDeviceName());
176 
178 
179  if (isConnected())
180  {
181  defineProperty(CalibrationMessageTP);
182  defineProperty(CalibrationSP);
183  defineProperty(MotorRateNP);
184  defineProperty(MotorCurrentNP);
185  defineProperty(MotorHoldSP);
186  defineProperty(MotorApplyPresetSP);
187  defineProperty(MotorApplyUserPresetSP);
188  defineProperty(MotorSaveUserPresetSP);
189 
190  defineProperty(FirmwareTP);
191 
192  if (updateTemperature())
193  defineProperty(TemperatureNP);
194 
195  if (updateVoltageIn())
196  defineProperty(VoltageInNP);
197 
198  if (getStartupValues())
199  LOG_INFO("Parameters updated, focuser ready for use.");
200  else
201  LOG_WARN("Failed to inquire parameters. Check logs.");
202  }
203  else
204  {
205  if (TemperatureNP.getState() == IPS_OK)
206  deleteProperty(TemperatureNP);
207  deleteProperty(FirmwareTP);
208  deleteProperty(VoltageInNP);
209  deleteProperty(CalibrationMessageTP);
210  deleteProperty(CalibrationSP);
211  deleteProperty(MotorRateNP);
212  deleteProperty(MotorCurrentNP);
213  deleteProperty(MotorHoldSP);
214  deleteProperty(MotorApplyPresetSP);
215  deleteProperty(MotorApplyUserPresetSP);
216  deleteProperty(MotorSaveUserPresetSP);
217  }
218 
219  return true;
220 }
221 
226 {
227  if (Ack())
228  {
229  LOGF_INFO("%s is online. Getting focus parameters...", getDeviceName());
230  return true;
231  }
232 
233  LOG_INFO("Error retrieving data from device, please ensure focuser is powered and the port is correct.");
234  return false;
235 }
236 
241 {
242  return m_SestoSenso2->setBacklash(steps);
243 }
244 
249 {
250  return "Sesto Senso 2";
251 }
252 
256 bool SestoSenso2::updateTemperature()
257 {
258  double temperature = 0;
259 
260  if (isSimulation())
261  temperature = 23.5;
262  else if ( m_SestoSenso2->getMotorTemp(temperature) == false)
263  return false;
264 
265  if (temperature > 90)
266  return false;
267 
268  TemperatureNP[TEMPERATURE_MOTOR].setValue(temperature);
269  TemperatureNP.setState(IPS_OK);
270 
271  // External temperature - Optional
272  if (m_SestoSenso2->getExternalTemp(temperature))
273  {
274  if (temperature < 90)
275  TemperatureNP[TEMPERATURE_EXTERNAL].setValue(temperature);
276  else
277  TemperatureNP[TEMPERATURE_EXTERNAL].setValue(-273.15);
278  }
279 
280  return true;
281 }
282 
286 bool SestoSenso2::updateMaxLimit()
287 {
288  uint32_t maxLimit = 0;
289 
290  if (isSimulation())
291  return true;
292 
293  if (m_SestoSenso2->getMaxPosition(maxLimit) == false)
294  return false;
295 
296  FocusMaxPosN[0].max = maxLimit;
297  if (FocusMaxPosN[0].value > maxLimit)
298  FocusMaxPosN[0].value = maxLimit;
299 
300  FocusAbsPosN[0].min = 0;
301  FocusAbsPosN[0].max = maxLimit;
302  FocusAbsPosN[0].value = 0;
303  FocusAbsPosN[0].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
304 
305  FocusRelPosN[0].min = 0.;
306  FocusRelPosN[0].max = FocusAbsPosN[0].step * 10;
307  FocusRelPosN[0].value = 0;
308  FocusRelPosN[0].step = FocusAbsPosN[0].step;
309 
310  PresetN[0].max = maxLimit;
311  PresetN[0].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
312  PresetN[1].max = maxLimit;
313  PresetN[1].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
314  PresetN[2].max = maxLimit;
315  PresetN[2].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
316 
317 
319  return true;
320 }
321 
325 bool SestoSenso2::updatePosition()
326 {
327  uint32_t steps = 0;
328  if (isSimulation())
329  steps = static_cast<uint32_t>(FocusAbsPosN[0].value);
330  else if (m_SestoSenso2->getAbsolutePosition(steps) == false)
331  return false;
332 
333  FocusAbsPosN[0].value = steps;
335  return true;
336 }
337 
341 bool SestoSenso2::updateVoltageIn()
342 {
343  double voltageIn = 0;
344 
345  if (isSimulation())
346  voltageIn = 12.0;
347  else if (m_SestoSenso2->getVoltage12v(voltageIn) == false)
348  return false;
349 
350  if (voltageIn > 24)
351  return false;
352 
353  VoltageInNP[0].setValue(voltageIn);
354  VoltageInNP.setState((voltageIn >= 11.0) ? IPS_OK : IPS_ALERT);
355 
356  return true;
357 }
358 
362 bool SestoSenso2::fetchMotorSettings()
363 {
364  // Fetch driver state and reflect in INDI
367  bool motorHoldActive = false;
368 
369  if (isSimulation())
370  {
371  ms.accRate = 1;
372  ms.runSpeed = 2;
373  ms.decRate = 1;
374  mc.accCurrent = 3;
375  mc.runCurrent = 4;
376  mc.decCurrent = 3;
377  mc.holdCurrent = 2;
378  }
379  else
380  {
381  if (!m_SestoSenso2->getMotorSettings(ms, mc, motorHoldActive))
382  {
383  MotorRateNP.setState(IPS_IDLE);
384  MotorCurrentNP.setState(IPS_IDLE);
385  MotorHoldSP.setState(IPS_IDLE);
386  return false;
387  }
388  }
389 
390  MotorRateNP[MOTOR_RATE_ACC].setValue(ms.accRate);
391  MotorRateNP[MOTOR_RATE_RUN].setValue(ms.runSpeed);
392  MotorRateNP[MOTOR_RATE_DEC].setValue(ms.decRate);
393  MotorRateNP.setState(IPS_OK);
394  MotorRateNP.apply();
395 
396  MotorCurrentNP[MOTOR_CURR_ACC].setValue(mc.accCurrent);
397  MotorCurrentNP[MOTOR_CURR_RUN].setValue(mc.runCurrent);
398  MotorCurrentNP[MOTOR_CURR_DEC].setValue(mc.decCurrent);
399  MotorCurrentNP[MOTOR_CURR_HOLD].setValue(mc.holdCurrent);
400  MotorCurrentNP.setState(IPS_OK);
401  MotorCurrentNP.apply();
402 
403  // Also update motor hold switch
404  auto activeSwitchID = motorHoldActive ? "HOLD_ON" : "HOLD_OFF";
405  auto sp = MotorHoldSP.findWidgetByName(activeSwitchID);
406  assert(sp != nullptr && "Motor hold switch not found");
407  if (sp)
408  {
409  MotorHoldSP.reset();
410  sp->setState(ISS_ON);
411  MotorHoldSP.setState(motorHoldActive ? IPS_OK : IPS_ALERT);
412  MotorHoldSP.apply();
413  }
414 
415  if (motorHoldActive && mc.holdCurrent == 0)
416  {
417  LOG_WARN("Motor hold current set to 0, motor hold setting will have no effect");
418  }
419 
420  return true;
421 }
422 
426 bool SestoSenso2::applyMotorRates()
427 {
428  if (isSimulation())
429  return true;
430 
431  // Send INDI state to driver
433  mr.accRate = static_cast<uint32_t>(MotorRateNP[MOTOR_RATE_ACC].getValue());
434  mr.runSpeed = static_cast<uint32_t>(MotorRateNP[MOTOR_RATE_RUN].getValue());
435  mr.decRate = static_cast<uint32_t>(MotorRateNP[MOTOR_RATE_DEC].getValue());
436 
437  if (!m_SestoSenso2->setMotorRates(mr))
438  {
439  LOG_ERROR("Failed to apply motor rates");
440  // TODO: Error state?
441  return false;
442  }
443 
444  LOGF_INFO("Motor rates applied: Acc: %u Run: %u Dec: %u", mr.accRate, mr.runSpeed, mr.decRate);
445  return true;
446 }
447 
451 bool SestoSenso2::applyMotorCurrents()
452 {
453  if (isSimulation())
454  return true;
455 
456  // Send INDI state to driver
458  mc.accCurrent = static_cast<uint32_t>(MotorCurrentNP[MOTOR_CURR_ACC].getValue());
459  mc.runCurrent = static_cast<uint32_t>(MotorCurrentNP[MOTOR_CURR_RUN].getValue());
460  mc.decCurrent = static_cast<uint32_t>(MotorCurrentNP[MOTOR_CURR_DEC].getValue());
461  mc.holdCurrent = static_cast<uint32_t>(MotorCurrentNP[MOTOR_CURR_HOLD].getValue());
462 
463  if (!m_SestoSenso2->setMotorCurrents(mc))
464  {
465  LOG_ERROR("Failed to apply motor currents");
466  return false;
467  }
468 
469  LOGF_INFO("Motor currents applied: Acc: %u Run: %u Dec: %u Hold: %u", mc.accCurrent, mc.runCurrent, mc.decCurrent,
470  mc.holdCurrent);
471  return true;
472 }
473 
477 bool SestoSenso2::isMotionComplete()
478 {
479  if (isSimulation())
480  {
481  int32_t nextPos = FocusAbsPosN[0].value;
482  int32_t targPos = static_cast<int32_t>(targetPos);
483 
484  if (targPos > nextPos)
485  nextPos += 250;
486  else if (targPos < nextPos)
487  nextPos -= 250;
488 
489  if (abs(nextPos - targPos) < 250)
490  nextPos = targetPos;
491  else if (nextPos < 0)
492  nextPos = 0;
493  else if (nextPos > FocusAbsPosN[0].max)
494  nextPos = FocusAbsPosN[0].max;
495 
496  FocusAbsPosN[0].value = nextPos;
497  return (std::abs(nextPos - static_cast<int32_t>(targetPos)) == 0);
498  }
499 
500  return !m_SestoSenso2->isBusy();
501 }
502 
506 bool SestoSenso2::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
507 {
508  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
509  {
510  // Calibrate focuser
511  if (CalibrationSP.isNameMatch(name))
512  {
513  CalibrationSP.update(states, names, n);
514  auto current_switch = CalibrationSP.findOnSwitchIndex();
515  CalibrationSP[current_switch].setState(ISS_ON);
516  CalibrationSP.apply();
517 
518  if (current_switch == CALIBRATION_START)
519  {
520  if (cStage == Idle || cStage == Complete )
521  {
522  // Start the calibration process
523  LOG_INFO("Start Calibration");
524  CalibrationSP.setState(IPS_BUSY);
525  CalibrationSP.apply();
526 
527  //
528  // Init
529  //
530  if (m_IsSestoSenso2 && m_SestoSenso2->initCalibration() == false)
531  return false;
532 
533  CalibrationMessageTP[0].setText("Set focus in MIN position and then press NEXT.");
534  CalibrationMessageTP.apply();
535 
536  // Motor hold disabled during calibration init, so fetch new hold state
537  fetchMotorSettings();
538 
539  // Set next step
540  cStage = GoToMiddle;
541  }
542  else
543  {
544  LOG_INFO("Already started calibration. Proceed to next step.");
545  CalibrationMessageTP[0].setText("Already started. Proceed to NEXT.");
546  CalibrationMessageTP.apply();
547  }
548  }
549  else if (current_switch == CALIBRATION_NEXT)
550  {
551  if (cStage == GoToMiddle)
552  {
553  defineProperty(FastMoveSP);
554  if (m_IsSestoSenso2)
555  {
556  if (m_SestoSenso2->storeAsMinPosition() == false)
557  return false;
558 
559  CalibrationMessageTP[0].setText("Press MOVE OUT to move focuser out (CAUTION!)");
560  CalibrationMessageTP.apply();
561  cStage = GoMinimum;
562  }
563  // For Esatto, start moving out immediately
564  else
565  {
566  cStage = GoMaximum;
567 
568  ISState fs[1] = { ISS_ON };
569  const char *fn[1] = { FastMoveSP[FASTMOVE_OUT].getName() };
570  ISNewSwitch(getDeviceName(), FastMoveSP.getName(), fs, const_cast<char **>(fn), 1);
571  }
572  }
573  else if (cStage == GoMinimum)
574  {
575  if (m_SestoSenso2->storeAsMaxPosition() == false)
576  return false;
577 
578  CalibrationMessageTP[0].setText("Press NEXT to finish.");
579  CalibrationMessageTP.apply();
580  cStage = GoMaximum;
581  }
582  else if (cStage == GoMaximum)
583  {
584  uint32_t maxLimit = 0;
585 
586  if (m_SestoSenso2->getMaxPosition(maxLimit) == false)
587  return false;
588 
589  LOGF_INFO("MAX setting is %d", maxLimit);
590 
591  FocusMaxPosN[0].max = maxLimit;
592  FocusMaxPosN[0].value = maxLimit;
593 
594  FocusAbsPosN[0].min = 0;
595  FocusAbsPosN[0].max = maxLimit;
596  FocusAbsPosN[0].value = maxLimit;
597  FocusAbsPosN[0].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
598 
599  FocusRelPosN[0].min = 0.;
600  FocusRelPosN[0].max = FocusAbsPosN[0].step * 10;
601  FocusRelPosN[0].value = 0;
602  FocusRelPosN[0].step = FocusAbsPosN[0].step;
603 
604  PresetN[0].max = maxLimit;
605  PresetN[0].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
606  PresetN[1].max = maxLimit;
607  PresetN[1].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
608  PresetN[2].max = maxLimit;
609  PresetN[2].step = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 50.0;
610 
616 
617  CalibrationMessageTP[0].setText("Calibration Completed.");
618  CalibrationMessageTP.apply();
619 
620  deleteProperty(FastMoveSP);
621  cStage = Complete;
622 
623  LOG_INFO("Calibration completed");
624  CalibrationSP.setState(IPS_OK);
625  CalibrationSP.apply();
626  CalibrationSP[current_switch].setState(ISS_OFF);
627  CalibrationSP.apply();
628 
629  // Double check motor hold state after calibration
630  fetchMotorSettings();
631  }
632  else
633  {
634  CalibrationMessageTP[0].setText("Calibration not in progress.");
635  CalibrationMessageTP.apply();
636  }
637 
638  }
639  return true;
640  }
641  // Fast motion
642  else if (FastMoveSP.isNameMatch(name))
643  {
644  FastMoveSP.update(states, names, n);
645  auto current_switch = FastMoveSP.findOnSwitchIndex();
646 
647  switch (current_switch)
648  {
649  case FASTMOVE_IN:
650  if (m_SestoSenso2->fastMoveIn() == false)
651  return false;
652  break;
653  case FASTMOVE_OUT:
654  // NOT CORRECT FIX ME
655  // Only use when calibration active?
656  if (m_IsSestoSenso2)
657  {
658  if (m_SestoSenso2->goOutToFindMaxPos() == false)
659  {
660  return false;
661  }
662  CalibrationMessageTP[0].setText("Press STOP focuser almost at MAX position.");
663  fetchMotorSettings();
664  }
665  else
666  {
667  if (m_SestoSenso2->fastMoveOut())
668  {
669  CalibrationMessageTP[0].setText("Focusing out to detect hall sensor.");
670  m_MotionProgressTimer.start(500);
671  }
672  }
673  CalibrationMessageTP.apply();
674  break;
675  case FASTMOVE_STOP:
676  if (m_SestoSenso2->stop() == false)
677  {
678  return false;
679  }
680  CalibrationMessageTP[0].setText("Press NEXT to store max limit.");
681  CalibrationMessageTP.apply();
682  break;
683  default:
684  break;
685  }
686 
687  FastMoveSP.setState(IPS_BUSY);
688  FastMoveSP.apply();
689  return true;
690  }
691  // Homing
692  else if (MotorHoldSP.isNameMatch(name))
693  {
694  MotorHoldSP.update(states, names, n);
695  auto sp = MotorHoldSP.findOnSwitch();
696  assert(sp != nullptr);
697 
698  // NOTE: Default to HOLD_ON as a safety feature
699  if (!strcmp(sp->name, "HOLD_OFF"))
700  {
701  m_SestoSenso2->setMotorHold(false);
702  MotorHoldSP.setState(IPS_ALERT);
703  LOG_INFO("Motor hold OFF. You may now manually adjust the focuser. Remember to enable motor hold once done.");
704  }
705  else
706  {
707  m_SestoSenso2->setMotorHold(true);
708  MotorHoldSP.setState(IPS_OK);
709  LOG_INFO("Motor hold ON. Do NOT attempt to manually adjust the focuser!");
710  if (MotorCurrentNP[MOTOR_CURR_HOLD].getValue() < 2.0)
711  {
712  LOGF_WARN("Motor hold current set to %.1f: This may be insufficent to hold focus",
713  MotorCurrentNP[MOTOR_CURR_HOLD].getValue());
714  }
715  }
716 
717  MotorHoldSP.apply();
718  return true;
719  }
720  else if (MotorApplyPresetSP.isNameMatch(name))
721  {
722  MotorApplyPresetSP.update(states, names, n);
723  auto index = MotorApplyPresetSP.findOnSwitchIndex();
724  assert(index >= 0 && index < 3);
725 
726  const char* presetName = MOTOR_PRESET_NAMES[index];
727 
728  if (m_SestoSenso2->applyMotorPreset(presetName))
729  {
730  LOGF_INFO("Loaded motor preset: %s", presetName);
731  MotorApplyPresetSP.setState(IPS_IDLE);
732  }
733  else
734  {
735  LOGF_ERROR("Failed to load motor preset: %s", presetName);
736  MotorApplyPresetSP.setState(IPS_ALERT);
737  }
738 
739  MotorApplyPresetSP[index].setState(ISS_OFF);
740  MotorApplyPresetSP.apply();
741 
742  fetchMotorSettings();
743  return true;
744  }
745  // else if (MotorApplyUserPresetSP->isNameMatch(name))
746  // {
747  // MotorApplyUserPresetSP.update(states, names, n);
748  // auto index = IUFindOnSwitchIndex(&MotorApplyUserPresetSP);
749  // assert(index >= 0 && index < 3);
750  // uint32_t userIndex = index + 1;
751 
752  // if (m_SestoSenso2->applyMotorPreset(userIndex))
753  // {
754  // LOGF_INFO("Loaded motor user preset: %u", userIndex);
755  // MotorApplyUserPresetSP.setState(IPS_IDLE);
756  // }
757  // else
758  // {
759  // LOGF_ERROR("Failed to load motor user preset: %u", userIndex);
760  // MotorApplyUserPresetSP.s = IPS_ALERT;
761  // }
762 
763  // MotorApplyUserPresetS[index].s = ISS_OFF;
764  // IDSetSwitch(&MotorApplyUserPresetSP, nullptr);
765 
766  // fetchMotorSettings();
767  // return true;
768  // }
769  // else if (!strcmp(name, MotorSaveUserPresetSP.name))
770  // {
771  // IUUpdateSwitch(&MotorSaveUserPresetSP, states, names, n);
772  // int index = IUFindOnSwitchIndex(&MotorSaveUserPresetSP);
773  // assert(index >= 0 && index < 3);
774  // uint32_t userIndex = index + 1;
775 
776  // MotorRates mr;
777  // mr.accRate = static_cast<uint32_t>(MotorRateN[MOTOR_RATE_ACC].value);
778  // mr.runSpeed = static_cast<uint32_t>(MotorRateN[MOTOR_RATE_RUN].value);
779  // mr.decRate = static_cast<uint32_t>(MotorRateN[MOTOR_RATE_DEC].value);
780 
781  // MotorCurrents mc;
782  // mc.accCurrent = static_cast<uint32_t>(MotorCurrentN[MOTOR_CURR_ACC].value);
783  // mc.runCurrent = static_cast<uint32_t>(MotorCurrentN[MOTOR_CURR_RUN].value);
784  // mc.decCurrent = static_cast<uint32_t>(MotorCurrentN[MOTOR_CURR_DEC].value);
785  // mc.holdCurrent = static_cast<uint32_t>(MotorCurrentN[MOTOR_CURR_HOLD].value);
786 
787  // if (command->saveMotorUserPreset(userIndex, mr, mc))
788  // {
789  // LOGF_INFO("Saved motor user preset %u to firmware", userIndex);
790  // MotorSaveUserPresetSP.s = IPS_IDLE;
791  // }
792  // else
793  // {
794  // LOGF_ERROR("Failed to save motor user preset %u to firmware", userIndex);
795  // MotorSaveUserPresetSP.s = IPS_ALERT;
796  // }
797 
798  // MotorSaveUserPresetS[index].s = ISS_OFF;
799  // IDSetSwitch(&MotorSaveUserPresetSP, nullptr);
800  // return true;
801  // }
802  }
803  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
804 }
805 
809 bool SestoSenso2::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
810 {
811  if (dev == nullptr || strcmp(dev, getDeviceName()) != 0)
812  return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
813 
814  if (MotorRateNP.isNameMatch(name))
815  {
816  MotorRateNP.update(values, names, n);
817  MotorRateNP.setState(IPS_OK);
818  applyMotorRates();
819  MotorRateNP.apply();
820  return true;
821  }
822  else if (MotorCurrentNP.isNameMatch(name))
823  {
824  MotorCurrentNP.update(values, names, n);
825  MotorCurrentNP.setState(IPS_OK);
826  applyMotorCurrents();
827  MotorCurrentNP.apply();
828  return true;
829  }
830 
831  return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
832 }
833 
838 {
839  targetPos = targetTicks;
840 
841  if (isSimulation() == false)
842  {
843  backlashDirection = targetTicks < lastPos ? FOCUS_INWARD : FOCUS_OUTWARD;
844  if(backlashDirection == FOCUS_INWARD)
845  {
846  targetPos -= backlashTicks;
847  }
848  else
849  {
850  targetPos += backlashTicks;
851  }
852  if (m_SestoSenso2->goAbsolutePosition(targetPos) == false)
853  return IPS_ALERT;
854  }
855 
856  m_MotionProgressTimer.start(10);
857  return IPS_BUSY;
858 }
859 
864 {
865  int reversed = (IUFindOnSwitchIndex(&FocusReverseSP) == INDI_ENABLED) ? -1 : 1;
866  int relativeTicks = ((dir == FOCUS_INWARD) ? -ticks : ticks) * reversed;
867  double newPosition = FocusAbsPosN[0].value + relativeTicks;
868 
869  bool rc = MoveAbsFocuser(newPosition);
870 
871  return (rc ? IPS_BUSY : IPS_ALERT);
872 }
873 
878 {
879  // if (m_MotionProgressTimerID > 0)
880  // {
881  // IERmTimer(m_MotionProgressTimerID);
882  // m_MotionProgressTimerID = -1;
883  // }
884 
885  m_MotionProgressTimer.stop();
886 
887  if (isSimulation())
888  return true;
889 
890  return m_SestoSenso2->stop();
891 }
892 
896 void SestoSenso2::checkMotionProgressCallback()
897 {
898  if (isMotionComplete())
899  {
902  IDSetNumber(&FocusRelPosNP, nullptr);
903  IDSetNumber(&FocusAbsPosNP, nullptr);
904  lastPos = FocusAbsPosN[0].value;
905 
906  if (CalibrationSP.getState() == IPS_BUSY)
907  {
908  ISState states[2] = { ISS_OFF, ISS_ON };
909  const char * names[2] = { CalibrationSP[CALIBRATION_START].getName(), CalibrationSP[CALIBRATION_NEXT].getName() };
910  ISNewSwitch(getDeviceName(), CalibrationSP.getName(), states, const_cast<char **>(names), CalibrationSP.count());
911  }
912  else
913  LOG_INFO("Focuser reached requested position.");
914  return;
915  }
916  else
917  {
918  IDSetNumber(&FocusAbsPosNP, nullptr);
919  }
920 
921  lastPos = FocusAbsPosN[0].value;
922  m_MotionProgressTimer.start(500);
923 }
924 
928 //void SestoSenso2::checkHallSensorCallback()
929 //{
930 // // FIXME
931 // // Function not getting call from anywhere?
932 // char res[SESTO_LEN] = {0};
933 // if (command->getHallSensor(res))
934 // {
935 // int detected = 0;
936 // if (sscanf(res, "%d", &detected) == 1)
937 // {
938 // if (detected == 1)
939 // {
940 // ISState states[2] = { ISS_OFF, ISS_ON };
941 // const char * names[2] = { CalibrationS[CALIBRATION_START].name, CalibrationS[CALIBRATION_NEXT].name };
942 // ISNewSwitch(getDeviceName(), CalibrationSP.name, states, const_cast<char **>(names), CalibrationSP.nsp);
943 // return;
944 // }
945 // }
946 // }
947 
948 // //m_HallSensorTimerID = IEAddTimer(1000, &SestoSenso2::checkHallSensorHelper, this);
949 // m_HallSensorTimer.start();
950 //}
951 
956 {
957  if (!isConnected() ||
959  || FocusRelPosNP.s == IPS_BUSY ||
960  (m_IsSestoSenso2 && CalibrationSP.getState() == IPS_BUSY))
961  {
963  return;
964  }
965 
966  bool rc = updatePosition();
967  if (rc)
968  {
969  if (fabs(lastPos - FocusAbsPosN[0].value) > 0)
970  {
971  IDSetNumber(&FocusAbsPosNP, nullptr);
972  lastPos = FocusAbsPosN[0].value;
973  }
974  }
975 
976  if (m_TemperatureCounter++ == SESTO_TEMPERATURE_FREQ)
977  {
978  rc = updateTemperature();
979  if (rc)
980  {
981  if (fabs(lastTemperature - TemperatureNP[0].getValue()) >= 0.1)
982  {
983  TemperatureNP.apply();
984  lastTemperature = TemperatureNP[0].getValue();
985  }
986  }
987 
988  // Also use temparature poll rate for tracking input voltage
989  rc = updateVoltageIn();
990  if (rc)
991  {
992  if (fabs(lastVoltageIn - VoltageInNP[0].getValue()) >= 0.1)
993  {
994  VoltageInNP.apply();
995  lastVoltageIn = VoltageInNP[0].getValue();
996 
997  if (VoltageInNP[0].getValue() < 11.0)
998  {
999  LOG_WARN("Please check 12v DC power supply is connected.");
1000  }
1001  }
1002  }
1003 
1004  m_TemperatureCounter = 0; // Reset the counter
1005  }
1006 
1008 }
1009 
1013 bool SestoSenso2::getStartupValues()
1014 {
1015  bool rc = updatePosition();
1016  if (rc)
1017  {
1018  IDSetNumber(&FocusAbsPosNP, nullptr);
1019  }
1020 
1021  rc &= fetchMotorSettings();
1022 
1023  return (rc);
1024 }
1025 
1030 {
1031  INDI_UNUSED(enable);
1032  return false;
1033 }
1034 
1038 bool SestoSenso2::Ack()
1039 {
1040  std::string response;
1041 
1042  if (isSimulation())
1043  response = "1.0 Simulation";
1044  else
1045  {
1046  if(initCommandSet() == false)
1047  {
1048  LOG_ERROR("Failed setting attributes on serial port and init command sets");
1049  return false;
1050  }
1051  if(m_SestoSenso2->getSerialNumber(response))
1052  {
1053  LOGF_INFO("Serial number: %s", response.c_str());
1054  }
1055  else
1056  {
1057  return false;
1058  }
1059  }
1060 
1061  m_IsSestoSenso2 = !strstr(response.c_str(), "ESATTO");
1062  FirmwareTP[FIRMWARE_SN].setText(response.c_str());
1063 
1064  if (m_SestoSenso2->getFirmwareVersion(response))
1065  {
1066  LOGF_INFO("Firmware version: %s", response.c_str());
1067  IUSaveText(&FirmwareTP[FIRMWARE_VERSION], response.c_str());
1068  }
1069  else
1070  {
1071  return false;
1072  }
1073 
1074  return true;
1075 }
1076 
1080 void SestoSenso2::setConnectionParams()
1081 {
1084 }
1085 
1089 bool SestoSenso2::initCommandSet()
1090 {
1091  m_SestoSenso2.reset(new PrimalucaLabs::SestoSenso2(getDeviceName(), PortFD));
1092 
1093  struct termios tty_setting;
1094  if (tcgetattr(PortFD, &tty_setting) == -1)
1095  {
1096  LOG_ERROR("setTTYFlags: failed getting tty attributes.");
1097  return false;
1098  }
1099  tty_setting.c_lflag |= ICANON;
1100  if (tcsetattr(PortFD, TCSANOW, &tty_setting))
1101  {
1102  LOG_ERROR("setTTYFlags: failed setting attributes on serial port.");
1103  return false;
1104  }
1105  return true;
1106 }
1107 
1112 {
1113  Focuser::saveConfigItems(fp);
1114  MotorRateNP.save(fp);
1115  MotorCurrentNP.save(fp);
1116  return true;
1117 }
1118 
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
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
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.
INumberVectorProperty FocusAbsPosNP
INumberVectorProperty FocusRelPosNP
ISwitchVectorProperty FocusReverseSP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INumberVectorProperty FocusMaxPosNP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
INumber PresetN[3]
Definition: indifocuser.h:107
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indifocuser.cpp:42
INumberVectorProperty PresetNP
Definition: indifocuser.h:108
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Connection::Serial * serialConnection
Definition: indifocuser.h:116
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
IPState getState() const
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.
Definition: inditimer.cpp:109
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 updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: sestosenso2.cpp:62
virtual bool ReverseFocuser(bool enabled) override
ReverseFocuser Reverse focuser motion direction.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Focuser Presets in the configuration file
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
const char * getDefaultName() override
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual bool Handshake() override
perform handshake with device to check communication
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
double max(void)
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_ATMOST1
Definition: indiapi.h:174
Implementations for common driver routines.
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IUUpdateMinMax(const INumberVectorProperty *nvp)
Function to update the min and max elements of a number in the client.
Definition: indidriver.c:1296
#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 ENVIRONMENT_TAB
const char * CONNECTION_TAB
const char * MOTOR_PRESET_NAMES[]
Definition: sestosenso2.cpp:42
struct termios tty_setting
Definition: stvdriver.c:51