Instrument Neutral Distributed Interface INDI  2.0.2
scopedome_dome.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  ScopeDome Dome INDI Driver
3 
4  Copyright(c) 2017-2021 Jarno Paananen. All rights reserved.
5 
6  based on:
7 
8  ScopeDome Windows ASCOM driver version 5.1.30
9 
10  and
11 
12  Baader Planetarium Dome INDI Driver
13 
14  Copyright(c) 2014 Jasem Mutlaq. All rights reserved.
15 
16  Baader Dome INDI Driver
17 
18  This library is free software; you can redistribute it and/or
19  modify it under the terms of the GNU Library General Public
20  License version 2 as published by the Free Software Foundation.
21  .
22  This library is distributed in the hope that it will be useful,
23  but WITHOUT ANY WARRANTY; without even the implied warranty of
24  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25  Library General Public License for more details.
26  .
27  You should have received a copy of the GNU Library General Public License
28  along with this library; see the file COPYING.LIB. If not, write to
29  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30  Boston, MA 02110-1301, USA.
31 *******************************************************************************/
32 
33 #include "scopedome_dome.h"
34 #include "scopedome_arduino.h"
35 #include "scopedome_sim.h"
36 #include "scopedome_usb21.h"
37 
39 #include "indicom.h"
40 
41 #include <cmath>
42 #include <cstdio>
43 #include <cstring>
44 #include <memory>
45 #include <termios.h>
46 #include <unistd.h>
47 #include <wordexp.h>
48 
49 // We declare an auto pointer to ScopeDome.
50 std::unique_ptr<ScopeDome> scopeDome(new ScopeDome());
51 
53 {
54  setVersion(2, 0);
55  targetAz = 0;
60 
62 
63  stepsPerRevolution = ~0;
64 
65  // Load dome inertia table if present
66  wordexp_t wexp;
67  if (wordexp("~/.indi/ScopeDome_DomeInertia_Table.txt", &wexp, 0) == 0)
68  {
69  FILE *inertia = fopen(wexp.we_wordv[0], "r");
70  if (inertia)
71  {
72  // skip UTF-8 marker bytes
73  fseek(inertia, 3, SEEK_SET);
74  char line[100];
75  int lineNum = 0;
76 
77  while (fgets(line, sizeof(line), inertia))
78  {
79  int step, result;
80  // File format is "steps ; actual steps ;" but with varying number of spaces
81  char* splitter = strchr(line, ';');
82  if(splitter)
83  {
84  *splitter++ = 0;
85 
86  step = atoi(line);
87  result = atoi(splitter);
88  if (step == lineNum)
89  {
90  inertiaTable.push_back(result);
91  }
92  }
93  lineNum++;
94  }
95  fclose(inertia);
96  LOGF_INFO("Read inertia file %s", wexp.we_wordv[0]);
97  }
98  else
99  {
100  LOG_WARN("Could not read inertia file, please generate one with Windows "
101  "driver setup and copy to "
102  "~/.indi/ScopeDome_DomeInertia_Table.txt");
103  }
104  }
105  wordfree(&wexp);
106 }
107 
109 {
111 
112  DomeHomePositionNP[0].fill("DH_POSITION", "AZ (deg)", "%6.2f", 0.0, 360.0, 1.0, 0.0);
113  DomeHomePositionNP.fill(getDeviceName(), "DOME_HOME_POSITION",
114  "Home sensor position", SITE_TAB, IP_RW, 60, IPS_OK);
115 
116  HomeSensorPolaritySP[ScopeDomeCard::ACTIVE_HIGH].fill("ACTIVE_HIGH", "Active high", ISS_ON);
117  HomeSensorPolaritySP[ScopeDomeCard::ACTIVE_LOW].fill("ACTIVE_LOW", "Active low", ISS_OFF);
118  HomeSensorPolaritySP.fill(getDeviceName(), "HOME_SENSOR_POLARITY",
119  "Home sensor polarity", SITE_TAB, IP_RW, ISR_1OFMANY, 0, IPS_OK);
120 
121  FindHomeSP[0].fill("START", "Start", ISS_OFF);
122  FindHomeSP.fill(getDeviceName(), "FIND_HOME", "Find home", MAIN_CONTROL_TAB, IP_RW,
123  ISR_ATMOST1, 0, IPS_OK);
124 
125  DerotateSP[0].fill("START", "Start", ISS_OFF);
126  DerotateSP.fill(getDeviceName(), "DEROTATE", "Derotate", MAIN_CONTROL_TAB, IP_RW,
127  ISR_ATMOST1, 0, IPS_OK);
128 
129  FirmwareVersionsNP[0].fill("MAIN", "Main part", "%2.2f", 0.0, 99.0, 1.0, 0.0);
130  FirmwareVersionsNP[1].fill("ROTARY", "Rotary part", "%2.1f", 0.0, 99.0, 1.0, 0.0);
131  FirmwareVersionsNP.fill(getDeviceName(), "FIRMWARE_VERSION",
132  "Firmware versions", INFO_TAB, IP_RO, 60, IPS_IDLE);
133 
134  StepsPerRevolutionNP[0].fill("STEPS", "Steps per revolution", "%5.0f", 0.0, 99999.0, 1.0, 0.0);
135  StepsPerRevolutionNP.fill(getDeviceName(), "CALIBRATION_VALUES",
136  "Calibration values", SITE_TAB, IP_RO, 60, IPS_IDLE);
137 
138  CalibrationNeededSP[0].fill("CALIBRATION_NEEDED", "Calibration needed", ISS_OFF);
139  CalibrationNeededSP.fill(getDeviceName(), "CALIBRATION_STATUS",
140  "Calibration status", SITE_TAB, IP_RO, ISR_ATMOST1, 0, IPS_IDLE);
141 
142  StartCalibrationSP[0].fill("START", "Start", ISS_OFF);
143  StartCalibrationSP.fill(getDeviceName(), "RUN_CALIBRATION", "Run calibration",
145 
146  char defaultUsername[MAXINDINAME] = "scopedome";
147  char defaultPassword[MAXINDINAME] = "default";
148  IUGetConfigText(getDeviceName(), "CREDENTIALS", "USERNAME", defaultUsername, MAXINDINAME);
149  IUGetConfigText(getDeviceName(), "CREDENTIALS", "PASSWORD", defaultPassword, MAXINDINAME);
150  CredentialsTP[0].fill("USERNAME", "User name", defaultUsername);
151  CredentialsTP[1].fill("PASSWORD", "Password", defaultPassword);
152  CredentialsTP.fill(getDeviceName(), "CREDENTIALS", "Credentials", CONNECTION_TAB, IP_RW, 0, IPS_OK);
153 
154  ISState usbcard21 = ISS_ON;
155  ISState arduino = ISS_OFF;
156  IUGetConfigSwitch(getDeviceName(), "CARD_TYPE", "USB_CARD_21", &usbcard21);
157  IUGetConfigSwitch(getDeviceName(), "CARD_TYPE", "ARDUINO", &arduino);
158  CardTypeSP[SCOPEDOME_USB21].fill("USB_CARD_21", "USB Card 2.1", usbcard21);
159  CardTypeSP[SCOPEDOME_ARDUINO].fill("ARDUINO", "Arduino Card", arduino);
160  CardTypeSP.fill(getDeviceName(), "CARD_TYPE", "Card type",
162 
163  switch(CardTypeSP.findOnSwitchIndex())
164  {
165  case SCOPEDOME_USB21:
166  default:
168  break;
169  case SCOPEDOME_ARDUINO:
171  break;
172  }
173 
175  addAuxControls();
176 
179 
180  setPollingPeriodRange(1000, 3000); // Device doesn't like too long interval
182  return true;
183 }
184 
185 /************************************************************************************
186  *
187  * ***********************************************************************************/
189 {
190  targetAz = 0;
191 
192  stepsPerRevolution = interface->getStepsPerRevolution();
193  LOGF_INFO("Steps per revolution read as %d", stepsPerRevolution);
194  StepsPerRevolutionNP[0].setValue(stepsPerRevolution);
197 
198  if (UpdatePosition())
199  IDSetNumber(&DomeAbsPosNP, nullptr);
200 
201  if (UpdateShutterStatus())
202  IDSetSwitch(&DomeShutterSP, nullptr);
203 
206 
207  if (InitPark())
208  {
209  // If loading parking data is successful, we just set the default parking
210  // values.
212  }
213  else
214  {
215  // Otherwise, we set all parking data to default in case no parking data is
216  // found.
217  SetAxis1Park(0);
219  }
220 
221  bool calibrationNeeded = interface->isCalibrationNeeded();
222  CalibrationNeededSP[0].setState(calibrationNeeded ? ISS_ON : ISS_OFF);
225 
226  double fwMain, fwRotary;
227  interface->getFirmwareVersions(fwMain, fwRotary);
228  FirmwareVersionsNP[0].setValue(fwMain);
229  FirmwareVersionsNP[1].setValue(fwRotary);
232 
233  rotaryLinkEstablished = interface->getInputState(ScopeDomeCard::ROTARY_LINK);
234  if(rotaryLinkEstablished == ISS_OFF)
235  {
236  LOG_WARN("Rotary link not established, shutter control not possible");
237  }
238  return true;
239 }
240 
241 /************************************************************************************
242  *
243  ************************************************************************************/
245 {
246  if(reconnecting)
247  return true;
248 
249  sim = isSimulation();
250 
251  if (sim)
252  {
253  interface.reset(static_cast<ScopeDomeCard *>(new ScopeDomeSim()));
254  }
255  else
256  {
257  switch(CardTypeSP.findOnSwitchIndex())
258  {
259  case SCOPEDOME_USB21:
260  default:
261  interface.reset(static_cast<ScopeDomeCard *>(new ScopeDomeUSB21(this, PortFD)));
262  break;
263  case SCOPEDOME_ARDUINO:
264  interface.reset(static_cast<ScopeDomeCard *>(new ScopeDomeArduino(this, getActiveConnection())));
265  break;
266  }
267  }
268  if (interface->detect())
269  {
270  return true;
271  }
272  LOG_ERROR("Can't identify the card, check card type and baud rate (115200 for USB Card 2.1, 9600 for Arduino Card)");
273  return false;
274 }
275 
276 /************************************************************************************
277  *
278  ************************************************************************************/
280 {
281  return (const char *)"ScopeDome Dome";
282 }
283 
284 /************************************************************************************
285  *
286  ************************************************************************************/
288 {
290 
291  if (isConnected())
292  {
293  // Initialize dynamic properties
294  {
295  size_t numSensors = interface->getNumberOfSensors();
296  SensorsNP.resize(numSensors);
297  for(size_t i = 0; i < numSensors; i++)
298  {
299  auto info = interface->getSensorInfo(i);
300  SensorsNP[i].fill(info.propName.c_str(), info.label.c_str(),
301  info.format.c_str(), info.minValue, info.maxValue, 1.0, 0.0);
302  }
303  SensorsNP.fill(getDeviceName(), "SCOPEDOME_SENSORS",
304  "Sensors", INFO_TAB, IP_RO, 60, IPS_IDLE);
305  }
306 
307  {
308  size_t numRelays = interface->getNumberOfRelays();
309  RelaysSP.resize(numRelays);
310  for(size_t i = 0; i < numRelays; i++)
311  {
312  auto info = interface->getRelayInfo(i);
313  RelaysSP[i].fill(info.propName.c_str(), info.label.c_str(), ISS_OFF);
314  }
315  RelaysSP.fill(getDeviceName(), "RELAYS", "Relays", MAIN_CONTROL_TAB, IP_RW,
316  ISR_NOFMANY,
317  0, IPS_IDLE);
318  }
319 
320  {
321  size_t numInputs = interface->getNumberOfInputs();
322  InputsSP.resize(numInputs);
323  for(size_t i = 0; i < numInputs; i++)
324  {
325  auto info = interface->getInputInfo(i);
326  InputsSP[i].fill(info.propName.c_str(), info.label.c_str(), ISS_OFF);
327  }
328  InputsSP.fill(getDeviceName(), "INPUTS", "Inputs", INFO_TAB, IP_RO,
329  ISR_NOFMANY, 0, IPS_IDLE);
330  }
331 
343  SetupParms();
344  }
345  else
346  {
358  }
359 
360  return true;
361 }
362 
363 /************************************************************************************
364  *
365  * ***********************************************************************************/
366 bool ScopeDome::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
367 {
368  // ignore if not ours
369  if (dev == nullptr || strcmp(dev, getDeviceName()))
370  return false;
371 
372  if (FindHomeSP.isNameMatch(name))
373  {
374  if (status != DOME_HOMING)
375  {
376  LOG_INFO("Finding home sensor");
378  FindHomeSP.reset();
381  FindHomeSP.apply();
382  interface->findHome();
383  }
384  return true;
385  }
386 
387  if (DerotateSP.isNameMatch(name))
388  {
389  if (status != DOME_DEROTATING)
390  {
391  LOG_INFO("De-rotating started");
393  DerotateSP.reset();
396  DerotateSP.apply();
397  }
398  return true;
399  }
400 
402  {
403  if (status != DOME_CALIBRATING)
404  {
405  LOG_INFO("Calibration started");
411  interface->calibrate();
412  }
413  return true;
414  }
415 
416  if (RelaysSP.isNameMatch(name))
417  {
418  RelaysSP.update(states, names, n);
419  for(int i = 0; i < n; i++)
420  {
421  interface->setRelayState(i, RelaysSP[i].getState());
422  }
423  RelaysSP.apply();
424  return true;
425  }
426 
427  if (CardTypeSP.isNameMatch(name))
428  {
429  CardTypeSP.update(states, names, n);
430  CardTypeSP.apply();
431 
432  if(CardTypeSP[0].getState() == ISS_ON)
433  {
434  // USB Card 2.1 uses 115200 bauds
437  }
438  else
439  {
440  // Arduino Card uses 9600 bauds
443  }
444  return true;
445  }
447  {
448  HomeSensorPolaritySP.update(states, names, n);
450 
452  {
453  interface->setHomeSensorPolarity(ScopeDomeCard::ACTIVE_LOW);
454  LOG_DEBUG("Home sensor polarity set to ACTIVE_LOW");
455  }
456  else
457  {
458  interface->setHomeSensorPolarity(ScopeDomeCard::ACTIVE_HIGH);
459  LOG_DEBUG("Home sensor polarity set to ACTIVE_HIGH");
460  }
461  return true;
462  }
463 
464  // Pass to base class
465  return INDI::Dome::ISNewSwitch(dev, name, states, names, n);
466 }
467 
468 bool ScopeDome::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
469 {
470  // ignore if not ours
471  if (dev == nullptr || strcmp(dev, getDeviceName()))
472  return false;
473 
474 
476  {
477  DomeHomePositionNP.update(values, names, n);
480  LOGF_DEBUG("Dome home position set to %f", DomeHomePositionNP[0].getValue());
481  return true;
482  }
483  return INDI::Dome::ISNewNumber(dev, name, values, names, n);
484 }
485 
486 bool ScopeDome::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
487 {
488  // ignore if not ours
489  if (dev == nullptr || strcmp(dev, getDeviceName()))
490  return false;
491 
492  if (CredentialsTP.isNameMatch(name))
493  {
494  CredentialsTP.update(texts, names, n);
496  return true;
497  }
498  return INDI::Dome::ISNewText(dev, name, texts, names, n);
499 }
500 
501 /************************************************************************************
502  *
503  * ***********************************************************************************/
505 {
506  int rc = 0;
507  if (rc != 0)
508  {
509  LOGF_ERROR("Error reading input state: %d", rc);
510  return false;
511  }
512 
513  for(size_t i = 0; i < InputsSP.size(); i++)
514  {
515  InputsSP[i].setState(interface->getInputValue(i) ? ISS_ON : ISS_OFF);
516  }
518  InputsSP.apply();
519 
520  if (interface->getInputState(ScopeDomeCard::OPEN1) == ISS_ON) // shutter open switch triggered
521  {
523  {
525  interface->controlShutter(ScopeDomeCard::STOP_SHUTTER);
526  if (getDomeState() == DOME_UNPARKING)
527  {
528  SetParked(false);
529  }
530  }
532  }
533  else if (interface->getInputState(ScopeDomeCard::CLOSED1) == ISS_ON) // shutter closed switch triggered
534  {
536  {
538  interface->controlShutter(ScopeDomeCard::STOP_SHUTTER);
539 
541  {
542  SetParked(true);
543  }
544  }
546  }
547  else
548  {
550  }
551 
552  ISState link = interface->getInputState(ScopeDomeCard::ROTARY_LINK);
553  if (link != rotaryLinkEstablished)
554  {
555  rotaryLinkEstablished = link;
556  if(rotaryLinkEstablished == ISS_OFF)
557  {
558  LOG_WARN("Rotary link not established, shutter control not possible");
559  }
560  else
561  {
562  LOG_WARN("Rotary link established, shutter control now possible");
563  }
564  }
565  return true;
566 }
567 
568 /************************************************************************************
569  *
570  * ***********************************************************************************/
572 {
573  rotationCounter = interface->getRotationCounter();
574 
575  // We assume counter value 0 is at home sensor position and grows counter clockwise as this is
576  // how USB Card 2.1 works.
577  double az = ((double)rotationCounter * -360.0 / stepsPerRevolution) + DomeHomePositionNP[0].getValue();
578  az = fmod(az, 360.0);
579  if (az < 0.0)
580  {
581  az += 360.0;
582  }
583  DomeAbsPosN[0].value = az;
584  LOGF_DEBUG("Dome position %f, step count %d", az, rotationCounter);
585  return true;
586 }
587 
588 /************************************************************************************
589  *
590  * ***********************************************************************************/
592 {
593  for (size_t i = 0; i < SensorsNP.size(); ++i)
594  {
595  SensorsNP[i].setValue(interface->getSensorValue(i));
596  }
597  // SensorsN[10].value = getDewPoint(SensorsN[8].value, SensorsN[7].value);
599  SensorsNP.apply();
600  return true;
601 }
602 
603 /************************************************************************************
604  *
605  * ***********************************************************************************/
607 {
608  for(size_t i = 0; i < RelaysSP.size(); i++)
609  {
610  RelaysSP[i].setState(interface->getRelayState(i));
611  }
613  RelaysSP.apply();
614  return true;
615 }
616 
617 /************************************************************************************
618  *
619  * ***********************************************************************************/
621 {
622  if (!isConnected())
623  return; // No need to reset timer if we are not connected anymore
624 
625  interface->updateState();
626 
627  currentStatus = interface->getStatus();
628 
629  UpdatePosition();
631  IDSetSwitch(&DomeShutterSP, nullptr);
632 
634 
635  if (status == DOME_HOMING)
636  {
637  if ((currentStatus & (ScopeDomeCard::STATUS_HOMING | ScopeDomeCard::STATUS_MOVING)) == 0)
638  {
639  double azDiff = DomeHomePositionNP[0].getValue() - DomeAbsPosN[0].value;
640 
641  if (azDiff > 180)
642  {
643  azDiff -= 360;
644  }
645  if (azDiff < -180)
646  {
647  azDiff += 360;
648  }
649 
650  if (interface->getInputState(ScopeDomeCard::HOME) || fabs(azDiff) <= DomeParamN[0].value)
651  {
652  // Found home (or close enough)
653  LOG_INFO("Home sensor found");
654  status = DOME_READY;
655  targetAz = DomeHomePositionNP[0].getValue();
656 
657  // Reset counters
658  interface->resetCounter();
659 
661  FindHomeSP.apply();
663  }
664  else
665  {
666  // We overshoot, go closer
667  MoveAbs(DomeHomePositionNP[0].getValue());
668  }
669  }
670  IDSetNumber(&DomeAbsPosNP, nullptr);
671  }
672  else if (status == DOME_DEROTATING)
673  {
674  if ((currentStatus & ScopeDomeCard::STATUS_MOVING) == 0)
675  {
676  currentRotation = interface->getRotationCounterExt();
677  LOGF_INFO("Current rotation is %d", currentRotation);
678  if (abs(currentRotation) < 100)
679  {
680  // Close enough
681  LOG_INFO("De-rotation complete");
682  status = DOME_READY;
684  DerotateSP.apply();
686  }
687  else
688  {
689  interface->move(currentRotation);
690  }
691  }
692  }
693  else if (status == DOME_CALIBRATING)
694  {
696  {
697  stepsPerRevolution = interface->getStepsPerRevolution();
698  LOGF_INFO("Calibration complete, steps per revolution read as %d", stepsPerRevolution);
699  StepsPerRevolutionNP[0].setValue(stepsPerRevolution);
704  status = DOME_READY;
706  }
707  }
708  else if (DomeAbsPosNP.s == IPS_BUSY)
709  {
710  if ((currentStatus & ScopeDomeCard::STATUS_MOVING) == 0)
711  {
712  // Rotation idle, are we close enough?
713  double azDiff = targetAz - DomeAbsPosN[0].value;
714 
715  if (azDiff > 180)
716  {
717  azDiff -= 360;
718  }
719  if (azDiff < -180)
720  {
721  azDiff += 360;
722  }
723  if (!refineMove || fabs(azDiff) <= DomeParamN[0].value)
724  {
725  if (refineMove)
726  {
727  DomeAbsPosN[0].value = targetAz;
728  }
730  LOG_INFO("Dome reached requested azimuth angle.");
731 
732  if (getDomeState() == DOME_PARKING)
733  {
735  interface->getInputState(ScopeDomeCard::CLOSED1) == ISS_OFF)
736  {
738  }
739  else
740  {
741  SetParked(true);
742  }
743  }
744  else if (getDomeState() == DOME_UNPARKING)
745  {
746  SetParked(false);
747  }
748  else
749  {
751  }
752  }
753  else
754  {
755  // Refine azimuth
756  MoveAbs(targetAz);
757  }
758  }
759  IDSetNumber(&DomeAbsPosNP, nullptr);
760  }
761  else
762  {
763  IDSetNumber(&DomeAbsPosNP, nullptr);
764  }
765 
766  // Read temperatures only every 10th time
767  static int tmpCounter = 0;
768  if (--tmpCounter <= 0)
769  {
771  tmpCounter = 10;
772  }
773 
775 }
776 
777 /************************************************************************************
778  *
779  * ***********************************************************************************/
781 {
782  LOGF_DEBUG("MoveAbs (%f)", az);
783  targetAz = az;
784  double azDiff = az - DomeAbsPosN[0].value;
785  LOGF_DEBUG("azDiff = %f", azDiff);
786 
787  // Make relative (-180 - 180) regardless if it passes az 0
788  if (azDiff > 180)
789  {
790  azDiff -= 360;
791  }
792  if (azDiff < -180)
793  {
794  azDiff += 360;
795  }
796 
797  LOGF_DEBUG("azDiff rel = %f", azDiff);
798 
799  refineMove = true;
800  return sendMove(azDiff);
801 }
802 
803 /************************************************************************************
804  *
805  * ***********************************************************************************/
807 {
808  refineMove = false;
809  return sendMove(azDiff);
810 }
811 
812 /************************************************************************************
813  *
814  * ***********************************************************************************/
815 IPState ScopeDome::sendMove(double azDiff)
816 {
817  if (azDiff < 0)
818  {
819  int steps = static_cast<int>(-azDiff * stepsPerRevolution / 360.0);
820  LOGF_DEBUG("CCW (%d)", steps);
821  if (steps <= 0)
822  {
823  return IPS_OK;
824  }
825  steps = compensateInertia(steps);
826  LOGF_DEBUG("CCW inertia (%d)", steps);
827  if (steps <= 0)
828  {
829  return IPS_OK;
830  }
831  interface->move(-steps);
832  }
833  else
834  {
835  int steps = static_cast<int>(azDiff * stepsPerRevolution / 360.0);
836  LOGF_DEBUG("CW (%d)", steps);
837  if (steps <= 0)
838  {
839  return IPS_OK;
840  }
841  steps = compensateInertia(steps);
842  LOGF_DEBUG("CW inertia (%d)", steps);
843  if (steps <= 0)
844  {
845  return IPS_OK;
846  }
847  interface->move(steps);
848  }
849  return IPS_BUSY;
850 }
851 
852 /************************************************************************************
853  *
854  * ***********************************************************************************/
856 {
857  // Map to button outputs
858  if (operation == MOTION_START)
859  {
860  refineMove = false;
861  if (dir == DOME_CW)
862  {
863  interface->setOutputState(ScopeDomeCard::CCW, ISS_OFF);
864  interface->setOutputState(ScopeDomeCard::CW, ISS_ON);
865  }
866  else
867  {
868  interface->setOutputState(ScopeDomeCard::CW, ISS_OFF);
869  interface->setOutputState(ScopeDomeCard::CCW, ISS_ON);
870  }
871  return IPS_BUSY;
872  }
873  interface->setOutputState(ScopeDomeCard::CW, ISS_OFF);
874  interface->setOutputState(ScopeDomeCard::CCW, ISS_OFF);
875  return IPS_OK;
876 }
877 
878 /************************************************************************************
879  *
880  * ***********************************************************************************/
882 {
883  // First move to park position and then optionally close shutter
885  IPState s = MoveAbs(targetAz);
887  {
888  // Already at park position, just close if needed
890  }
891  return s;
892 }
893 
894 /************************************************************************************
895  *
896  * ***********************************************************************************/
898 {
900  {
902  }
903  return IPS_OK;
904 }
905 
906 /************************************************************************************
907  *
908  * ***********************************************************************************/
910 {
911  LOGF_INFO("Control shutter %d", (int)operation);
912  targetShutter = operation;
913  if (operation == SHUTTER_OPEN)
914  {
915  LOG_INFO("Opening shutter");
916  if (interface->getInputState(ScopeDomeCard::OPEN1))
917  {
918  LOG_INFO("Shutter already open");
919  return IPS_OK;
920  }
921  interface->controlShutter(ScopeDomeCard::OPEN_SHUTTER);
922  }
923  else
924  {
925  LOG_INFO("Closing shutter");
926  if (interface->getInputState(ScopeDomeCard::CLOSED1))
927  {
928  LOG_INFO("Shutter already closed");
929  return IPS_OK;
930  }
931  interface->controlShutter(ScopeDomeCard::CLOSE_SHUTTER);
932  }
933 
935  return IPS_BUSY;
936 }
937 
938 /************************************************************************************
939  *
940  * ***********************************************************************************/
942 {
943  interface->abort();
944  status = DOME_READY;
945  return true;
946 }
947 
948 /************************************************************************************
949  *
950  * ***********************************************************************************/
952 {
954 
957  CardTypeSP.save(fp);
958  CredentialsTP.save(fp);
959  return true;
960 }
961 
962 /************************************************************************************
963  *
964  * ***********************************************************************************/
966 {
967  SetAxis1Park(DomeAbsPosN[0].value);
968  return true;
969 }
970 /************************************************************************************
971  *
972  * ***********************************************************************************/
973 
975 {
976  // By default set position to 90
977  SetAxis1Park(90);
978  return true;
979 }
980 
981 /************************************************************************************
982  *
983  * ***********************************************************************************/
984 void ScopeDome::reconnect()
985 {
986  // Reconnect serial port after write error
987  LOG_INFO("Reconnecting serial port");
988  reconnecting = true;
990  usleep(1000000); // 1s
993  interface->setPortFD(PortFD);
994  LOG_INFO("Reconnected");
995  reconnecting = false;
996 }
997 
998 /*
999  * Saturation Vapor Pressure formula for range -100..0 Deg. C.
1000  * This is taken from
1001  * ITS-90 Formulations for Vapor Pressure, Frostpoint Temperature,
1002  * Dewpoint Temperature, and Enhancement Factors in the Range 100 to +100 C
1003  * by Bob Hardy
1004  * as published in "The Proceedings of the Third International Symposium on
1005  * Humidity & Moisture",
1006  * Teddington, London, England, April 1998
1007  */
1008 static const float k0 = -5.8666426e3;
1009 static const float k1 = 2.232870244e1;
1010 static const float k2 = 1.39387003e-2;
1011 static const float k3 = -3.4262402e-5;
1012 static const float k4 = 2.7040955e-8;
1013 static const float k5 = 6.7063522e-1;
1014 
1015 static float pvsIce(float T)
1016 {
1017  float lnP = k0 / T + k1 + (k2 + (k3 + (k4 * T)) * T) * T + k5 * log(T);
1018  return exp(lnP);
1019 }
1020 
1033 static const float n1 = 0.11670521452767e4;
1034 static const float n6 = 0.14915108613530e2;
1035 static const float n2 = -0.72421316703206e6;
1036 static const float n7 = -0.48232657361591e4;
1037 static const float n3 = -0.17073846940092e2;
1038 static const float n8 = 0.40511340542057e6;
1039 static const float n4 = 0.12020824702470e5;
1040 static const float n9 = -0.23855557567849;
1041 static const float n5 = -0.32325550322333e7;
1042 static const float n10 = 0.65017534844798e3;
1043 
1044 static float pvsWater(float T)
1045 {
1046  float th = T + n9 / (T - n10);
1047  float A = (th + n1) * th + n2;
1048  ;
1049  float B = (n3 * th + n4) * th + n5;
1050  float C = (n6 * th + n7) * th + n8;
1051  ;
1052 
1053  float p = 2.0f * C / (-B + sqrt(B * B - 4.0f * A * C));
1054  p *= p;
1055  p *= p;
1056  return p * 1e6;
1057 }
1058 
1059 static const float C_OFFSET = 273.15f;
1060 static const float minT = 173; // -100 Deg. C.
1061 static const float maxT = 678;
1062 
1063 static float PVS(float T)
1064 {
1065  if (T < minT || T > maxT)
1066  return 0;
1067  else if (T < C_OFFSET)
1068  return pvsIce(T);
1069  else
1070  return pvsWater(T);
1071 }
1072 
1073 static float solve(float (*f)(float), float y, float x0)
1074 {
1075  float x = x0;
1076  int maxCount = 10;
1077  int count = 0;
1078  while (++count < maxCount)
1079  {
1080  float xNew;
1081  float dx = x / 1000;
1082  float z = f(x);
1083  xNew = x + dx * (y - z) / (f(x + dx) - z);
1084  if (fabs((xNew - x) / xNew) < 0.0001f)
1085  return xNew;
1086  x = xNew;
1087  }
1088  return 0;
1089 }
1090 
1091 float ScopeDome::getDewPoint(float RH, float T)
1092 {
1093  T = T + C_OFFSET;
1094  return solve(PVS, RH / 100 * PVS(T), T) - C_OFFSET;
1095 }
1096 
1097 int ScopeDome::compensateInertia(int steps)
1098 {
1099  if (inertiaTable.size() == 0)
1100  {
1101  LOGF_DEBUG("inertia passthrough %d", steps);
1102  return steps; // pass value as such if we don't have enough data
1103  }
1104 
1105  for (uint16_t out = 0; out < inertiaTable.size(); out++)
1106  {
1107  if (inertiaTable[out] > steps)
1108  {
1109  LOGF_DEBUG("inertia %d -> %d", steps, out - 1);
1110  return out - 1;
1111  }
1112  }
1113  // Check difference from largest table entry and assume we have
1114  // similar inertia also after that
1115  int lastEntry = inertiaTable.size() - 1;
1116  int inertia = inertiaTable[lastEntry] - lastEntry;
1117  int movement = steps - inertia;
1118  LOGF_DEBUG("inertia %d -> %d", steps, movement);
1119  if (movement <= 0)
1120  return 0;
1121 
1122  return movement;
1123 }
std::thread th
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
virtual bool Disconnect() override
Disconnect Disconnect from device.
virtual bool Connect() override
Connect Connect to device via the implemented communication medium. Do not perform any handshakes.
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
void setPollingPeriodRange(uint32_t minimum, uint32_t maximum)
setPollingPeriodRange Set the range permitted by the polling range in milliseconds
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.
Connection::Interface * getActiveConnection()
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
@ DOME_CAN_PARK
Definition: indidome.h:159
@ DOME_CAN_ABS_MOVE
Definition: indidome.h:157
@ DOME_HAS_SHUTTER
Definition: indidome.h:161
@ DOME_CAN_REL_MOVE
Definition: indidome.h:158
@ DOME_CAN_ABORT
Definition: indidome.h:156
@ CONNECTION_SERIAL
Definition: indidome.h:172
@ CONNECTION_TCP
Definition: indidome.h:173
void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
Definition: indidome.cpp:1633
void SetDomeCapability(uint32_t cap)
SetDomeCapability set the dome capabilities. All capabilities must be initialized.
Definition: indidome.cpp:1561
void setDomeConnection(const uint8_t &value)
setDomeConnection Set Dome connection mode. Child class should call this in the constructor before Do...
Definition: indidome.cpp:2297
const char * GetShutterStatusString(ShutterState status)
getShutterStatusString
Definition: indidome.cpp:1569
@ SHUTTER_OPEN_ON_UNPARK
Definition: indidome.h:579
@ SHUTTER_CLOSE_ON_PARK
Definition: indidome.h:578
@ DOME_UNPARKING
Definition: indidome.h:135
@ DOME_PARKING
Definition: indidome.h:134
@ DOME_SYNCED
Definition: indidome.h:133
ShutterState m_ShutterState
Definition: indidome.h:624
void SetAxis1Park(double value)
SetRAPark Set current AZ parking position. The data park file (stored in ~/.indi/ParkData....
Definition: indidome.cpp:1861
INumber DomeAbsPosN[1]
Definition: indidome.h:534
ISwitch ShutterParkPolicyS[2]
Definition: indidome.h:575
void SetAxis1ParkDefault(double steps)
SetAxis1Park Set default AZ parking position.
Definition: indidome.cpp:1868
ShutterOperation
Shutter operation command.
Definition: indidome.h:114
@ SHUTTER_CLOSE
Definition: indidome.h:116
@ SHUTTER_OPEN
Definition: indidome.h:115
int PortFD
Definition: indidome.h:617
DomeMotionCommand
Definition: indidome.h:97
@ MOTION_START
Definition: indidome.h:98
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indidome.cpp:93
INumberVectorProperty DomeAbsPosNP
Definition: indidome.h:533
double GetAxis1Park()
Definition: indidome.cpp:1851
ISwitchVectorProperty DomeShutterSP
Definition: indidome.h:548
@ SHUTTER_MOVING
Definition: indidome.h:149
@ SHUTTER_UNKNOWN
Definition: indidome.h:150
@ SHUTTER_OPENED
Definition: indidome.h:147
@ SHUTTER_CLOSED
Definition: indidome.h:148
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: indidome.cpp:279
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: indidome.cpp:492
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Dome Presets in the configuration file
Definition: indidome.cpp:1043
INumber DomeParamN[1]
Definition: indidome.h:543
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition: indidome.cpp:795
void setShutterState(const ShutterState &value)
Definition: indidome.cpp:1119
void setDomeState(const DomeState &value)
Definition: indidome.cpp:1156
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
Definition: indidome.cpp:1644
Connection::Serial * serialConnection
Definition: indidome.h:619
void SetParkDataType(DomeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
Definition: indidome.cpp:1587
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: indidome.cpp:390
DomeState getDomeState() const
Definition: indidome.h:285
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
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)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], 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)
friend class ScopeDomeUSB21
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
INDI::PropertySwitch DerotateSP
virtual const char * getDefaultName() override
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
INDI::PropertyText CredentialsTP
ShutterState simShutterStatus
friend class ScopeDomeArduino
INDI::PropertySwitch FindHomeSP
ShutterOperation targetShutter
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
double targetAz
INDI::PropertyNumber DomeHomePositionNP
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
DomeStatus status
bool UpdateSensorStatus()
INDI::PropertyNumber FirmwareVersionsNP
virtual IPState MoveRel(double azDiff) override
Move the Dome to an relative position.
virtual IPState ControlShutter(ShutterOperation operation) override
Open or Close shutter.
virtual IPState UnPark() override
UnPark dome. The action of the Unpark command is dome specific, but it may include opening the shutte...
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool Abort() override
Abort all dome motion.
INDI::PropertySwitch CardTypeSP
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
bool UpdateShutterStatus()
virtual bool Handshake() override
perform handshake with device to check communication
INDI::PropertySwitch StartCalibrationSP
INDI::PropertySwitch HomeSensorPolaritySP
INDI::PropertySwitch InputsSP
INDI::PropertySwitch RelaysSP
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Dome Presets in the configuration file
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
INDI::PropertyNumber SensorsNP
virtual IPState MoveAbs(double az) override
Move the Dome to an absolute azimuth.
INDI::PropertyNumber StepsPerRevolutionNP
INDI::PropertySwitch CalibrationNeededSP
bool UpdateRelayStatus()
bool UpdatePosition()
bool SetupParms()
virtual IPState Move(DomeDirection dir, DomeMotionCommand operation) override
Move the Dome in a particular direction.
virtual IPState Park() override
Goto Park Position. The park position is an absolute azimuth value.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
const char * SITE_TAB
SITE_TAB Where all site information setting are located.
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_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_NOFMANY
Definition: indiapi.h:175
@ ISR_ATMOST1
Definition: indiapi.h:174
#define MAXINDINAME
Definition: indiapi.h:191
Implementations for common driver routines.
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
Definition: indidriver.c:889
int IUGetConfigSwitch(const char *dev, const char *property, const char *member, ISState *value)
IUGetConfigSwitch Opens configuration file and reads single switch property.
Definition: indidriver.c:653
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define LOG_WARN(txt)
Definition: indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
const char * CONNECTION_TAB
std::unique_ptr< ScopeDome > scopeDome(new ScopeDome())