Instrument Neutral Distributed Interface INDI  1.9.5
lx200_OnStep.cpp
Go to the documentation of this file.
1 /*
2  LX200 LX200_OnStep
3  Based on LX200 classic, (alain@zwingelstein.org)
4  Contributors:
5  James Lan https://github.com/james-lan
6  Ray Wells https://github.com/blueshawk
7  Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com)
8 
9  This library is free software; you can redistribute it and/or
10  modify it under the terms of the GNU Lesser General Public
11  License as published by the Free Software Foundation; either
12  version 2.1 of the License, or (at your option) any later version.
13 
14  This library is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  Lesser General Public License for more details.
18 
19  You should have received a copy of the GNU Lesser General Public
20  License along with this library; if not, write to the Free Software
21  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 
23 */
24 
25 //#define DEBUG_TRACKSTATE
26 
27 
28 #include "lx200_OnStep.h"
29 
30 
31 #include <mutex>
32 
33 #define LIBRARY_TAB "Library"
34 #define FIRMWARE_TAB "Firmware data"
35 #define STATUS_TAB "ONStep Status"
36 #define PEC_TAB "PEC"
37 #define ALIGN_TAB "Align"
38 #define OUTPUT_TAB "Outputs"
39 #define ENVIRONMENT_TAB "Weather"
40 #define ROTATOR_TAB "Rotator"
41 
42 #define RA_AXIS 0
43 #define DEC_AXIS 1
44 
45 extern std::mutex lx200CommsLock;
46 
47 
49 {
50  currentCatalog = LX200_STAR_C;
51  currentSubCatalog = 0;
52 
53  setVersion(1, 13); // don't forget to update libindi/drivers.xml
54 
57 
60 
61  //CAN_ABORT, CAN_GOTO ,CAN_PARK ,CAN_SYNC ,HAS_LOCATION ,HAS_TIME ,HAS_TRACK_MODE Already inherited from lx200generic,
62  // 4 stands for the number of Slewrate Buttons as defined in Inditelescope.cpp
63  //setLX200Capability(LX200_HAS_FOCUS | LX200_HAS_TRACKING_FREQ | LX200_HAS_ALIGNMENT_TYPE | LX200_HAS_SITES | LX200_HAS_PULSE_GUIDING);
64  //
65  // Get generic capabilities but discard the followng:
66  // LX200_HAS_FOCUS
67 
68 
70  // Unused option: FOCUSER_HAS_VARIABLE_SPEED
71 
73  // /*{
74  // ROTATOR_CAN_ABORT = 1 << 0, /*!< Can the Rotator abort motion once started? */
75  // ROTATOR_CAN_HOME = 1 << 1, /*!< Can the Rotator go to home position? */
76  // ROTATOR_CAN_SYNC = 1 << 2, /*!< Can the Rotator sync to specific tick? */ /*Not supported */
77  // ROTATOR_CAN_REVERSE = 1 << 3, /*!< Can the Rotator reverse direction? */ //It CAN reverse, but there's no way to query the direction
78  // ROTATOR_HAS_BACKLASH = 1 << 4 /*!< Can the Rotatorer compensate for backlash? */
79  // //}*/
80 
81 }
82 
84 {
85  return "LX200 OnStep";
86 }
87 
89 {
90 
96 
97  //FocuserInterface
98  //Initial, these will be updated later.
99  FocusRelPosN[0].min = 0.;
100  FocusRelPosN[0].max = 30000.;
101  FocusRelPosN[0].value = 0;
102  FocusRelPosN[0].step = 10;
103  FocusAbsPosN[0].min = 0.;
104  FocusAbsPosN[0].max = 60000.;
105  FocusAbsPosN[0].value = 0;
106  FocusAbsPosN[0].step = 10;
107 
108 
109  // ============== MAIN_CONTROL_TAB
110  IUFillSwitch(&ReticS[0], "PLUS", "Light", ISS_OFF);
111  IUFillSwitch(&ReticS[1], "MOINS", "Dark", ISS_OFF);
112  IUFillSwitchVector(&ReticSP, ReticS, 2, getDeviceName(), "RETICULE_BRIGHTNESS", "Reticule +/-", MAIN_CONTROL_TAB, IP_RW,
113  ISR_ATMOST1, 60, IPS_IDLE);
114 
115  IUFillNumber(&ElevationLimitN[0], "minAlt", "Elev Min", "%+03f", -90.0, 90.0, 1.0, -30.0);
116  IUFillNumber(&ElevationLimitN[1], "maxAlt", "Elev Max", "%+03f", -90.0, 90.0, 1.0, 89.0);
118  IP_RW, 0, IPS_IDLE);
119 
120  IUFillText(&ObjectInfoT[0], "Info", "", "");
122 
123  // ============== CONNECTION_TAB
124 
125  // ============== OPTION_TAB
126 
127  // ============== MOTION_CONTROL_TAB
128  //Override the standard slew rate command. Also add appropriate description. This also makes it work in Ekos Mount Control correctly
129  //Note that SlewRateSP and MaxSlewRateNP BOTH track the rate. I have left them in there because MaxRateNP reports OnStep Values
130  uint8_t nSlewRate = 10;
131  free(SlewRateS);
132  SlewRateS = (ISwitch *)malloc(sizeof(ISwitch) * nSlewRate);
133  // 0=.25X 1=.5x 2=1x 3=2x 4=4x 5=8x 6=24x 7=48x 8=half-MaxRate 9=MaxRate
134  IUFillSwitch(&SlewRateS[0], "0", "0.25x", ISS_OFF);
135  IUFillSwitch(&SlewRateS[1], "1", "0.5x", ISS_OFF);
136  IUFillSwitch(&SlewRateS[2], "2", "1x", ISS_OFF);
137  IUFillSwitch(&SlewRateS[3], "3", "2x", ISS_OFF);
138  IUFillSwitch(&SlewRateS[4], "4", "4x", ISS_OFF);
139  IUFillSwitch(&SlewRateS[5], "5", "8x", ISS_ON);
140  IUFillSwitch(&SlewRateS[6], "6", "24x", ISS_OFF);
141  IUFillSwitch(&SlewRateS[7], "7", "48x", ISS_OFF);
142  IUFillSwitch(&SlewRateS[8], "8", "Half-Max", ISS_OFF);
143  IUFillSwitch(&SlewRateS[9], "9", "Max", ISS_OFF);
144  IUFillSwitchVector(&SlewRateSP, SlewRateS, nSlewRate, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate", MOTION_TAB,
145  IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
146 
147  IUFillNumber(&MaxSlewRateN[0], "maxSlew", "Rate", "%f", 0.0, 9.0, 1.0, 5.0); //2.0, 9.0, 1.0, 9.0
149 
150  IUFillSwitch(&TrackCompS[0], "1", "Full Compensation", ISS_OFF);
151  IUFillSwitch(&TrackCompS[1], "2", "Refraction", ISS_OFF);
152  IUFillSwitch(&TrackCompS[2], "3", "Off", ISS_ON);
153  IUFillSwitchVector(&TrackCompSP, TrackCompS, 3, getDeviceName(), "Compensation", "Compensation Tracking", MOTION_TAB, IP_RW,
154  ISR_1OFMANY, 0, IPS_IDLE);
155 
156  IUFillSwitch(&TrackAxisS[0], "1", "Single Axis", ISS_OFF);
157  IUFillSwitch(&TrackAxisS[1], "2", "Dual Axis", ISS_OFF);
158  IUFillSwitchVector(&TrackAxisSP, TrackAxisS, 2, getDeviceName(), "Multi-Axis", "Multi-Axis Tracking", MOTION_TAB, IP_RW,
159  ISR_1OFMANY, 0, IPS_IDLE);
160 
161  IUFillSwitch(&TrackAxisS[0], "1", "Single Axis", ISS_OFF);
162  IUFillSwitch(&TrackAxisS[1], "2", "Dual Axis", ISS_OFF);
163  IUFillSwitchVector(&TrackAxisSP, TrackAxisS, 2, getDeviceName(), "Multi-Axis", "Multi-Axis Tracking", MOTION_TAB, IP_RW,
164  ISR_1OFMANY, 0, IPS_IDLE);
165 
166  IUFillNumber(&BacklashN[0], "Backlash DEC", "DE", "%g", 0, 3600, 1, 15);
167  IUFillNumber(&BacklashN[1], "Backlash RA", "RA", "%g", 0, 3600, 1, 15);
169 
170  IUFillNumber(&GuideRateN[RA_AXIS], "GUIDE_RATE_WE", "W/E Rate", "%g", 0, 1, 0.25, 0.5);
171  IUFillNumber(&GuideRateN[DEC_AXIS], "GUIDE_RATE_NS", "N/S Rate", "%g", 0, 1, 0.25, 0.5);
172  IUFillNumberVector(&GuideRateNP, GuideRateN, 2, getDeviceName(), "GUIDE_RATE", "Guiding Rate", MOTION_TAB, IP_RO, 0,
173  IPS_IDLE);
174 
175  IUFillSwitch(&AutoFlipS[0], "1", "AutoFlip: OFF", ISS_OFF);
176  IUFillSwitch(&AutoFlipS[1], "2", "AutoFlip: ON", ISS_OFF);
177  IUFillSwitchVector(&AutoFlipSP, AutoFlipS, 2, getDeviceName(), "AutoFlip", "Meridian Auto Flip", MOTION_TAB, IP_RW,
178  ISR_1OFMANY, 0, IPS_IDLE);
179 
180  IUFillSwitch(&HomePauseS[0], "1", "HomePause: OFF", ISS_OFF);
181  IUFillSwitch(&HomePauseS[1], "2", "HomePause: ON", ISS_OFF);
182  IUFillSwitch(&HomePauseS[2], "3", "HomePause: Continue", ISS_OFF);
183  IUFillSwitchVector(&HomePauseSP, HomePauseS, 3, getDeviceName(), "HomePause", "Pause at Home", MOTION_TAB, IP_RW,
184  ISR_1OFMANY, 0, IPS_IDLE);
185 
186  IUFillSwitch(&FrequencyAdjustS[0], "1", "Frequency -", ISS_OFF);
187  IUFillSwitch(&FrequencyAdjustS[1], "2", "Frequency +", ISS_OFF);
188  IUFillSwitch(&FrequencyAdjustS[2], "3", "Reset Sidereal Frequency", ISS_OFF);
189  IUFillSwitchVector(&FrequencyAdjustSP, FrequencyAdjustS, 3, getDeviceName(), "FrequencyAdjust", "Frequency Adjust",
191 
192  IUFillSwitch(&PreferredPierSideS[0], "1", "West", ISS_OFF);
193  IUFillSwitch(&PreferredPierSideS[1], "2", "East", ISS_OFF);
194  IUFillSwitch(&PreferredPierSideS[2], "3", "Best", ISS_OFF);
196  "Preferred Pier Side", MOTION_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
197 
198  IUFillNumber(&minutesPastMeridianN[0], "East", "East", "%g", 0, 180, 1, 30);
199  IUFillNumber(&minutesPastMeridianN[1], "West", "West", "%g", 0, 180, 1, 30);
201  "Minutes Past Meridian", MOTION_TAB, IP_RW, 0, IPS_IDLE);
202 
203 
204  // ============== SITE_MANAGEMENT_TAB
205  IUFillSwitch(&SetHomeS[0], "RETURN_HOME", "Return Home", ISS_OFF);
206  IUFillSwitch(&SetHomeS[1], "AT_HOME", "At Home (Reset)", ISS_OFF);
207  IUFillSwitchVector(&SetHomeSP, SetHomeS, 2, getDeviceName(), "HOME_INIT", "Homing", SITE_TAB, IP_RW, ISR_ATMOST1, 60,
208  IPS_IDLE);
209 
210  // ============== GUIDE_TAB
211 
212  // ============== FOCUS_TAB
213  // Focuser 1
214 
215  IUFillSwitch(&OSFocus1InitializeS[0], "Focus1_0", "Zero", ISS_OFF);
216  IUFillSwitch(&OSFocus1InitializeS[1], "Focus1_2", "Mid", ISS_OFF);
217  // IUFillSwitch(&OSFocus1InitializeS[2], "Focus1_3", "max", ISS_OFF);
219  IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
220 
221  IUFillSwitch(&OSFocusSelectS[0], "Focuser_Primary_1", "Focuser 1", ISS_ON);
222  IUFillSwitch(&OSFocusSelectS[1], "Focuser_Primary_2", "Focuser 2/Swap", ISS_OFF);
223  // For when OnStepX comes out
224  IUFillSwitch(&OSFocusSelectS[2], "Focuser_Primary_3", "3", ISS_OFF);
225  IUFillSwitch(&OSFocusSelectS[3], "Focuser_Primary_4", "4", ISS_OFF);
226  IUFillSwitch(&OSFocusSelectS[4], "Focuser_Primary_5", "5", ISS_OFF);
227  IUFillSwitch(&OSFocusSelectS[5], "Focuser_Primary_6", "6", ISS_OFF);
228  IUFillSwitch(&OSFocusSelectS[6], "Focuser_Primary_7", "7", ISS_OFF);
229  IUFillSwitch(&OSFocusSelectS[7], "Focuser_Primary_8", "8", ISS_OFF);
230  IUFillSwitch(&OSFocusSelectS[8], "Focuser_Primary_9", "9", ISS_OFF);
231  IUFillSwitch(&OSFocusSelectS[9], "Focuser_Primary_10", "10", ISS_OFF);
232 
233  IUFillSwitchVector(&OSFocusSelectSP, OSFocusSelectS, 1, getDeviceName(), "OSFocusSWAP", "Primary Focuser", FOCUS_TAB,
234  IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
235 
236 
237  // Focuser 2
238  //IUFillSwitch(&OSFocus2SelS[0], "Focus2_Sel1", "Foc 1", ISS_OFF);
239  //IUFillSwitch(&OSFocus2SelS[1], "Focus2_Sel2", "Foc 2", ISS_OFF);
240  //IUFillSwitchVector(&OSFocus2SelSP, OSFocus2SelS, 2, getDeviceName(), "Foc2Sel", "Foc 2", FOCUS_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
241 
242  IUFillSwitch(&OSFocus2MotionS[0], "Focus2_In", "In", ISS_OFF);
243  IUFillSwitch(&OSFocus2MotionS[1], "Focus2_Out", "Out", ISS_OFF);
244  IUFillSwitch(&OSFocus2MotionS[2], "Focus2_Stop", "Stop", ISS_OFF);
246  ISR_ATMOST1, 0, IPS_IDLE);
247 
248  IUFillSwitch(&OSFocus2RateS[0], "Focus2_1", "min", ISS_OFF);
249  IUFillSwitch(&OSFocus2RateS[1], "Focus2_2", "0.01", ISS_OFF);
250  IUFillSwitch(&OSFocus2RateS[2], "Focus2_3", "0.1", ISS_OFF);
251  IUFillSwitch(&OSFocus2RateS[3], "Focus2_4", "1", ISS_OFF);
252  IUFillSwitchVector(&OSFocus2RateSP, OSFocus2RateS, 4, getDeviceName(), "Foc2Rate", "Foc 2 Rates", FOCUS_TAB, IP_RW,
253  ISR_ATMOST1, 0, IPS_IDLE);
254 
255  IUFillNumber(&OSFocus2TargN[0], "FocusTarget2", "Abs Pos", "%g", -25000, 25000, 1, 0);
256  IUFillNumberVector(&OSFocus2TargNP, OSFocus2TargN, 1, getDeviceName(), "Foc2Targ", "Foc 2 Target", FOCUS_TAB, IP_RW, 0,
257  IPS_IDLE);
258 
259  // =========== ROTATOR TAB
260 
261  IUFillSwitch(&OSRotatorDerotateS[0], "Derotate_OFF", "OFF", ISS_OFF);
262  IUFillSwitch(&OSRotatorDerotateS[1], "Derotate_ON", "ON", ISS_OFF);
264  IP_RW,
265  ISR_ATMOST1, 0, IPS_IDLE);
266 
267  // ============== FIRMWARE_TAB
268  IUFillText(&VersionT[0], "Date", "", "");
269  IUFillText(&VersionT[1], "Time", "", "");
270  IUFillText(&VersionT[2], "Number", "", "");
271  IUFillText(&VersionT[3], "Name", "", "");
272  IUFillTextVector(&VersionTP, VersionT, 4, getDeviceName(), "Firmware Info", "", FIRMWARE_TAB, IP_RO, 0, IPS_IDLE);
273 
274  //PEC Tab
275  IUFillSwitch(&OSPECStatusS[0], "OFF", "OFF", ISS_OFF);
276  IUFillSwitch(&OSPECStatusS[1], "Playing", "Playing", ISS_OFF);
277  IUFillSwitch(&OSPECStatusS[2], "Recording", "Recording", ISS_OFF);
278  IUFillSwitch(&OSPECStatusS[3], "Will Play", "Will Play", ISS_OFF);
279  IUFillSwitch(&OSPECStatusS[4], "Will Record", "Will Record", ISS_OFF);
280  IUFillSwitchVector(&OSPECStatusSP, OSPECStatusS, 5, getDeviceName(), "PEC Status", "PEC Status", PEC_TAB, IP_RO,
281  ISR_ATMOST1, 0, IPS_IDLE);
282 
283  IUFillSwitch(&OSPECIndexS[0], "Not Detected", "Not Detected", ISS_ON);
284  IUFillSwitch(&OSPECIndexS[1], "Detected", "Detected", ISS_OFF);
285  IUFillSwitchVector(&OSPECIndexSP, OSPECIndexS, 2, getDeviceName(), "PEC Index Detect", "PEC Index", PEC_TAB, IP_RO,
286  ISR_ATMOST1, 0, IPS_IDLE);
287 
288  IUFillSwitch(&OSPECRecordS[0], "Clear", "Clear", ISS_OFF);
289  IUFillSwitch(&OSPECRecordS[1], "Record", "Record", ISS_OFF);
290  IUFillSwitch(&OSPECRecordS[2], "Write to EEPROM", "Write to EEPROM", ISS_OFF);
291  IUFillSwitchVector(&OSPECRecordSP, OSPECRecordS, 3, getDeviceName(), "PEC Operations", "PEC Recording", PEC_TAB, IP_RW,
292  ISR_ATMOST1, 0, IPS_IDLE);
293 
294  IUFillSwitch(&OSPECReadS[0], "Read", "Read PEC to FILE****", ISS_OFF);
295  IUFillSwitch(&OSPECReadS[1], "Write", "Write PEC from FILE***", ISS_OFF);
296  // IUFillSwitch(&OSPECReadS[2], "Write to EEPROM", "Write to EEPROM", ISS_OFF);
297  IUFillSwitchVector(&OSPECReadSP, OSPECReadS, 2, getDeviceName(), "PEC File", "PEC File", PEC_TAB, IP_RW, ISR_ATMOST1, 0,
298  IPS_IDLE);
299  // ============== New ALIGN_TAB
300  // Only supports Alpha versions currently (July 2018) Now Beta (Dec 2018)
301  IUFillSwitch(&OSNAlignStarsS[0], "1", "1 Star", ISS_OFF);
302  IUFillSwitch(&OSNAlignStarsS[1], "2", "2 Stars", ISS_OFF);
303  IUFillSwitch(&OSNAlignStarsS[2], "3", "3 Stars", ISS_ON);
304  IUFillSwitch(&OSNAlignStarsS[3], "4", "4 Stars", ISS_OFF);
305  IUFillSwitch(&OSNAlignStarsS[4], "5", "5 Stars", ISS_OFF);
306  IUFillSwitch(&OSNAlignStarsS[5], "6", "6 Stars", ISS_OFF);
307  IUFillSwitch(&OSNAlignStarsS[6], "7", "7 Stars", ISS_OFF);
308  IUFillSwitch(&OSNAlignStarsS[7], "8", "8 Stars", ISS_OFF);
309  IUFillSwitch(&OSNAlignStarsS[8], "9", "9 Stars", ISS_OFF);
310  IUFillSwitchVector(&OSNAlignStarsSP, OSNAlignStarsS, 9, getDeviceName(), "AlignStars", "Align using some stars, Alpha only",
312 
313  IUFillSwitch(&OSNAlignS[0], "0", "Start Align", ISS_OFF);
314  IUFillSwitch(&OSNAlignS[1], "1", "Issue Align", ISS_OFF);
315  IUFillSwitch(&OSNAlignS[2], "3", "Write Align", ISS_OFF);
316  IUFillSwitchVector(&OSNAlignSP, OSNAlignS, 2, getDeviceName(), "NewAlignStar", "Align using up to 6 stars, Alpha only",
318 
319  IUFillSwitch(&OSNAlignWriteS[0], "0", "Write Align to NVRAM/Flash", ISS_OFF);
321  ISR_ATMOST1, 0, IPS_IDLE);
322  IUFillSwitch(&OSNAlignPolarRealignS[0], "0", "Instructions", ISS_OFF);
323  IUFillSwitch(&OSNAlignPolarRealignS[1], "1", "Refine Polar Align (manually)", ISS_OFF);
325  "Polar Correction, See info box", ALIGN_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
326 
327  IUFillText(&OSNAlignT[0], "0", "Align Process Status", "Align not started");
328  IUFillText(&OSNAlignT[1], "1", "1. Manual Process", "Point towards the NCP");
329  IUFillText(&OSNAlignT[2], "2", "2. Plate Solver Process", "Point towards the NCP");
330  IUFillText(&OSNAlignT[3], "3", "Manual Action after 1", "Press 'Start Align'");
331  IUFillText(&OSNAlignT[4], "4", "Current Status", "Not Updated");
332  IUFillText(&OSNAlignT[5], "5", "Max Stars", "Not Updated");
333  IUFillText(&OSNAlignT[6], "6", "Current Star", "Not Updated");
334  IUFillText(&OSNAlignT[7], "7", "# of Align Stars", "Not Updated");
335  IUFillTextVector(&OSNAlignTP, OSNAlignT, 8, getDeviceName(), "NAlign Process", "", ALIGN_TAB, IP_RO, 0, IPS_IDLE);
336 
337  IUFillText(&OSNAlignErrT[0], "0", "EQ Polar Error Alt", "Available once Aligned");
338  IUFillText(&OSNAlignErrT[1], "1", "EQ Polar Error Az", "Available once Aligned");
339  // IUFillText(&OSNAlignErrT[2], "2", "2. Plate Solver Process", "Point towards the NCP");
340  // IUFillText(&OSNAlignErrT[3], "3", "After 1 or 2", "Press 'Start Align'");
341  // IUFillText(&OSNAlignErrT[4], "4", "Current Status", "Not Updated");
342  // IUFillText(&OSNAlignErrT[5], "5", "Max Stars", "Not Updated");
343  // IUFillText(&OSNAlignErrT[6], "6", "Current Star", "Not Updated");
344  // IUFillText(&OSNAlignErrT[7], "7", "# of Align Stars", "Not Updated");
345  IUFillTextVector(&OSNAlignErrTP, OSNAlignErrT, 2, getDeviceName(), "ErrAlign Process", "", ALIGN_TAB, IP_RO, 0, IPS_IDLE);
346 
347 #ifdef ONSTEP_NOTDONE
348  // =============== OUTPUT_TAB
349  // ===============
350  IUFillSwitch(&OSOutput1S[0], "0", "OFF", ISS_ON);
351  IUFillSwitch(&OSOutput1S[1], "1", "ON", ISS_OFF);
352  IUFillSwitchVector(&OSOutput1SP, OSOutput1S, 2, getDeviceName(), "Output 1", "Output 1", OUTPUT_TAB, IP_RW, ISR_ATMOST1, 60,
353  IPS_ALERT);
354 
355  IUFillSwitch(&OSOutput2S[0], "0", "OFF", ISS_ON);
356  IUFillSwitch(&OSOutput2S[1], "1", "ON", ISS_OFF);
357  IUFillSwitchVector(&OSOutput2SP, OSOutput2S, 2, getDeviceName(), "Output 2", "Output 2", OUTPUT_TAB, IP_RW, ISR_ATMOST1, 60,
358  IPS_ALERT);
359 #endif
360 
361  for(int i = 0; i < PORTS_COUNT; i++)
362  {
363  char port_name[30];
364  snprintf(port_name, sizeof(port_name), "Output %d", i);
365  IUFillNumber(&OutputPorts[i], port_name, port_name, "%g", 0, 255, 1, 0);
366  }
367 
369  IPS_OK);
370 
371 
372  // ============== STATUS_TAB
373  IUFillText(&OnstepStat[0], ":GU# return", "", "");
374  IUFillText(&OnstepStat[1], "Tracking", "", "");
375  IUFillText(&OnstepStat[2], "Refractoring", "", "");
376  IUFillText(&OnstepStat[3], "Park", "", "");
377  IUFillText(&OnstepStat[4], "Pec", "", "");
378  IUFillText(&OnstepStat[5], "TimeSync", "", "");
379  IUFillText(&OnstepStat[6], "Mount Type", "", "");
380  IUFillText(&OnstepStat[7], "Error", "", "");
381  IUFillText(&OnstepStat[8], "Multi-Axis Tracking", "", "");
382  IUFillText(&OnstepStat[9], "TMC Axis1", "", "");
383  IUFillText(&OnstepStat[10], "TMC Axis2", "", "");
384  IUFillTextVector(&OnstepStatTP, OnstepStat, 11, getDeviceName(), "OnStep Status", "", STATUS_TAB, IP_RO, 0, IPS_OK);
385 
386  // ============== WEATHER TAB
387  // Uses OnStep's defaults for this
388  IUFillNumber(&OSSetTemperatureN[0], "Set Temperature (C)", "C", "%4.2f", -100, 100, 1, 10);//-274, 999, 1, 10);
390  IP_RW, 0, IPS_IDLE);
391  IUFillNumber(&OSSetHumidityN[0], "Set Relative Humidity (%)", "%", "%5.2f", 0, 100, 1, 70);
392  IUFillNumberVector(&OSSetHumidityNP, OSSetHumidityN, 1, getDeviceName(), "Set Relative Humidity (%)", "", ENVIRONMENT_TAB,
393  IP_RW, 0, IPS_IDLE);
394  IUFillNumber(&OSSetPressureN[0], "Set Pressure (hPa)", "hPa", "%4f", 500, 1500, 1, 1010);
396  0, IPS_IDLE);
397 
398  //Will eventually pull from the elevation in site settings
399  //TODO: Pull from elevation in site settings
400  IUFillNumber(&OSSetAltitudeN[0], "Set Altitude (m)", "m", "%4f", 0, 20000, 1, 110);
402  IPS_IDLE);
403 
404 
405  addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -40, 85, 15);
406  addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15);
407  addParameter("WEATHER_BAROMETER", "Pressure (hPa)", 0, 1500, 15);
408  addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15); // From OnStep
409  addParameter("WEATHER_CPU_TEMPERATURE", "OnStep CPU Temperature", -274, 200, -274); // From OnStep, -274 = unread
410  setCriticalParameter("WEATHER_TEMPERATURE");
411 
412  addAuxControls();
413 
414 
416 
417  return true;
418 }
419 
420 void LX200_OnStep::ISGetProperties(const char *dev)
421 {
422  if (dev != nullptr && strcmp(dev, getDeviceName()) != 0) return;
424 }
425 
427 {
429  //TODO: Properly setup Weather
431 
432  if (isConnected())
433  {
434  Connection::Interface *activeConnection = getActiveConnection();
435  if (!activeConnection->name().compare("CONNECTION_TCP"))
436  {
437  LOG_INFO("Network based connection, detection timeouts set to 2 seconds");
439  OSTimeoutSeconds = 2;
440  } else {
441  LOG_INFO("Non-Network based connection, detection timeouts set to 0.1 seconds");
442  OSTimeoutMicroSeconds = 100000;
443  OSTimeoutSeconds = 0;
444  }
445 
446  // Firstinitialize some variables
447  // keep sorted by TABs is easier
448  // Main Control
452  // Connection
453 
454  // Options
455 
456  // OnStep Status
458 
459  // Motion Control
470 
471  // Site Management
474 
475  // Guide
476 
477  // Focuser
478 
479  // Focuser 1
480  OSNumFocusers = 0; //Reset before detection
481  //if (!sendOnStepCommand(":FA#")) // do we have a Focuser 1
482  char response[RB_MAX_LEN] = {0};
483  int error_or_fail = getCommandSingleCharResponse(PortFD, response, ":FA#"); //0 = failure, 1 = success, no # on reply
484  if (error_or_fail > 0 && response[0] == '1')
485  {
486  LOG_INFO("Focuser 1 found");
487  OSFocuser1 = true;
489  OSNumFocusers = 1;
490  }
491  else
492  {
493  OSFocuser1 = false;
494  LOG_INFO("Focuser 1 NOT found");
495  LOGF_DEBUG("error_or_fail = %u, response = %c", error_or_fail, response[0]);
496  }
497  // Focuser 2
498  if (!sendOnStepCommand(":fA#")) // Do we have a Focuser 2 (:fA# will only work for OnStep, not OnStepX)
499  {
500  LOG_INFO("Focuser 2 found");
501  OSFocuser2 = true;
502  OSNumFocusers = 2;
503  //defineProperty(&OSFocus2SelSP);
507  IUFillSwitchVector(&OSFocusSelectSP, OSFocusSelectS, OSNumFocusers, getDeviceName(), "OSFocusSWAP", "Primary Focuser",
508  FOCUS_TAB,
509  IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
510  defineProperty(&OSFocusSelectSP); //Swap focusers (only matters if two focusers)
511  }
512  else //For OnStepX, up to 9 focusers
513  {
514  LOG_INFO("Focuser 2 NOT found");
515  OSFocuser2 = false;
517  {
518  LOG_INFO("Version unknown or OnStepX (Checking for OnStepX Focusers)");
519  for (int i = 0; i < 9; i++)
520  {
521  char cmd[CMD_MAX_LEN] = {0};
522  char read_buffer[RB_MAX_LEN] = {0};
523  snprintf(cmd, sizeof(cmd), ":F%dA#", i + 1);
524  int fail_or_error = getCommandSingleCharResponse(PortFD, read_buffer, cmd); //0 = failure, 1 = success, 0 on all prior to OnStepX no # on reply
525  if (!fail_or_error && read_buffer[0] == '1') // Do we have a Focuser X
526  {
527  LOGF_INFO("Focuser %i Found", i);
528  OSNumFocusers = i + 1;
529  }
530  else
531  {
532  if(fail_or_error < 0)
533  {
534  //Non detection = 0, Read errors < 0, stop
535  LOGF_INFO("Function call failed in a way that says OnStep doesn't have this setup, stopping Focuser probing, return: %i", fail_or_error);
536  break;
537  }
538  }
539  }
540  }
541  if (OSNumFocusers > 1)
542  {
543  IUFillSwitchVector(&OSFocusSelectSP, OSFocusSelectS, OSNumFocusers, getDeviceName(), "OSFocusSWAP", "Primary Focuser",
544  FOCUS_TAB,
545  IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
547  }
548  }
549  if (OSNumFocusers == 0)
550  {
551  LOG_INFO("No Focusers found");
552  }
553  else
554  {
555  LOG_INFO("At least one focuser found, showing interface");
557  }
558 
559  LOG_DEBUG("Focusers checked Variables:");
560  LOGF_DEBUG("OSFocuser1: %d, OSFocuser2: %d, OSNumFocusers: %i", OSFocuser1, OSFocuser2, OSNumFocusers);
561 
562  //Rotation Information
563  char rotator_response[RB_MAX_LEN] = {0};
564  error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, rotator_response, ":GX98#");
565  if (error_or_fail > 0)
566  {
567  if (rotator_response[0] == 'D' || rotator_response[0] == 'R') {
568  LOG_INFO("Rotator found.");
569  OSRotator1 = true;
571  }
572  if (rotator_response[0] == 'D')
573  {
575  }
576  } else {
577  LOGF_WARN("Error: %i", error_or_fail);
578  LOG_WARN("Error on response to rotator check (:GX98#) CHECK CONNECTION");
579  }
580 
581  if (OSRotator1 == false)
582  {
583  LOG_INFO("No Rotator found.");
584  OSRotator1 = false;
585  }
586 
587  // Firmware Data
589 
590  //PEC
591  //TODO: Define later when it might be supported
598 
599  //New Align
606 
607 #ifdef ONSTEP_NOTDONE
608  //Outputs
611 #endif
612  // OSHasOutputs = true; //Set to true on new connection, Init_Outputs will set to false if appropriate
613  Init_Outputs();
614 
615  //Weather
620 
621 
622 
623  if (InitPark())
624  {
625  // If loading parking data is successful, we just set the default parking values.
626  SetAxis1ParkDefault(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
628  }
629  else
630  {
631  // Otherwise, we set all parking data to default in case no parking data is found.
632  SetAxis1Park(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
634 
635  SetAxis1ParkDefault(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
637  }
638 
639  double longitude = -1000, latitude = -1000;
640  // Get value from config file if it exists.
641  IUGetConfigNumber(getDeviceName(), "GEOGRAPHIC_COORD", "LONG", &longitude);
642  IUGetConfigNumber(getDeviceName(), "GEOGRAPHIC_COORD", "LAT", &latitude);
643  // if (longitude != -1000 && latitude != -1000)
644  // {
645  // updateLocation(latitude, longitude, 0);
646  // }
647  //NOTE: if updateProperties is called it clobbers this, so added here
648  IUFillSwitch(&SlewRateS[0], "0", "0.25x", ISS_OFF);
649  IUFillSwitch(&SlewRateS[1], "1", "0.5x", ISS_OFF);
650  IUFillSwitch(&SlewRateS[2], "2", "1x", ISS_OFF);
651  IUFillSwitch(&SlewRateS[3], "3", "2x", ISS_OFF);
652  IUFillSwitch(&SlewRateS[4], "4", "4x", ISS_OFF);
653  IUFillSwitch(&SlewRateS[5], "5", "8x", ISS_ON);
654  IUFillSwitch(&SlewRateS[6], "6", "24x", ISS_OFF);
655  IUFillSwitch(&SlewRateS[7], "7", "48x", ISS_OFF);
656  IUFillSwitch(&SlewRateS[8], "8", "Half-Max", ISS_OFF);
657  IUFillSwitch(&SlewRateS[9], "9", "Max", ISS_OFF);
658  }
659  else
660  {
661  // keep sorted by TABs is easier
662  // Main Control
665  // Connection
666 
667  // Options
668 
669  // Motion Control
680 
681  // Site Management
684  // Guide
685 
686  // Focuser
687  // Focuser 1
689 
690  // Focuser 2
691  //deleteProperty(OSFocus2SelSP.name);
696 
697  // Rotator
699 
700  // Firmware Data
702 
703 
704  //PEC
711 
712  //New Align
719 
720 #ifdef ONSTEP_NOTDONE
721  //Outputs
724 #endif
725 
727 
728  // OnStep Status
730  //Weather
737  OSHasOutputs = true; //Set once per connection, either at startup or on disconnection for next connection;
738  }
739  LOG_INFO("Initialization Complete");
740  return true;
741 }
742 
743 bool LX200_OnStep::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
744 {
745  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
746  {
747  if (strstr(name, "FOCUS_"))
748  return FI::processNumber(dev, name, values, names, n);
749  if (strstr(name, "ROTATOR_"))
750  return RI::processNumber(dev, name, values, names, n);
751 
752  if (strcmp(name, "EQUATORIAL_EOD_COORD") == 0)
753  //Replace this from inditelescope so it doesn't change state
754  //Most of this needs to be handled by our updates, or it breaks things
755  {
756  // this is for us, and it is a goto
757  bool rc = false;
758  double ra = -1;
759  double dec = -100;
760 
761  for (int x = 0; x < n; x++)
762  {
763  INumber *eqp = IUFindNumber(&EqNP, names[x]);
764  if (eqp == &EqN[AXIS_RA])
765  {
766  ra = values[x];
767  }
768  else if (eqp == &EqN[AXIS_DE])
769  {
770  dec = values[x];
771  }
772  }
773  if ((ra >= 0) && (ra <= 24) && (dec >= -90) && (dec <= 90))
774  {
775  // Check if it is already parked.
776  if (CanPark())
777  {
778  if (isParked())
779  {
780  LOG_DEBUG("Please unpark the mount before issuing any motion/sync commands.");
781  // EqNP.s = lastEqState = IPS_IDLE;
782  // IDSetNumber(&EqNP, nullptr);
783  return false;
784  }
785  }
786 
787  // Check if it can sync
788  if (Telescope::CanSync())
789  {
790  ISwitch *sw;
791  sw = IUFindSwitch(&CoordSP, "SYNC");
792  if ((sw != nullptr) && (sw->s == ISS_ON))
793  {
794  rc = Sync(ra, dec);
795  // if (rc)
796  // EqNP.s = lastEqState = IPS_OK;
797  // else
798  // EqNP.s = lastEqState = IPS_ALERT;
799  // IDSetNumber(&EqNP, nullptr);
800  return rc;
801  }
802  }
803 
804  // Remember Track State
805  // RememberTrackState = TrackState;
806  // Issue GOTO
807  rc = Goto(ra, dec);
808  if (rc)
809  {
810  // EqNP.s = lastEqState = IPS_BUSY;
811  // Now fill in target co-ords, so domes can start turning
812  TargetN[AXIS_RA].value = ra;
813  TargetN[AXIS_DE].value = dec;
814  IDSetNumber(&TargetNP, nullptr);
815  }
816  else
817  {
818  // EqNP.s = lastEqState = IPS_ALERT;
819  }
820  // IDSetNumber(&EqNP, nullptr);
821  }
822  return rc;
823  }
824 
825  if (!strcmp(name, ObjectNoNP.name))
826  {
827  char object_name[256];
828 
829  if (selectCatalogObject(PortFD, currentCatalog, (int)values[0]) < 0)
830  {
832  IDSetNumber(&ObjectNoNP, "Failed to select catalog object.");
833  return false;
834  }
835 
838 
839  ObjectNoNP.s = IPS_OK;
840  IDSetNumber(&ObjectNoNP, "Object updated.");
841 
842  if (getObjectInfo(PortFD, object_name) < 0)
843  IDMessage(getDeviceName(), "Getting object info failed.");
844  else
845  {
846  IUSaveText(&ObjectInfoTP.tp[0], object_name);
847  IDSetText(&ObjectInfoTP, nullptr);
848  }
850  return true;
851  }
852 
853  if (!strcmp(name, MaxSlewRateNP.name))
854  {
855  int ret;
856  char cmd[CMD_MAX_LEN] = {0};
857  snprintf(cmd, 5, ":R%d#", (int)values[0]);
859 
860  //if (setMaxSlewRate(PortFD, (int)values[0]) < 0) //(int) MaxSlewRateN[0].value
861  if (ret == -1)
862  {
863  LOGF_DEBUG("Pas OK Return value =%d", ret);
864  LOGF_DEBUG("Setting Max Slew Rate to %f\n", values[0]);
866  IDSetNumber(&MaxSlewRateNP, "Setting Max Slew Rate Failed");
867  return false;
868  }
869  LOGF_DEBUG("OK Return value =%d", ret);
871  MaxSlewRateNP.np[0].value = values[0];
872  IDSetNumber(&MaxSlewRateNP, "Slewrate set to %04.1f", values[0]);
874  SlewRateS[int(values[0])].s = ISS_ON;
875  SlewRateSP.s = IPS_OK;
876  IDSetSwitch(&SlewRateSP, nullptr);
877  return true;
878  }
879 
880  if (!strcmp(name, BacklashNP.name))
881  {
882  //char cmd[CMD_MAX_LEN] = {0};
883  int i, nset;
884  double bklshdec = 0, bklshra = 0;
885 
886  for (nset = i = 0; i < n; i++)
887  {
888  INumber *bktp = IUFindNumber(&BacklashNP, names[i]);
889  if (bktp == &BacklashN[0])
890  {
891  bklshdec = values[i];
892  LOGF_DEBUG("===CMD==> Backlash DEC= %f", bklshdec);
893  nset += bklshdec >= 0 && bklshdec <= 999; //range 0 to 999
894  }
895  else if (bktp == &BacklashN[1])
896  {
897  bklshra = values[i];
898  LOGF_DEBUG("===CMD==> Backlash RA= %f", bklshra);
899  nset += bklshra >= 0 && bklshra <= 999; //range 0 to 999
900  }
901  }
902  if (nset == 2)
903  {
904  char cmd[CMD_MAX_LEN] = {0};
905  snprintf(cmd, 9, ":$BD%d#", (int)bklshdec);
906  if (sendOnStepCommand(cmd))
907  {
909  IDSetNumber(&BacklashNP, "Error Backlash DEC limit.");
910  }
911  const struct timespec timeout = {0, 100000000L};
912  nanosleep(&timeout, nullptr); // time for OnStep to respond to previous cmd
913  snprintf(cmd, 9, ":$BR%d#", (int)bklshra);
914  if (sendOnStepCommand(cmd))
915  {
917  IDSetNumber(&BacklashNP, "Error Backlash RA limit.");
918  }
919 
920  BacklashNP.np[0].value = bklshdec;
921  BacklashNP.np[1].value = bklshra;
922  BacklashNP.s = IPS_OK;
923  IDSetNumber(&BacklashNP, nullptr);
924  return true;
925  }
926  else
927  {
929  IDSetNumber(&BacklashNP, "Backlash invalid.");
930  return false;
931  }
932  }
933 
934  if (!strcmp(name, ElevationLimitNP.name))
935  {
936  // new elevation limits
937  double minAlt = 0, maxAlt = 0;
938  int i, nset;
939 
940  for (nset = i = 0; i < n; i++)
941  {
942  INumber *altp = IUFindNumber(&ElevationLimitNP, names[i]);
943  if (altp == &ElevationLimitN[0])
944  {
945  minAlt = values[i];
946  nset += minAlt >= -30.0 && minAlt <= 30.0; //range -30 to 30
947  }
948  else if (altp == &ElevationLimitN[1])
949  {
950  maxAlt = values[i];
951  nset += maxAlt >= 60.0 && maxAlt <= 90.0; //range 60 to 90
952  }
953  }
954  if (nset == 2)
955  {
956  if (setMinElevationLimit(PortFD, (int)minAlt) < 0)
957  {
959  IDSetNumber(&ElevationLimitNP, "Error setting min elevation limit.");
960  }
961 
962  if (setMaxElevationLimit(PortFD, (int)maxAlt) < 0)
963  {
965  IDSetNumber(&ElevationLimitNP, "Error setting max elevation limit.");
966  return false;
967  }
968  ElevationLimitNP.np[0].value = minAlt;
969  ElevationLimitNP.np[1].value = maxAlt;
971  IDSetNumber(&ElevationLimitNP, nullptr);
972  return true;
973  }
974  else
975  {
977  IDSetNumber(&ElevationLimitNP, "elevation limit missing or invalid.");
978  return false;
979  }
980  }
981  }
982 
983  if (!strcmp(name, minutesPastMeridianNP.name))
984  {
985  //char cmd[CMD_MAX_LEN] ={0};
986  int i, nset;
987  double minPMEast = 0, minPMWest = 0;
988 
989  for (nset = i = 0; i < n; i++)
990  {
991  INumber *bktp = IUFindNumber(&minutesPastMeridianNP, names[i]);
992  if (bktp == &minutesPastMeridianN[0])
993  {
994  minPMEast = values[i];
995  LOGF_DEBUG("===CMD==> minutesPastMeridianN[0]/East = %f", minPMEast);
996  nset += minPMEast >= 0 && minPMEast <= 180; //range 0 to 180
997  }
998  else if (bktp == &minutesPastMeridianN[1])
999  {
1000  minPMWest = values[i];
1001  LOGF_DEBUG("===CMD==> minutesPastMeridianN[1]/West= %f", minPMWest);
1002  nset += minPMWest >= 0 && minPMWest <= 180; //range 0 to 180
1003  }
1004  }
1005  if (nset == 2)
1006  {
1007  char cmd[CMD_MAX_LEN] = {0};
1008  snprintf(cmd, 20, ":SXE9,%d#", (int) minPMEast);
1009  if (sendOnStepCommand(cmd))
1010  {
1012  IDSetNumber(&minutesPastMeridianNP, "Error Backlash DEC limit.");
1013  }
1014  const struct timespec timeout = {0, 100000000L};
1015  nanosleep(&timeout, nullptr); // time for OnStep to respond to previous cmd
1016  snprintf(cmd, 20, ":SXEA,%d#", (int) minPMWest);
1017  if (sendOnStepCommand(cmd))
1018  {
1020  IDSetNumber(&minutesPastMeridianNP, "Error Backlash RA limit.");
1021  }
1022 
1023  minutesPastMeridianNP.np[0].value = minPMEast;
1024  minutesPastMeridianNP.np[1].value = minPMWest;
1027  return true;
1028  }
1029  else
1030  {
1032  IDSetNumber(&minutesPastMeridianNP, "minutesPastMeridian invalid.");
1033  return false;
1034  }
1035  }
1036  // Focuser
1037  // Focuser 1 Now handled by Focusr Interface
1038 
1039  // Focuser 2 Target
1040  if (!strcmp(name, OSFocus2TargNP.name))
1041  {
1042  char cmd[CMD_MAX_LEN] = {0};
1043 
1044  if ((values[0] >= -25000) && (values[0] <= 25000))
1045  {
1046  snprintf(cmd, 15, ":fR%d#", (int)values[0]);
1049  IDSetNumber(&OSFocus2TargNP, "Focuser 2 position (relative) moved by %d", (int)values[0]);
1050  OSUpdateFocuser();
1051  }
1052  else
1053  {
1055  IDSetNumber(&OSFocus2TargNP, "Setting Max Slew Rate Failed");
1056  }
1057  return true;
1058  }
1059 
1060  if (!strcmp(name, OutputPorts_NP.name))
1061  {
1062  //go through all output values and see if any value needs to be changed
1063  for(int i = 0; i < n; i++)
1064  {
1065  int value = (int)values[i];
1066  if(OutputPorts_NP.np[i].value != value)
1067  {
1068  int ret;
1069  char cmd[CMD_MAX_LEN] = {0};
1070  int port = STARTING_PORT + i;
1071 
1072  //This is for newer version of OnStep:
1073  snprintf(cmd, sizeof(cmd), ":SXX%d,V%d#", port, value);
1074  //This is for older version of OnStep:
1075  //snprintf(cmd, sizeof(cmd), ":SXG%d,%d#", port, value);
1076  ret = sendOnStepCommandBlind(cmd);
1077 
1078  if (ret == -1)
1079  {
1080  LOGF_ERROR("Set port %d to value =%d failed", port, value);
1082  return false;
1083  }
1084 
1086  OutputPorts_NP.np[i].value = value;
1087  IDSetNumber(&OutputPorts_NP, "Set port %d to value =%d", port, value);
1088  }
1089  }
1090  return true;
1091  }
1092  //Weather not handled by Weather Interface
1093 
1094  if (!strcmp(name, OSSetTemperatureNP.name))
1095  {
1096  // char cmd[CMD_MAX_LEN] = {0};
1097 
1098  if ((values[0] >= -100) && (values[0] <= 100))
1099  {
1100  char cmd[CMD_MAX_LEN] = {0};
1101  snprintf(cmd, 15, ":SX9A,%d#", (int)values[0]);
1104  IDSetNumber(&OSSetTemperatureNP, "Temperature set to %d", (int)values[0]);
1105  }
1106  else
1107  {
1109  IDSetNumber(&OSSetTemperatureNP, "Setting Temperature Failed");
1110  }
1111  return true;
1112  }
1113 
1114  if (!strcmp(name, OSSetHumidityNP.name))
1115  {
1116  char cmd[CMD_MAX_LEN] = {0};
1117 
1118  if ((values[0] >= 0) && (values[0] <= 100))
1119  {
1120  snprintf(cmd, 15, ":SX9C,%d#", (int)values[0]);
1123  IDSetNumber(&OSSetHumidityNP, "Humidity set to %d", (int)values[0]);
1124  }
1125  else
1126  {
1128  IDSetNumber(&OSSetHumidityNP, "Setting Humidity Failed");
1129  }
1130  return true;
1131  }
1132 
1133  if (!strcmp(name, OSSetPressureNP.name))
1134  {
1135  char cmd[CMD_MAX_LEN] = {0};
1136 
1137  if ((values[0] >= 0) && (values[0] <= 100))
1138  {
1139  snprintf(cmd, 15, ":SX9B,%d#", (int)values[0]);
1142  IDSetNumber(&OSSetPressureNP, "Pressure set to %d", (int)values[0]);
1143  }
1144  else
1145  {
1147  IDSetNumber(&OSSetPressureNP, "Setting Pressure Failed");
1148  }
1149  return true;
1150  }
1151 
1152  if (strstr(name, "WEATHER_"))
1153  {
1154  return WI::processNumber(dev, name, values, names, n);
1155  }
1156 
1157  return LX200Generic::ISNewNumber(dev, name, values, names, n);
1158 }
1159 
1160 bool LX200_OnStep::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1161 {
1162  int index = 0;
1163 
1164  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1165  {
1166  //Intercept Before inditelescope base can set TrackState
1167  //Next one modification of inditelescope.cpp function
1168  if (!strcmp(name, TrackStateSP.name))
1169  {
1170  // int previousState = IUFindOnSwitchIndex(&TrackStateSP);
1171  IUUpdateSwitch(&TrackStateSP, states, names, n);
1172  int targetState = IUFindOnSwitchIndex(&TrackStateSP);
1173  // LOG_DEBUG("OnStep driver TrackStateSP override called");
1174  // if (previousState == targetState)
1175  // {
1176  // IDSetSwitch(&TrackStateSP, nullptr);
1177  // return true;
1178  // }
1179 
1180  if (TrackState == SCOPE_PARKED)
1181  {
1182  LOG_WARN("Telescope is Parked, Unpark before tracking.");
1183  return false;
1184  }
1185 
1186  bool rc = SetTrackEnabled((targetState == TRACK_ON) ? true : false);
1187 
1188  if (rc)
1189  {
1190  return true;
1191  //TrackStateSP moved to Update
1192  }
1193  else
1194  {
1195  //This is the case for an error on sending the command, so change TrackStateSP
1198  return false;
1199  }
1200 
1201  LOG_DEBUG("TrackStateSP intercept, OnStep driver, should never get here");
1202  return false;
1203  }
1204 
1205 
1206 
1207  // Reticlue +/- Buttons
1208  if (!strcmp(name, ReticSP.name))
1209  {
1210  long ret = 0;
1211 
1212  IUUpdateSwitch(&ReticSP, states, names, n);
1213  ReticSP.s = IPS_OK;
1214 
1215  if (ReticS[0].s == ISS_ON)
1216  {
1217  ret = ReticPlus(PortFD);
1218  ReticS[0].s = ISS_OFF;
1219  IDSetSwitch(&ReticSP, "Bright");
1220  }
1221  else
1222  {
1223  ret = ReticMoins(PortFD);
1224  ReticS[1].s = ISS_OFF;
1225  IDSetSwitch(&ReticSP, "Dark");
1226  }
1227 
1229  IDSetSwitch(&ReticSP, nullptr);
1230  return true;
1231  }
1232  //Move to more standard controls
1233  if (!strcmp(name, SlewRateSP.name))
1234  {
1235  IUUpdateSwitch(&SlewRateSP, states, names, n);
1236  int ret;
1237  char cmd[CMD_MAX_LEN] = {0};
1238  int index = IUFindOnSwitchIndex(&SlewRateSP) ;//-1; //-1 because index is 1-10, OS Values are 0-9
1239  snprintf(cmd, sizeof(cmd), ":R%d#", index);
1240  ret = sendOnStepCommandBlind(cmd);
1241 
1242  //if (setMaxSlewRate(PortFD, (int)values[0]) < 0) //(int) MaxSlewRateN[0].value
1243  if (ret == -1)
1244  {
1245  LOGF_DEBUG("Pas OK Return value =%d", ret);
1246  LOGF_DEBUG("Setting Max Slew Rate to %u\n", index);
1248  IDSetSwitch(&SlewRateSP, "Setting Max Slew Rate Failed");
1249  return false;
1250  }
1251  LOGF_INFO("Setting Max Slew Rate to %u (%s) \n", index, SlewRateS[index].label);
1252  LOGF_DEBUG("OK Return value =%d", ret);
1254  MaxSlewRateNP.np[0].value = index;
1255  IDSetNumber(&MaxSlewRateNP, "Slewrate set to %d", index);
1257  SlewRateS[index].s = ISS_ON;
1258  SlewRateSP.s = IPS_OK;
1259  IDSetSwitch(&SlewRateSP, nullptr);
1260  return true;
1261  }
1262  // Homing, Cold and Warm Init
1263  if (!strcmp(name, SetHomeSP.name))
1264  {
1265  IUUpdateSwitch(&SetHomeSP, states, names, n);
1266  SetHomeSP.s = IPS_OK;
1267 
1268  if (SetHomeS[0].s == ISS_ON)
1269  {
1270  if(!sendOnStepCommandBlind(":hC#"))
1271  return false;
1272  IDSetSwitch(&SetHomeSP, "Return Home");
1273  SetHomeS[0].s = ISS_OFF;
1274  }
1275  else
1276  {
1277  if(!sendOnStepCommandBlind(":hF#"))
1278  return false;
1279  IDSetSwitch(&SetHomeSP, "At Home (Reset)");
1280  SetHomeS[1].s = ISS_OFF;
1281  }
1283  SetHomeSP.s = IPS_IDLE;
1284  IDSetSwitch(&SetHomeSP, nullptr);
1285  return true;
1286  }
1287 
1288  // Tracking Compensation selection
1289  if (!strcmp(name, TrackCompSP.name))
1290  {
1291  IUUpdateSwitch(&TrackCompSP, states, names, n);
1293 
1294  if (TrackCompS[0].s == ISS_ON)
1295  {
1296  if (!sendOnStepCommand(":To#"))
1297  {
1298  IDSetSwitch(&TrackCompSP, "Full Compensated Tracking On");
1299  TrackCompSP.s = IPS_OK;
1300  IDSetSwitch(&TrackCompSP, nullptr);
1301  return true;
1302  }
1303  }
1304  if (TrackCompS[1].s == ISS_ON)
1305  {
1306  if (!sendOnStepCommand(":Tr#"))
1307  {
1308  IDSetSwitch(&TrackCompSP, "Refraction Tracking On");
1309  TrackCompSP.s = IPS_OK;
1310  IDSetSwitch(&TrackCompSP, nullptr);
1311  return true;
1312  }
1313  }
1314  if (TrackCompS[2].s == ISS_ON)
1315  {
1316  if (!sendOnStepCommand(":Tn#"))
1317  {
1318  IDSetSwitch(&TrackCompSP, "Refraction Tracking Disabled");
1319  TrackCompSP.s = IPS_OK;
1320  IDSetSwitch(&TrackCompSP, nullptr);
1321  return true;
1322  }
1323  }
1326  IDSetSwitch(&TrackCompSP, nullptr);
1327  return true;
1328  }
1329 
1330  if (!strcmp(name, TrackAxisSP.name))
1331  {
1332  IUUpdateSwitch(&TrackAxisSP, states, names, n);
1334 
1335  if (TrackAxisS[0].s == ISS_ON)
1336  {
1337  if (!sendOnStepCommand(":T1#"))
1338  {
1339  IDSetSwitch(&TrackAxisSP, "Single Tracking On");
1340  TrackAxisSP.s = IPS_OK;
1341  IDSetSwitch(&TrackAxisSP, nullptr);
1342  return true;
1343  }
1344  }
1345  if (TrackAxisS[1].s == ISS_ON)
1346  {
1347  if (!sendOnStepCommand(":T2#"))
1348  {
1349  IDSetSwitch(&TrackAxisSP, "Dual Axis Tracking On");
1350  TrackAxisSP.s = IPS_OK;
1351  IDSetSwitch(&TrackAxisSP, nullptr);
1352  return true;
1353  }
1354  }
1357  IDSetSwitch(&TrackAxisSP, nullptr);
1358  return true;
1359  }
1360 
1361  if (!strcmp(name, AutoFlipSP.name))
1362  {
1363  IUUpdateSwitch(&AutoFlipSP, states, names, n);
1364  AutoFlipSP.s = IPS_BUSY;
1365 
1366  if (AutoFlipS[0].s == ISS_ON)
1367  {
1368  if (sendOnStepCommand(":SX95,0#"))
1369  {
1370  AutoFlipSP.s = IPS_OK;
1371  IDSetSwitch(&AutoFlipSP, "Auto Meridian Flip OFF");
1372  return true;
1373  }
1374  }
1375  if (AutoFlipS[1].s == ISS_ON)
1376  {
1377  if (sendOnStepCommand(":SX95,1#"))
1378  {
1379  AutoFlipSP.s = IPS_OK;
1380  IDSetSwitch(&AutoFlipSP, "Auto Meridian Flip ON");
1381  return true;
1382  }
1383  }
1385  //AutoFlipSP.s = IPS_IDLE;
1386  IDSetSwitch(&AutoFlipSP, nullptr);
1387  return true;
1388  }
1389 
1390  if (!strcmp(name, HomePauseSP.name))
1391  {
1392  IUUpdateSwitch(&HomePauseSP, states, names, n);
1394 
1395  if (HomePauseS[0].s == ISS_ON)
1396  {
1397  if (sendOnStepCommand(":SX98,0#"))
1398  {
1399  HomePauseSP.s = IPS_OK;
1400  IDSetSwitch(&HomePauseSP, "Home Pause OFF");
1401  return true;
1402  }
1403  }
1404  if (HomePauseS[1].s == ISS_ON)
1405  {
1406  if (sendOnStepCommand(":SX98,1#"))
1407  {
1408  HomePauseSP.s = IPS_OK;
1409  IDSetSwitch(&HomePauseSP, "Home Pause ON");
1410  return true;
1411  }
1412  }
1413  if (HomePauseS[2].s == ISS_ON)
1414  {
1415  if (sendOnStepCommand(":SX99,1#"))
1416  {
1418  HomePauseSP.s = IPS_OK;
1419  IDSetSwitch(&HomePauseSP, "Home Pause: Continue");
1420  return true;
1421  }
1422  }
1425  IDSetSwitch(&HomePauseSP, nullptr);
1426  return true;
1427  }
1428 
1429  if (!strcmp(name, FrequencyAdjustSP.name)) //
1430 
1431  //
1432  {
1433  IUUpdateSwitch(&FrequencyAdjustSP, states, names, n);
1435 
1436  if (FrequencyAdjustS[0].s == ISS_ON)
1437  {
1438  if (!sendOnStepCommandBlind(":T-#"))
1439  {
1440  IDSetSwitch(&FrequencyAdjustSP, "Frequency decreased");
1441  return true;
1442  }
1443  }
1444  if (FrequencyAdjustS[1].s == ISS_ON)
1445  {
1446  if (!sendOnStepCommandBlind(":T+#"))
1447  {
1448  IDSetSwitch(&FrequencyAdjustSP, "Frequency increased");
1449  return true;
1450  }
1451  }
1452  if (FrequencyAdjustS[2].s == ISS_ON)
1453  {
1454  if (!sendOnStepCommandBlind(":TR#"))
1455  {
1456  IDSetSwitch(&FrequencyAdjustSP, "Frequency Reset (TO saved EEPROM)");
1457  return true;
1458  }
1459  }
1462  IDSetSwitch(&FrequencyAdjustSP, nullptr);
1463  return true;
1464  }
1465 
1466  //Pier Side
1467  if (!strcmp(name, PreferredPierSideSP.name))
1468  {
1469  IUUpdateSwitch(&PreferredPierSideSP, states, names, n);
1471 
1472  if (PreferredPierSideS[0].s == ISS_ON) //West
1473  {
1474  if (sendOnStepCommand(":SX96,W#"))
1475  {
1477  IDSetSwitch(&PreferredPierSideSP, "Preferred Pier Side: West");
1478  return true;
1479  }
1480  }
1481  if (PreferredPierSideS[1].s == ISS_ON) //East
1482  {
1483  if (sendOnStepCommand(":SX96,E#"))
1484  {
1486  IDSetSwitch(&PreferredPierSideSP, "Preferred Pier Side: East");
1487  return true;
1488  }
1489  }
1490  if (PreferredPierSideS[2].s == ISS_ON) //Best
1491  {
1492  if (sendOnStepCommand(":SX96,B#"))
1493  {
1495  IDSetSwitch(&PreferredPierSideSP, "Preferred Pier Side: Best");
1496  return true;
1497  }
1498  }
1500  IDSetSwitch(&PreferredPierSideSP, nullptr);
1501  return true;
1502  }
1503 
1504 
1505  // Focuser
1506  // Focuser 1 Rates
1507  if (!strcmp(name, OSFocus1InitializeSP.name))
1508  {
1509  char cmd[CMD_MAX_LEN] = {0};
1510  if (IUUpdateSwitch(&OSFocus1InitializeSP, states, names, n) < 0)
1511  return false;
1513  if (index == 0)
1514  {
1515  snprintf(cmd, 5, ":FZ#");
1517  OSFocus1InitializeS[index].s = ISS_OFF;
1519  IDSetSwitch(&OSFocus1InitializeSP, nullptr);
1520  }
1521  if (index == 1)
1522  {
1523  snprintf(cmd, 5, ":FH#");
1525  OSFocus1InitializeS[index].s = ISS_OFF;
1527  IDSetSwitch(&OSFocus1InitializeSP, nullptr);
1528  }
1529  }
1530 
1531 
1532  //Focuser Swap/Select
1533  if (!strcmp(name, OSFocusSelectSP.name))
1534  {
1535  char cmd[CMD_MAX_LEN] = {0};
1536  int i;
1537  if (IUUpdateSwitch(&OSFocusSelectSP, states, names, n) < 0)
1538  return false;
1540  LOGF_INFO("Primary focuser set: Focuser 1 in INDI/Controllable Focuser = OnStep Focuser %d", index + 1);
1541  if (index == 0 && OSNumFocusers <= 2)
1542  {
1543  LOG_INFO("If using OnStep: Focuser 2 in INDI = OnStep Focuser 2");
1544  }
1545  if (index == 1 && OSNumFocusers <= 2)
1546  {
1547  LOG_INFO("If using OnStep: Focuser 2 in INDI = OnStep Focuser 1");
1548  }
1549  if (OSNumFocusers > 2)
1550  {
1551  LOGF_INFO("If using OnStepX, There is no swap, and current max number: %d", OSNumFocusers);
1552  }
1553  snprintf(cmd, 7, ":FA%d#", index + 1 );
1554  for (i = 0; i < 9; i++)
1555  {
1556  OSFocusSelectS[i].s = ISS_OFF;
1557  }
1558  OSFocusSelectS[index].s = ISS_ON;
1559  if (!sendOnStepCommand(cmd))
1560  {
1562  }
1563  else
1564  {
1566  }
1567  IDSetSwitch(&OSFocusSelectSP, nullptr);
1568  }
1569 
1570 
1571  // Focuser 2 Rates
1572  if (!strcmp(name, OSFocus2RateSP.name))
1573  {
1574  char cmd[CMD_MAX_LEN] = {0};
1575 
1576  if (IUUpdateSwitch(&OSFocus2RateSP, states, names, n) < 0)
1577  return false;
1578 
1580  snprintf(cmd, 5, ":F%d#", index + 1);
1582  OSFocus2RateS[index].s = ISS_OFF;
1584  IDSetSwitch(&OSFocus2RateSP, nullptr);
1585  }
1586  // Focuser 2 Motion
1587  if (!strcmp(name, OSFocus2MotionSP.name))
1588  {
1589  char cmd[CMD_MAX_LEN] = {0};
1590 
1591  if (IUUpdateSwitch(&OSFocus2MotionSP, states, names, n) < 0)
1592  return false;
1593 
1595  if (index == 0)
1596  {
1597  strncpy(cmd, ":f+#", sizeof(cmd));
1598  }
1599  if (index == 1)
1600  {
1601  strncpy(cmd, ":f-#", sizeof(cmd));
1602  }
1603  if (index == 2)
1604  {
1605  strncpy(cmd, ":fQ#", sizeof(cmd));
1606  }
1608  const struct timespec timeout = {0, 100000000L};
1609  nanosleep(&timeout, nullptr); // Pulse 0,1 s
1610  if(index != 2)
1611  {
1612  sendOnStepCommandBlind(":fQ#");
1613  }
1614  OSFocus2MotionS[index].s = ISS_OFF;
1616  IDSetSwitch(&OSFocus2MotionSP, nullptr);
1617  }
1618 
1619  //Rotator De-rotation
1620  // OSRotatorDerotateS
1621  if (!strcmp(name, OSRotatorDerotateSP.name))
1622  {
1623  char cmd[CMD_MAX_LEN] = {0};
1624 
1625  if (IUUpdateSwitch(&OSRotatorDerotateSP, states, names, n) < 0)
1626  return false;
1627 
1629  if (index == 0) //Derotate_OFF
1630  {
1631  strncpy(cmd, ":r-#", sizeof(cmd));
1632  }
1633  if (index == 1) //Derotate_ON
1634  {
1635  strncpy(cmd, ":r+#", sizeof(cmd));
1636  }
1638  OSRotatorDerotateS[index].s = ISS_OFF;
1640  IDSetSwitch(&OSRotatorDerotateSP, nullptr);
1641  }
1642 
1643  // PEC
1644  if (!strcmp(name, OSPECRecordSP.name))
1645  {
1646  IUUpdateSwitch(&OSPECRecordSP, states, names, n);
1648 
1649  if (OSPECRecordS[0].s == ISS_ON)
1650  {
1651  OSPECEnabled = true;
1652  ClearPECBuffer(0);
1653  OSPECRecordS[0].s = ISS_OFF;
1654  }
1655  if (OSPECRecordS[1].s == ISS_ON)
1656  {
1657  OSPECEnabled = true;
1658  StartPECRecord(0);
1659  OSPECRecordS[1].s = ISS_OFF;
1660  }
1661  if (OSPECRecordS[2].s == ISS_ON)
1662  {
1663  OSPECEnabled = true;
1664  SavePECBuffer(0);
1665  OSPECRecordS[2].s = ISS_OFF;
1666  }
1667  IDSetSwitch(&OSPECRecordSP, nullptr);
1668  }
1669  if (!strcmp(name, OSPECReadSP.name))
1670  {
1671  if (OSPECReadS[0].s == ISS_ON)
1672  {
1673  OSPECEnabled = true;
1674  ReadPECBuffer(0);
1675  OSPECReadS[0].s = ISS_OFF;
1676  }
1677  if (OSPECReadS[1].s == ISS_ON)
1678  {
1679  OSPECEnabled = true;
1680  WritePECBuffer(0);
1681  OSPECReadS[1].s = ISS_OFF;
1682  }
1683  IDSetSwitch(&OSPECReadSP, nullptr);
1684  }
1685  if (!strcmp(name, PECStateSP.name))
1686  {
1687  index = IUFindOnSwitchIndex(&PECStateSP);
1688  if (index == 0)
1689  {
1690  OSPECEnabled = true;
1691  StopPECPlayback(0); //Status will set OSPECEnabled to false if that's set by the controller
1692  PECStateS[0].s = ISS_ON;
1693  PECStateS[1].s = ISS_OFF;
1694  IDSetSwitch(&PECStateSP, nullptr);
1695  }
1696  else if (index == 1)
1697  {
1698  OSPECEnabled = true;
1699  StartPECPlayback(0);
1700  PECStateS[0].s = ISS_OFF;
1701  PECStateS[1].s = ISS_ON;
1702  IDSetSwitch(&PECStateSP, nullptr);
1703  }
1704 
1705  }
1706 
1707  // Align Buttons
1708  if (!strcmp(name, OSNAlignStarsSP.name))
1709  {
1711  IUUpdateSwitch(&OSNAlignStarsSP, states, names, n);
1713 
1714  return true;
1715  }
1716 
1717  // Alignment
1718  if (!strcmp(name, OSNAlignSP.name))
1719  {
1720  if (IUUpdateSwitch(&OSNAlignSP, states, names, n) < 0)
1721  return false;
1722 
1723  index = IUFindOnSwitchIndex(&OSNAlignSP);
1724  //NewGeometricAlignment
1725  //End NewGeometricAlignment
1726  OSNAlignSP.s = IPS_BUSY;
1727  if (index == 0)
1728  {
1729 
1730  /* Index is 0-8 and represents index+1*/
1731 
1732  int index_stars = IUFindOnSwitchIndex(&OSNAlignStarsSP);
1733  if ((index_stars <= 8) && (index_stars >= 0))
1734  {
1735  int stars = index_stars + 1;
1736  OSNAlignS[0].s = ISS_OFF;
1737  LOGF_INFO("Align index: %d, stars: %d", index_stars, stars);
1738  AlignStartGeometric(stars);
1739  }
1740  }
1741  if (index == 1)
1742  {
1743  OSNAlignS[1].s = ISS_OFF;
1745  }
1746  //Write to EEPROM moved to new line/variable
1747  IDSetSwitch(&OSNAlignSP, nullptr);
1749  }
1750 
1751  if (!strcmp(name, OSNAlignWriteSP.name))
1752  {
1753  if (IUUpdateSwitch(&OSNAlignWriteSP, states, names, n) < 0)
1754  return false;
1755 
1757 
1759  if (index == 0)
1760  {
1761  OSNAlignWriteS[0].s = ISS_OFF;
1763  }
1764  IDSetSwitch(&OSNAlignWriteSP, nullptr);
1766  }
1767 
1768  if (!strcmp(name, OSNAlignPolarRealignSP.name))
1769  {
1770  char cmd[CMD_MAX_LEN] = {0};
1771  if (IUUpdateSwitch(&OSNAlignPolarRealignSP, states, names, n) < 0)
1772  return false;
1773 
1774 
1776  if (OSNAlignPolarRealignS[0].s == ISS_ON) //INFO
1777  {
1779  LOG_INFO("Step 1: Goto a bright star between 50 and 80 degrees N/S from the pole. Preferably on the Meridian.");
1780  LOG_INFO("Step 2: Make sure it is centered.");
1781  LOG_INFO("Step 3: Press Refine Polar Alignment.");
1782  LOG_INFO("Step 4: Using the mount's Alt and Az screws manually recenter the star. (Video mode if your camera supports it will be helpful.)");
1783  LOG_INFO("Optional: Start a new alignment.");
1786  return true;
1787  }
1788  if (OSNAlignPolarRealignS[1].s == ISS_ON) //Command
1789  {
1791  // int returncode=sendOnStepCommand("
1792  snprintf(cmd, 5, ":MP#");
1794  if (!sendOnStepCommandBlind(":MP#"))
1795  {
1796  IDSetSwitch(&OSNAlignPolarRealignSP, "Command for Refine Polar Alignment successful");
1799  return true;
1800  }
1801  else
1802  {
1803  IDSetSwitch(&OSNAlignPolarRealignSP, "Command for Refine Polar Alignment FAILED");
1806  return false;
1807  }
1808  }
1809  }
1810 
1811 #ifdef ONSTEP_NOTDONE
1812  if (!strcmp(name, OSOutput1SP.name)) //
1813  {
1814  if (OSOutput1S[0].s == ISS_ON)
1815  {
1816  OSDisableOutput(1);
1817  //PECStateS[0].s == ISS_OFF;
1818  }
1819  else if (OSOutput1S[1].s == ISS_ON)
1820  {
1821  OSEnableOutput(1);
1822  //PECStateS[1].s == ISS_OFF;
1823  }
1824  IDSetSwitch(&OSOutput1SP, nullptr);
1825  }
1826  if (!strcmp(name, OSOutput2SP.name)) //
1827  {
1828  if (OSOutput2S[0].s == ISS_ON)
1829  {
1830  OSDisableOutput(2);
1831  //PECStateS[0].s == ISS_OFF;
1832  }
1833  else if (OSOutput2S[1].s == ISS_ON)
1834  {
1835  OSEnableOutput(2);
1836  //PECStateS[1].s == ISS_OFF;
1837  }
1838  IDSetSwitch(&OSOutput2SP, nullptr);
1839  }
1840 #endif
1841 
1842  // Focuser
1843  if (strstr(name, "FOCUS"))
1844  {
1845  return FI::processSwitch(dev, name, states, names, n);
1846  }
1847  // Focuser
1848  if (strstr(name, "ROTATOR"))
1849  {
1850  return RI::processSwitch(dev, name, states, names, n);
1851  }
1852  }
1853 
1854  return LX200Generic::ISNewSwitch(dev, name, states, names, n);
1855 }
1856 
1858 {
1859  // process parent
1861 
1862  if (!isSimulation())
1863  {
1864  char buffer[128] = {0};
1866  IUSaveText(&VersionT[0], buffer);
1868  IUSaveText(&VersionT[1], buffer);
1870  IUSaveText(&VersionT[2], buffer);
1872  IUSaveText(&VersionT[3], buffer);
1873 
1874  IDSetText(&VersionTP, nullptr);
1875  if ((VersionT[2].text[0] == '1' || VersionT[2].text[0] == '2') && (VersionT[2].text[1] == '.' )
1876  && (strcmp(VersionT[3].text, "OnStep") || strcmp(VersionT[3].text, "On-Step")))
1877  {
1878  LOG_INFO("Old OnStep (V1/V2 depreciated) detected, setting some defaults");
1879  LOG_INFO("Note: Everything should work, but it may have timeouts in places, as it's not tested against.");
1880  OSHighPrecision = false;
1882  }
1883  else if (VersionT[2].text[0] == '3' && (strcmp(VersionT[3].text, "OnStep") || strcmp(VersionT[3].text, "On-Step")))
1884  {
1885  LOG_INFO("V3 OnStep detected, setting some defaults");
1886  OSHighPrecision = false;
1888  }
1889  else if (VersionT[2].text[0] == '4' && (strcmp(VersionT[3].text, "OnStep") || strcmp(VersionT[3].text, "On-Step")))
1890  {
1891  LOG_INFO("V4 OnStep detected, setting some defaults");
1892  OSHighPrecision = true;
1894  }
1895  else if (VersionT[2].text[0] == '5' && (strcmp(VersionT[3].text, "OnStep") || strcmp(VersionT[3].text, "On-Step")))
1896  {
1897  LOG_INFO("V5 OnStep detected, setting some defaults");
1898  OSHighPrecision = true;
1900  }
1901  else if (VersionT[2].text[0] == '1' && VersionT[2].text[1] == '0' && VersionT[2].text[2] == '.'
1902  && (strcmp(VersionT[3].text, "OnStepX") || strcmp(VersionT[3].text, "On-Step")))
1903  {
1904  LOG_INFO("OnStepX detected, setting some defaults");
1905  OSHighPrecision = true;
1907  }
1908  else
1909  {
1910  LOG_INFO("OnStep/OnStepX version could not be detected");
1911  OSHighPrecision = false;
1913  }
1914 
1915  if (InitPark())
1916  {
1917  // If loading parking data is successful, we just set the default parking values.
1918  LOG_INFO("=============== Parkdata loaded");
1919  }
1920  else
1921  {
1922  // Otherwise, we set all parking data to default in case no parking data is found.
1923  LOG_INFO("=============== Parkdata Load Failed");
1924  }
1925  }
1926 }
1927 
1928 //======================== Parking =======================
1930 {
1931  char response[RB_MAX_LEN];
1932  // 0 = failure, 1 = success
1933  int error_or_fail = getCommandSingleCharResponse(PortFD, response, ":hQ#");
1934  if(error_or_fail != 1 || response[0] != '1')
1935  {
1936  LOGF_WARN("===CMD==> Set Park Pos %s", response);
1937  return false;
1938  }
1941  LOG_WARN("Park Value set to current position");
1942  return true;
1943 }
1944 
1946 {
1947  IDMessage(getDeviceName(), "Setting Park Data to Default.");
1948  SetAxis1Park(20);
1949  SetAxis2Park(80);
1950  LOG_WARN("Park Position set to Default value, 20/80");
1951  return true;
1952 }
1953 
1955 {
1956  char response[RB_MAX_LEN];
1957 
1958 
1959  if (!isSimulation())
1960  {
1961  int failure_or_error = getCommandSingleCharResponse(PortFD, response, ":hR#"); //0 = failure, 1 = success, no # on reply
1962  if ((response[0] != '1') || (failure_or_error < 0))
1963  {
1964  return false;
1965  }
1966  }
1967  return true;
1968 }
1969 
1971 {
1972  if (!isSimulation())
1973  {
1974  // If scope is moving, let's stop it first.
1975  if (EqNP.s == IPS_BUSY)
1976  {
1977  if (!isSimulation() && abortSlew(PortFD) < 0)
1978  {
1979  Telescope::AbortSP.s = IPS_ALERT;
1980  IDSetSwitch(&(Telescope::AbortSP), "Abort slew failed.");
1981  return false;
1982  }
1983  Telescope::AbortSP.s = IPS_OK;
1984  EqNP.s = IPS_IDLE;
1985  IDSetSwitch(&(Telescope::AbortSP), "Slew aborted.");
1986  IDSetNumber(&EqNP, nullptr);
1987 
1989  {
1992  EqNP.s = IPS_IDLE;
1995 
1996  IDSetSwitch(&MovementNSSP, nullptr);
1997  IDSetSwitch(&MovementWESP, nullptr);
1998  }
1999  }
2000  if (!isSimulation() && slewToPark(PortFD) < 0)
2001  {
2002  ParkSP.s = IPS_ALERT;
2003  IDSetSwitch(&ParkSP, "Parking Failed.");
2004  return false;
2005  }
2006  }
2007  ParkSP.s = IPS_BUSY;
2008  return true;
2009 }
2010 
2011 // Periodically Polls OnStep Parameter from controller
2013 {
2014  char OSbacklashDEC[RB_MAX_LEN] = {0};
2015  char OSbacklashRA[RB_MAX_LEN] = {0};
2016  char GuideValue[RB_MAX_LEN] = {0};
2017  // int i;
2018  bool pier_not_set = true; // Avoid a call to :Gm if :GU it
2019  Errors Lasterror = ERR_NONE;
2020 
2021 
2022  if (isSimulation()) //if Simulation is selected
2023  {
2024  mountSim();
2025  return true;
2026  }
2027 
2028  tcflush(PortFD, TCIOFLUSH);
2029  flushIO(PortFD);
2030 
2031 
2032 #ifdef OnStep_Alpha
2033  OSSupports_bitfield_Gu = try_bitfield_Gu();
2034 
2036  {
2037  //Fall back to :GU parsing
2038 #endif
2039 
2040  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, OSStat, ":GU#"); // :GU# returns a string containg controller status
2041  if (error_or_fail > 1) // check if successful read (strcmp(OSStat, OldOSStat) != 0) //if status changed
2042  { //If this fails, simply return;
2043  char check_GU_valid1[RB_MAX_LEN] ={0};
2044  char check_GU_valid2[RB_MAX_LEN] ={0};
2045  char check_GU_valid3[RB_MAX_LEN] ={0};
2046  //:GU should always have one of pIPF and 3 numbers
2047  if (sscanf(OSStat, "%s%[pIPF]%s%[0-9]%[0-9]%[0-9]", check_GU_valid1,&check_GU_valid2[0],check_GU_valid3, &check_GU_valid2[1],&check_GU_valid2[2],&check_GU_valid2[3]) != 1)
2048  {
2049  LOG_WARN(":GU# returned something that can not be right, this update aborted, will try again...");
2050  LOGF_DEBUG("Parameters matched: %u from %s",sscanf(OSStat, "%s%[pIPF]%s%[0-9]%[0-9]%[0-9]", check_GU_valid1,&check_GU_valid2[0],check_GU_valid3, &check_GU_valid2[1],&check_GU_valid2[2],&check_GU_valid2[3]), OSStat);
2051  flushIO(PortFD);
2052  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2053  }
2054  if (getLX200RA(PortFD, &currentRA) < 0 || getLX200DEC(PortFD, &currentDEC) < 0) // Update actual position
2055  {
2056  EqNP.s = IPS_ALERT;
2057  IDSetNumber(&EqNP, "Error reading RA/DEC.");
2058  LOG_INFO("RA/DEC could not be read, possible solution if using (wireless) ethernet: Use port 9998");
2059  LOG_WARN("This update aborted, will try again...");
2060  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2061  }
2062  strncpy(OldOSStat, OSStat, sizeof(OldOSStat));
2063 
2065 
2066  // ============= Parkstatus
2067 
2068 #ifdef DEBUG_TRACKSTATE
2069  LOG_DEBUG("Prior TrackState:");
2070  PrintTrackState();
2071  LOG_DEBUG("^ Prior");
2072 #endif
2073  //TelescopeStatus PriorTrackState = TrackState;
2074  // not [p]arked, parking [I]n-progress, [P]arked, Park [F]ailed
2075  // "P" (Parked moved to Telescope Status, since it would override any other TrackState
2076  // Other than parked, none of these affect TrackState
2077  if (strstr(OSStat, "F"))
2078  {
2079  IUSaveText(&OnstepStat[3], "Parking Failed");
2080  }
2081  if (strstr(OSStat, "I"))
2082  {
2083  IUSaveText(&OnstepStat[3], "Park in Progress");
2084  }
2085  if (strstr(OSStat, "p"))
2086  {
2087  IUSaveText(&OnstepStat[3], "UnParked");
2088  }
2089  // ============= End Parkstatus
2090 
2091 
2092 
2093 
2094  // ============= Telescope Status
2095 
2096  if (strstr(OSStat, "P"))
2097  {
2099  IUSaveText(&OnstepStat[3], "Parked");
2100  IUSaveText(&OnstepStat[1], "Parked");
2101  if (!isParked()) //Don't call this every time OSStat changes
2102  {
2103  SetParked(true);
2104  }
2105  PrintTrackState();
2106  }
2107  else
2108  {
2109  if (strstr(OSStat, "n") && strstr(OSStat, "N"))
2110  {
2111  IUSaveText(&OnstepStat[1], "Idle");
2113  }
2114  if (strstr(OSStat, "n") && !strstr(OSStat, "N"))
2115  {
2116  if (strstr(OSStat, "I"))
2117  {
2118  IUSaveText(&OnstepStat[1], "Parking/Slewing");
2120  }
2121  else
2122  {
2123  IUSaveText(&OnstepStat[1], "Slewing");
2125  }
2126  }
2127  if (strstr(OSStat, "N") && !strstr(OSStat, "n"))
2128  {
2129  IUSaveText(&OnstepStat[1], "Tracking");
2131  }
2132  if (!strstr(OSStat, "N") && !strstr(OSStat, "n"))
2133  {
2134  IUSaveText(&OnstepStat[1], "Slewing");
2136  }
2137  PrintTrackState();
2138  if (isParked()) //IMPORTANT: SET AFTER setting TrackState!
2139  {
2140  SetParked(false);
2141  }
2142  PrintTrackState();
2143  }
2144  // Set TrackStateSP based on above, but only change if needed.
2145  // NOTE: Technically during a slew it can have tracking on, but elsewhere there is the assumption:
2146  // Slewing = Not tracking
2147 #ifdef DEBUG_TRACKSTATE
2148  LOG_DEBUG("BEFORE UPDATE");
2149  if (EqNP.s == IPS_BUSY)
2150  {
2151  LOG_DEBUG("EqNP is IPS_BUSY (Goto/slew or Parking)");
2152  }
2153  if (EqNP.s == IPS_OK)
2154  {
2155  LOG_DEBUG("EqNP is IPS_OK (Tracking)");
2156  }
2157  if (EqNP.s == IPS_IDLE)
2158  {
2159  LOG_DEBUG("EqNP is IPS_IDLE (Not Tracking or Parked)");
2160  }
2161  if (EqNP.s == IPS_ALERT)
2162  {
2163  LOG_DEBUG("EqNP is IPS_ALERT (Something wrong)");
2164  }
2165  LOG_DEBUG("/BEFORE UPDATE");
2166 #endif
2167  // Fewer updates might help with KStars handling.
2168  bool trackStateUpdateNeded = false;
2169  if (TrackState == SCOPE_TRACKING)
2170  {
2171  if (TrackStateSP.s != IPS_BUSY)
2172  {
2174  trackStateUpdateNeded = true;
2175  }
2177  {
2180  trackStateUpdateNeded = true;
2181  }
2182  }
2183  else
2184  {
2185  if (TrackStateSP.s != IPS_IDLE)
2186  {
2188  trackStateUpdateNeded = true;
2189  }
2191  {
2194  trackStateUpdateNeded = true;
2195  }
2196  }
2197  if (trackStateUpdateNeded)
2198  {
2199 #ifdef DEBUG_TRACKSTATE
2200  LOG_DEBUG("TRACKSTATE CHANGED");
2201 #endif
2202  IDSetSwitch(&TrackStateSP, nullptr);
2203  }
2204  else
2205  {
2206 #ifdef DEBUG_TRACKSTATE
2207  LOG_DEBUG("TRACKSTATE UNCHANGED");
2208 #endif
2209  }
2210  //TrackState should be set correctly, only update EqNP if actually needed.
2211  bool update_needed = false;
2212  switch (TrackState)
2213  {
2214  case SCOPE_PARKED:
2215  case SCOPE_IDLE:
2216  if (EqNP.s != IPS_IDLE)
2217  {
2218  EqNP.s = IPS_IDLE;
2219  update_needed = true;
2220 #ifdef DEBUG_TRACKSTATE
2221  LOG_DEBUG("EqNP set to IPS_IDLE");
2222 #endif
2223  }
2224  break;
2225 
2226  case SCOPE_SLEWING:
2227  case SCOPE_PARKING:
2228  if (EqNP.s != IPS_BUSY)
2229  {
2230  EqNP.s = IPS_BUSY;
2231  update_needed = true;
2232 #ifdef DEBUG_TRACKSTATE
2233  LOG_DEBUG("EqNP set to IPS_BUSY");
2234 #endif
2235  }
2236  break;
2237 
2238  case SCOPE_TRACKING:
2239  if (EqNP.s != IPS_OK)
2240  {
2241  EqNP.s = IPS_OK;
2242  update_needed = true;
2243 #ifdef DEBUG_TRACKSTATE
2244  LOG_DEBUG("EqNP set to IPS_OK");
2245 #endif
2246  }
2247  break;
2248  }
2249  if (EqN[AXIS_RA].value != currentRA || EqN[AXIS_DE].value != currentDEC)
2250  {
2251 #ifdef DEBUG_TRACKSTATE
2252  LOG_DEBUG("EqNP coordinates updated");
2253 #endif
2254  update_needed = true;
2255  }
2256  if (update_needed)
2257  {
2258 #ifdef DEBUG_TRACKSTATE
2259  LOG_DEBUG("EqNP changed state");
2260 #endif
2261  EqN[AXIS_RA].value = currentRA;
2262  EqN[AXIS_DE].value = currentDEC;
2263  IDSetNumber(&EqNP, nullptr);
2264 #ifdef DEBUG_TRACKSTATE
2265  if (EqNP.s == IPS_BUSY)
2266  {
2267  LOG_DEBUG("EqNP is IPS_BUSY (Goto/slew or Parking)");
2268  }
2269  if (EqNP.s == IPS_OK)
2270  {
2271  LOG_DEBUG("EqNP is IPS_OK (Tracking)");
2272  }
2273  if (EqNP.s == IPS_IDLE)
2274  {
2275  LOG_DEBUG("EqNP is IPS_IDLE (Not Tracking or Parked)");
2276  }
2277  if (EqNP.s == IPS_ALERT)
2278  {
2279  LOG_DEBUG("EqNP is IPS_ALERT (Something wrong)");
2280  }
2281 #endif
2282  }
2283  else
2284  {
2285 #ifdef DEBUG_TRACKSTATE
2286  LOG_DEBUG("EqNP UNCHANGED");
2287 #endif
2288  }
2289  PrintTrackState();
2290 
2291  // ============= End Telescope Status
2292 
2293  // ============= Refractoring
2294  if ((strstr(OSStat, "r") || strstr(OSStat, "t"))) //On, either refractory only (r) or full (t)
2295  {
2296  if (strstr(OSStat, "t"))
2297  {
2298  IUSaveText(&OnstepStat[2], "Full Comp");
2299  }
2300  if (strstr(OSStat, "r"))
2301  {
2302  IUSaveText(&OnstepStat[2], "Refractory Comp");
2303  }
2304  if (strstr(OSStat, "s"))
2305  {
2306  IUSaveText(&OnstepStat[8], "Single Axis");
2307  }
2308  else
2309  {
2310  IUSaveText(&OnstepStat[8], "2-Axis");
2311  }
2312  }
2313  else
2314  {
2315  IUSaveText(&OnstepStat[2], "Refractoring Off");
2316  IUSaveText(&OnstepStat[8], "N/A");
2317  }
2318 
2319  //if (strstr(OSStat,"H")) { IUSaveText(&OnstepStat[3],"At Home"); }
2320  if (strstr(OSStat, "H") && strstr(OSStat, "P"))
2321  {
2322  IUSaveText(&OnstepStat[3], "At Home and Parked");
2323  }
2324  if (strstr(OSStat, "H") && strstr(OSStat, "p"))
2325  {
2326  IUSaveText(&OnstepStat[3], "At Home and UnParked");
2327  }
2328  //AutoPauseAtHome
2329  if (strstr(OSStat, "u")) // pa[u]se at home enabled?
2330  {
2331  HomePauseS[1].s = ISS_ON;
2332  HomePauseSP.s = IPS_OK;
2333  IDSetSwitch(&HomePauseSP, "Pause at Home Enabled");
2334  }
2335  else
2336  {
2337  HomePauseS[0].s = ISS_ON;
2338  HomePauseSP.s = IPS_OK;
2339  IDSetSwitch(&HomePauseSP, nullptr);
2340  }
2341 
2342  if (strstr(OSStat, "w"))
2343  {
2344  IUSaveText(&OnstepStat[3], "Waiting at Home");
2345  }
2346 
2347  // ============= Pec Status
2348  if ((!strstr(OSStat, "R") && !strstr(OSStat, "W")))
2349  {
2350  IUSaveText(&OnstepStat[4], "N/A");
2351  }
2352  if (strstr(OSStat, "R"))
2353  {
2354  IUSaveText(&OnstepStat[4], "Recorded");
2355  }
2356  if (strstr(OSStat, "W"))
2357  {
2358  IUSaveText(&OnstepStat[4], "Autorecord");
2359  }
2360 
2361  //Handles pec with :GU, also disables the (old) :$QZ?# command
2362  if (strstr(OSStat, "/"))
2363  {
2364  IUSaveText(&OnstepStat[4], "Ignored");
2365  OSPECviaGU = true;
2367  OSPECStatusS[0].s = ISS_ON;
2369  }
2370  if (strstr(OSStat, ";"))
2371  {
2372 
2373  IUSaveText(&OnstepStat[4], "AutoRecord (waiting on index)");
2374  OSPECviaGU = true;
2376  OSPECStatusS[4].s = ISS_ON ;
2378  }
2379  if (strstr(OSStat, ","))
2380  {
2381  IUSaveText(&OnstepStat[4], "AutoPlaying (waiting on index)");
2382  OSPECviaGU = true;
2384  OSPECStatusS[3].s = ISS_ON ;
2386  }
2387  if (strstr(OSStat, "~"))
2388  {
2389  IUSaveText(&OnstepStat[4], "Playing");
2390  OSPECviaGU = true;
2392  OSPECStatusS[1].s = ISS_ON ;
2394  }
2395  if (strstr(OSStat, "^"))
2396  {
2397  IUSaveText(&OnstepStat[4], "Recording");
2398  OSPECviaGU = true;
2400  OSPECStatusS[2].s = ISS_ON ;
2402  }
2403  if (OSPECviaGU)
2404  {
2406  {
2407  //We have PEC reported via :GU already, enable if any are detected, as they are not reported with ALTAZ/FORK_ALT)
2408  //NOTE: Might want to drop the && !strstr(OSStat, "/") as it will startup that way.
2409  uint32_t capabilities = GetTelescopeCapability();
2410  if ((capabilities | TELESCOPE_HAS_PEC) != capabilities)
2411  {
2412  LOG_INFO("Telescope detected having PEC, setting that capability");
2413  LOGF_DEBUG("capabilites = %x", capabilities);
2414  capabilities |= TELESCOPE_HAS_PEC;
2415  SetTelescopeCapability(capabilities, 10 );
2417  }
2418  }
2419  IDSetSwitch(&OSPECStatusSP, nullptr);
2420  IDSetSwitch(&OSPECRecordSP, nullptr);
2421  IDSetSwitch(&OSPECIndexSP, nullptr);
2422  }
2423 
2424 
2425 
2426  // ============= Time Sync Status
2427  if (!strstr(OSStat, "S"))
2428  {
2429  IUSaveText(&OnstepStat[5], "N/A");
2430  }
2431  if (strstr(OSStat, "S"))
2432  {
2433  IUSaveText(&OnstepStat[5], "PPS / GPS Sync Ok");
2434  }
2435 
2436  // ============= Mount Types
2437  if (strstr(OSStat, "E"))
2438  {
2439  IUSaveText(&OnstepStat[6], "German Equatorial Mount");
2441  }
2442  if (strstr(OSStat, "K"))
2443  {
2444  IUSaveText(&OnstepStat[6], "Fork Mount");
2446  }
2447  if (strstr(OSStat, "k"))
2448  {
2449  //NOTE: This seems to have been removed from OnStep, so the chances of encountering it are small, I can't even find, but I think it was Alt-Az mounting of a FORK, now folded into ALTAZ
2450  IUSaveText(&OnstepStat[6], "Fork Alt Mount");
2452  }
2453  if (strstr(OSStat, "A"))
2454  {
2455  IUSaveText(&OnstepStat[6], "AltAZ Mount");
2457  }
2458 
2459 
2460  //Pier side:
2461  // o - nOne
2462  // T - easT
2463  // W - West
2465  {
2466  uint32_t capabilities = GetTelescopeCapability();
2467  if ((capabilities | TELESCOPE_HAS_PIER_SIDE) != capabilities)
2468  {
2469  LOG_INFO("Telescope detected having Pier Side, adding that capability (many messages duplicated)");
2470  LOGF_DEBUG("capabilites = %x", capabilities);
2471  capabilities |= TELESCOPE_HAS_PIER_SIDE;
2472  SetTelescopeCapability(capabilities, 10 );
2474  }
2475  if (strstr(OSStat, "o"))
2476  {
2477  setPierSide(
2478  PIER_UNKNOWN); //Closest match to None, For forks may trigger an extra goto, during imaging if it would do a meridian flip
2479  pier_not_set = false;
2480  }
2481  if (strstr(OSStat, "T"))
2482  {
2484  pier_not_set = false;
2485  }
2486  if (strstr(OSStat, "W"))
2487  {
2489  pier_not_set = false;
2490  }
2491  }
2492 
2493  // ============= Error Code
2494  //From OnStep: ERR_NONE, ERR_MOTOR_FAULT, ERR_ALT_MIN, ERR_LIMIT_SENSE, ERR_DEC, ERR_AZM, ERR_UNDER_POLE, ERR_MERIDIAN, ERR_SYNC, ERR_PARK, ERR_GOTO_SYNC, ERR_UNSPECIFIED, ERR_ALT_MAX, ERR_GOTO_ERR_NONE, ERR_GOTO_ERR_BELOW_HORIZON, ERR_GOTO_ERR_ABOVE_OVERHEAD, ERR_GOTO_ERR_STANDBY, ERR_GOTO_ERR_PARK, ERR_GOTO_ERR_GOTO, ERR_GOTO_ERR_OUTSIDE_LIMITS, ERR_GOTO_ERR_HARDWARE_FAULT, ERR_GOTO_ERR_IN_MOTION, ERR_GOTO_ERR_UNSPECIFIED
2495 
2496  //For redoing this, quick python, if needed (will only give the error name):
2497  //all_errors=' (Insert)
2498  //split_errors=all_errors.split(', ')
2499  //for specific in split_errors:
2500  // print('case ' +specific+':')
2501  // print('\tIUSaveText(&OnstepStat[7],"'+specific+'");')
2502  // print('\tbreak;')
2503 
2504  Lasterror = (Errors)(OSStat[strlen(OSStat) - 1] - '0');
2505  } else {
2506  return false;
2507  }
2508 
2509 #ifdef OnStep_Alpha // For the moment, for :Gu
2510  }
2511  else
2512  {
2513  //TODO: Check and recode :Gu# paths
2514  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, OSStat, ":Gu#"); // :Gu# returns a string containg controller status that's bitpacked
2515  if (strcmp(OSStat, OldOSStat) != 0) //if status changed
2516  {
2517  //Ignored for now.
2518  }
2519  //Byte 0: Current Status
2520  if (OSStat[0] & 0b10000001 == 0b10000001)
2521  {
2522  //Not tracking
2523  }
2524  if (OSStat[0] & 0b10000010 == 0b10000010)
2525  {
2526  //No goto
2527  }
2528  if (OSStat[0] & 0b10000100 == 0b10000100)
2529  {
2530  // PPS sync
2531  IUSaveText(&OnstepStat[5], "PPS / GPS Sync Ok");
2532  }
2533  else
2534  {
2535  IUSaveText(&OnstepStat[5], "N/A");
2536  }
2537  if (OSStat[0] & 0b10001000 == 0b10001000)
2538  {
2539  // Guide active
2540  }
2541  //Refraction and Number of axis handled differently for now, might combine to one variable.
2542  // reply[0]|=0b11010000; // Refr enabled Single axis
2543  // reply[0]|=0b10010000; // Refr enabled
2544  // reply[0]|=0b11100000; // OnTrack enabled Single axis
2545  // reply[0]|=0b10100000; // OnTrack enabled
2546  if ((OSStat[0] & 0b10010000 == 0b10010000
2547  || OSStat[0] & 0b10100000 == 0b10100000)) //On, either refractory only (r) or full (t)
2548  {
2549  if (OSStat[0] & 0b10100000 == 0b10100000)
2550  {
2551  IUSaveText(&OnstepStat[2], "Full Comp");
2552  }
2553  if (OSStat[0] & 0b10010000 == 0b10010000)
2554  {
2555  IUSaveText(&OnstepStat[2], "Refractory Comp");
2556  }
2557  if (OSStat[0] & 0b11000000 == 0b11000000)
2558  {
2559  IUSaveText(&OnstepStat[8], "Single Axis");
2560  }
2561  else
2562  {
2563  IUSaveText(&OnstepStat[8], "2-Axis");
2564  }
2565  }
2566  else
2567  {
2568  IUSaveText(&OnstepStat[2], "Refractoring Off");
2569  IUSaveText(&OnstepStat[8], "N/A");
2570  }
2571  //Byte 1: Standard tracking rates
2572  if (OSStat[1] & 0b10000001 == 0b10000001)
2573  {
2574  // Lunar rate selected
2575  }
2576  if (OSStat[1] & 0b10000010 == 0b10000010)
2577  {
2578  // Solar rate selected
2579  }
2580  if (OSStat[1] & 0b10000011 == 0b10000011)
2581  {
2582  // King rate selected
2583  }
2584  //Byte 2: Flags
2585  if (OSStat[2] & 0b10000001 == 0b10000001)
2586  {
2587  // At home
2588  }
2589  if (OSStat[2] & 0b10000010 == 0b10000010)
2590  {
2591  // Waiting at home
2592  IUSaveText(&OnstepStat[3], "Waiting at Home");
2593  }
2594  if (OSStat[2] & 0b10000100 == 0b10000100)
2595  {
2596  // Pause at home enabled?
2597  //AutoPauseAtHome
2598  HomePauseS[1].s = ISS_ON;
2599  HomePauseSP.s = IPS_OK;
2600  IDSetSwitch(&HomePauseSP, "Pause at Home Enabled");
2601  }
2602  else
2603  {
2604  HomePauseS[0].s = ISS_ON;
2605  HomePauseSP.s = IPS_OK;
2606  IDSetSwitch(&HomePauseSP, nullptr);
2607  }
2608  if (OSStat[2] & 0b10001000 == 0b10001000)
2609  {
2610  // Buzzer enabled?
2611  }
2612  if (OSStat[2] & 0b10010000 == 0b10010000)
2613  {
2614  // Auto meridian flip
2615  AutoFlipS[1].s = ISS_ON;
2616  AutoFlipSP.s = IPS_OK;
2617  IDSetSwitch(&AutoFlipSP, nullptr);
2618  }
2619  else
2620  {
2621  AutoFlipS[0].s = ISS_ON;
2622  AutoFlipSP.s = IPS_OK;
2623  IDSetSwitch(&AutoFlipSP, nullptr);
2624  }
2625  if (OSStat[2] & 0b10100000 == 0b10100000)
2626  {
2627  // PEC data has been recorded
2628  }
2629 
2630 
2631 
2632 
2633  //Byte 3: Mount type and info
2634  if (OSStat[3] & 0b10000001 == 0b10000001)
2635  {
2636  // GEM
2637  IUSaveText(&OnstepStat[6], "German Mount");
2639  }
2640  if (OSStat[3] & 0b10000010 == 0b10000010)
2641  {
2642  // FORK
2643  IUSaveText(&OnstepStat[6], "Fork Mount");
2645  }
2646  if (OSStat[3] & 0b10000100 == 0b10000100) //Seems depreciated/subsumed into FORK
2647  {
2648  // Fork Alt
2649  IUSaveText(&OnstepStat[6], "Fork Alt Mount");
2651  }
2652  if (OSStat[3] & 0b10001000 == 0b10001000)
2653  {
2654  // ALTAZM
2655  IUSaveText(&OnstepStat[6], "AltAZ Mount");
2657  }
2658 
2659 
2661  if (OSStat[3] & 0b10010000 == 0b10010000)
2662  {
2663  // Pier side none
2665  // INDI doesn't account for 'None'
2666  }
2667  if (OSStat[3] & 0b10100000 == 0b10100000)
2668  {
2669  // Pier side east
2671  }
2672  if (OSStat[3] & 0b11000000 == 0b11000000)
2673  {
2674  // Pier side west
2676  }
2677  // Byte 4: PEC
2678  PECStatusGU = OSStat[4] & 0b01111111;
2679  if (OSStat[4] == 0)
2680  {
2681  // AltAZM, no PEC possible
2682  PECStatusGU = 0;
2683  }
2684  else
2685  {
2686  // PEC status: 0 ignore, 1 get ready to play, 2 playing, 3 get ready to record, 4 recording
2687 
2688 
2689  }
2690  ParkStatusGU = OSStat[5] & 0b01111111;
2691  PulseGuideGU = OSStat[6] & 0b01111111;
2692  GuideRateGU = OSStat[7] & 0b01111111;
2693  LastError = OSStat[8] & 0b01111111;
2694  }
2695 #endif
2696 
2697 
2698  switch (Lasterror)
2699  {
2700  case ERR_NONE:
2701  IUSaveText(&OnstepStat[7], "None");
2702  break;
2703  case ERR_MOTOR_FAULT:
2704  IUSaveText(&OnstepStat[7], "Motor/Driver Fault");
2705  break;
2706  case ERR_ALT_MIN:
2707  IUSaveText(&OnstepStat[7], "Below Horizon Limit");
2708  break;
2709  case ERR_LIMIT_SENSE:
2710  IUSaveText(&OnstepStat[7], "Limit Sense");
2711  break;
2712  case ERR_DEC:
2713  IUSaveText(&OnstepStat[7], "Dec Limit Exceeded");
2714  break;
2715  case ERR_AZM:
2716  IUSaveText(&OnstepStat[7], "Azm Limit Exceeded");
2717  break;
2718  case ERR_UNDER_POLE:
2719  IUSaveText(&OnstepStat[7], "Under Pole Limit Exceeded");
2720  break;
2721  case ERR_MERIDIAN:
2722  IUSaveText(&OnstepStat[7], "Meridian Limit (W) Exceeded");
2723  break;
2724  case ERR_SYNC:
2725  IUSaveText(&OnstepStat[7], "Sync Safety Limit Exceeded");
2726  break;
2727  case ERR_PARK:
2728  IUSaveText(&OnstepStat[7], "Park Failed");
2729  break;
2730  case ERR_GOTO_SYNC:
2731  IUSaveText(&OnstepStat[7], "Goto Sync Failed");
2732  break;
2733  case ERR_UNSPECIFIED:
2734  IUSaveText(&OnstepStat[7], "Unspecified Error");
2735  break;
2736  case ERR_ALT_MAX:
2737  IUSaveText(&OnstepStat[7], "Above Overhead Limit");
2738  break;
2739  case ERR_GOTO_ERR_NONE:
2740  IUSaveText(&OnstepStat[7], "Goto No Error");
2741  break;
2743  IUSaveText(&OnstepStat[7], "Goto Below Horizon");
2744  break;
2746  IUSaveText(&OnstepStat[7], "Goto Abv Overhead");
2747  break;
2748  case ERR_GOTO_ERR_STANDBY:
2749  IUSaveText(&OnstepStat[7], "Goto Err Standby");
2750  break;
2751  case ERR_GOTO_ERR_PARK:
2752  IUSaveText(&OnstepStat[7], "Goto Err Park");
2753  break;
2754  case ERR_GOTO_ERR_GOTO:
2755  IUSaveText(&OnstepStat[7], "Goto Err Goto");
2756  break;
2758  IUSaveText(&OnstepStat[7], "Goto Outside Limits");
2759  break;
2761  IUSaveText(&OnstepStat[7], "Goto H/W Fault");
2762  break;
2764  IUSaveText(&OnstepStat[7], "Goto Err Motion");
2765  break;
2767  IUSaveText(&OnstepStat[7], "Goto Unspecified Error");
2768  break;
2769  default:
2770  IUSaveText(&OnstepStat[7], "Unknown Error");
2771  break;
2772  }
2773 
2774 #ifndef OnStep_Alpha
2775  // Get actual Pier Side
2776  if (pier_not_set)
2777  {
2779  {
2781  }
2782  else
2783  {
2784  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, OSPier, ":Gm#");
2785  if (error_or_fail > 1) {
2786  if (strcmp(OSPier, OldOSPier) != 0) // any change ?
2787  {
2788  strncpy(OldOSPier, OSPier, sizeof(OldOSPier));
2789  switch(OSPier[0])
2790  {
2791  case 'E':
2793  break;
2794  case 'W':
2796  break;
2797  case 'N':
2799  break;
2800  case '?':
2802  break;
2803  }
2804  }
2805  } else {
2806  LOG_WARN("Communication error on Pier Side (:Gm#), this update aborted, will try again...");
2807  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2808  }
2809  }
2810  }
2811 #endif
2812 
2813  //========== Get actual Backlash values
2814  double backlash_DEC, backlash_RA;
2815  int BD_error = getCommandDoubleResponse(PortFD, &backlash_DEC, OSbacklashDEC, ":%BD#");
2816  int BR_error = getCommandDoubleResponse(PortFD, &backlash_RA, OSbacklashRA, ":%BR#");
2817  if (BD_error > 1 && BR_error > 1) {
2818  BacklashNP.np[0].value = backlash_DEC;
2819  BacklashNP.np[1].value = backlash_RA;
2820  IDSetNumber(&BacklashNP, nullptr);
2821  } else {
2822  LOG_WARN("Communication error on backlash (:%BD#/:%BR#), this update aborted, will try again...");
2823  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2824  }
2825 
2826  double pulseguiderate = 0.0;
2827  if (getCommandDoubleResponse(PortFD, &pulseguiderate, GuideValue, ":GX90#") > 1)
2828  {
2829  LOGF_DEBUG("Guide Rate String: %s", GuideValue);
2830  pulseguiderate = atof(GuideValue);
2831  LOGF_DEBUG("Guide Rate: %f", pulseguiderate);
2832  GuideRateNP.np[0].value = pulseguiderate;
2833  GuideRateNP.np[1].value = pulseguiderate;
2834  IDSetNumber(&GuideRateNP, nullptr);
2835  }
2836  else
2837  {
2838  LOGF_DEBUG("Guide Rate String: %s", GuideValue);
2839  LOG_DEBUG("Guide rate error response, Not setting guide rate from :GX90# response, falling back to :GU#, which may not be accurate, if custom settings are used");
2840  int pulseguiderateint = (Errors)(OSStat[strlen(OSStat) - 3] - '0');
2841  switch(pulseguiderateint)
2842  {
2843  case 0:
2844  pulseguiderate = (double)0.25;
2845  break;
2846  case 1:
2847  pulseguiderate = (double)0.5;
2848  break;
2849  case 2:
2850  pulseguiderate = (double)1.0;
2851  break;
2852  default:
2853  pulseguiderate = 0.0;
2854  LOG_DEBUG("Could not get guide rate from :GU# response, not setting");
2855  LOG_WARN("Communication error on Guide Rate (:GX90#/:GU#), this update aborted, will try again...");
2856  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2857  }
2858  if (pulseguiderate != 0.0)
2859  {
2860  LOGF_DEBUG("Guide Rate: %f", pulseguiderate);
2861  GuideRateNP.np[0].value = pulseguiderate;
2862  GuideRateNP.np[1].value = pulseguiderate;
2863  IDSetNumber(&GuideRateNP, nullptr);
2864  }
2865  }
2866 
2867 #ifndef OnStep_Alpha
2868  if (OSMountType == MOUNTTYPE_GEM)
2869  {
2870  //AutoFlip
2871  char merdidianflipauto_response[RB_MAX_LEN] = {0};
2872  int gx95_error = getCommandSingleCharErrorOrLongResponse(PortFD, merdidianflipauto_response, ":GX95#");
2873  if (gx95_error > 1)
2874  {
2875  if (merdidianflipauto_response[0] == '1' && merdidianflipauto_response[1] == 0) //Only set on 1#
2876  {
2877  AutoFlipS[1].s = ISS_ON;
2878  AutoFlipSP.s = IPS_OK;
2879  IDSetSwitch(&AutoFlipSP, nullptr);
2880  }
2881  else
2882  {
2883  AutoFlipS[0].s = ISS_ON;
2884  AutoFlipSP.s = IPS_OK;
2885  IDSetSwitch(&AutoFlipSP, nullptr);
2886  }
2887  }
2888  else {
2889  LOG_WARN("Communication error on meridianAutoFlip (:GX95#), this update aborted, will try again...");
2890  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2891  }
2892  }
2893 #endif
2894 
2895  if (OSMountType == MOUNTTYPE_GEM) //Doesn't apply to non-GEMs
2896  {
2897  //PreferredPierSide
2898  char preferredpierside_response[RB_MAX_LEN] = {0};
2899  int gx96_error = getCommandSingleCharErrorOrLongResponse(PortFD, preferredpierside_response, ":GX96#");
2900  if (gx96_error > 1)
2901  {
2902  if (strstr(preferredpierside_response, "W"))
2903  {
2904  PreferredPierSideS[0].s = ISS_ON;
2906  IDSetSwitch(&PreferredPierSideSP, nullptr);
2907  }
2908  else if (strstr(preferredpierside_response, "E"))
2909  {
2910  PreferredPierSideS[1].s = ISS_ON;
2912  IDSetSwitch(&PreferredPierSideSP, nullptr);
2913  }
2914  else if (strstr(preferredpierside_response, "B"))
2915  {
2916  PreferredPierSideS[2].s = ISS_ON;
2918  IDSetSwitch(&PreferredPierSideSP, nullptr);
2919  }
2920  else if (strstr(preferredpierside_response, "%"))
2921  {
2922  //NOTE: This bug is only present in very early OnStepX, and should be fixed shortly after 10.03k
2923  LOG_DEBUG(":GX96 returned \% indicating early OnStepX bug");
2926  IDSetSwitch(&PreferredPierSideSP, nullptr);
2927  }
2928  else
2929  {
2932  IDSetSwitch(&PreferredPierSideSP, nullptr);
2933  }
2934  }
2935  else
2936  {
2937  LOG_WARN("Communication error on Preferred Pier Side (:GX96#), this update aborted, will try again...");
2938  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2939  }
2940 
2941  if (OSMountType == MOUNTTYPE_GEM) {
2942  char limit1_response[RB_MAX_LEN] = {0};
2943  int gxea_error, gxe9_error;
2944  double degrees_past_Meridian_East, degrees_past_Meridian_West;
2945  gxe9_error = getCommandDoubleResponse(PortFD, &degrees_past_Meridian_East, limit1_response, ":GXE9#");
2946  if (gxe9_error > 1) //NOTE: Possible failure not checked.
2947  {
2948  char limit2_response[RB_MAX_LEN] = {0};
2949  gxea_error = getCommandDoubleResponse(PortFD, &degrees_past_Meridian_West, limit2_response, ":GXEA#");
2950  if (gxea_error > 1) { //NOTE: Possible failure not checked.
2951  minutesPastMeridianNP.np[0].value = degrees_past_Meridian_East; // E
2952  minutesPastMeridianNP.np[1].value = degrees_past_Meridian_West; //W
2954  } else {
2955  LOG_WARN("Communication error on Degrees past Meridian West (:GXEA#), this update aborted, will try again...");
2956  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2957  }
2958  }
2959  else
2960  {
2961  LOG_WARN("Communication error on Degrees past Meridian East (:GXE9#), this update aborted, will try again...");
2962  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2963  }
2964  }
2965  }
2966 
2967  //TODO: Improve Rotator support
2968  if (OSUpdateRotator() != 0) {
2969  LOG_WARN("Communication error on Rotator Update, this update aborted, will try again...");
2970  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2971  }
2972 
2973  //Weather update
2974  char temperature_response[RB_MAX_LEN] = {0};
2975  double temperature_value;
2976  int gx9a_error = getCommandDoubleResponse(PortFD, &temperature_value, temperature_response, ":GX9A#");
2977  if (gx9a_error > 1) //> 1 as an OnStep error would be 1 char in response
2978  {
2979  setParameterValue("WEATHER_TEMPERATURE", temperature_value);
2980  }
2981  else
2982  {
2983  LOG_WARN("Communication error on Temperature (:GX9A#), this update aborted, will try again...");
2984  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2985  }
2986 
2987  char humidity_response[RB_MAX_LEN] = {0};
2988  double humidity_value;
2989  int gx9c_error = getCommandDoubleResponse(PortFD, &humidity_value, humidity_response, ":GX9C#");
2990  if (gx9c_error > 1) //> 1 as an OnStep error would be 1 char in response
2991  {
2992  setParameterValue("WEATHER_HUMIDITY", humidity_value);
2993  }
2994  else
2995  {
2996  LOG_WARN("Communication error on Humidity (:GX9C#), this update aborted, will try again...");
2997  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
2998  }
2999 
3000 
3001  char barometer_response[RB_MAX_LEN] = {0};
3002  double barometer_value;
3003  int gx9b_error = getCommandDoubleResponse(PortFD, &barometer_value, barometer_response, ":GX9B#");
3004  if (gx9b_error > 1)
3005  {
3006  setParameterValue("WEATHER_BAROMETER", barometer_value);
3007  }
3008  else
3009  {
3010  LOG_WARN("Communication error on Barometer (:GX9B#), this update aborted, will try again...");
3011  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
3012  }
3013 
3014  char dewpoint_reponse[RB_MAX_LEN] = {0};
3015  double dewpoint_value;
3016  int gx9e_error = getCommandDoubleResponse(PortFD, &dewpoint_value, dewpoint_reponse, ":GX9E#");
3017  if (gx9e_error > 1)
3018  {
3019  setParameterValue("WEATHER_DEWPOINT", dewpoint_value);
3020  }
3021  else
3022  {
3023  LOG_WARN("Communication error on Dewpoint (:GX9E#), this update aborted, will try again...");
3024  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
3025  }
3026 
3027  if (OSCpuTemp_good)
3028  {
3029  char cputemp_reponse[RB_MAX_LEN] = {0};
3030  double cputemp_value;
3031  int error_return = getCommandDoubleResponse(PortFD, &cputemp_value, cputemp_reponse, ":GX9F#");
3032  if ( error_return >= 0 && !strcmp(cputemp_reponse, "0") )
3033  {
3034  setParameterValue("WEATHER_CPU_TEMPERATURE", cputemp_value);
3035  }
3036  else
3037  {
3038  LOGF_DEBUG("CPU Temp not responded to, disabling further checks, return values: error_return: %i, cputemp_reponse: %s",
3039  error_return, cputemp_reponse);
3040  OSCpuTemp_good = false;
3041  }
3042  }
3043  //
3044  //Disabled, because this is supplied via Kstars or other location, no sensor to read this
3045  //double altitude_value;
3046  //int error_or_fail = getCommandDoubleResponse(PortFD, &altitude_value, TempValue, ":GX9D#");
3047  //setParameterValue("WEATHER_ALTITUDE", altitude_value);
3049 
3051  IDSetLight(&critialParametersLP, nullptr);
3052  ParametersNP.s = IPS_OK;
3053  IDSetNumber(&ParametersNP, nullptr);
3054 
3055  if (TMCDrivers)
3056  {
3057  for (int driver_number = 1; driver_number < 3; driver_number++)
3058  {
3059  char TMCDriverTempValue[RB_MAX_LEN] = {0};
3060  char TMCDriverCMD[CMD_MAX_LEN] = {0};
3061  snprintf(TMCDriverCMD, sizeof(TMCDriverCMD), ":GXU%i#", driver_number);
3062  if (TMCDrivers) { //Prevent check on :GXU2# if :GXU1# failed
3063  int i = getCommandSingleCharErrorOrLongResponse(PortFD, TMCDriverTempValue, TMCDriverCMD);
3064  if (i == -4 && TMCDriverTempValue[0] == '0' )
3065  {
3066  char ResponseText[RB_MAX_LEN] = {0};
3067  snprintf(ResponseText, sizeof(ResponseText), "TMC Reporting not detected, Axis %i", driver_number);
3068  IUSaveText(&OnstepStat[8 + driver_number], ResponseText);
3069  LOG_DEBUG("TMC Drivers responding as if not there, disabling further checks");
3070  TMCDrivers = false;
3071  }
3072  else
3073  {
3074  if (i > 0 )
3075  {
3076  if (TMCDriverTempValue[0] == 0)
3077  {
3078  IUSaveText(&OnstepStat[8 + driver_number], "No Condition");
3079  TMCDrivers = false;
3080  }
3081  else
3082  {
3083  char StepperState[1024] = {0};
3084  bool unknown_value = false;
3085  int current_position = 0;
3086  while (TMCDriverTempValue[current_position] != 0 && unknown_value == false)
3087  {
3088  if (TMCDriverTempValue[current_position] == ',')
3089  {
3090  current_position++;
3091  }
3092  else
3093  {
3094  if (TMCDriverTempValue[current_position] == 'S' && TMCDriverTempValue[current_position + 1] == 'T')
3095  {
3096  strcat(StepperState, "Standstill,");
3097  }
3098  else if (TMCDriverTempValue[current_position] == 'O' && TMCDriverTempValue[current_position + 1] == 'A')
3099  {
3100  strcat(StepperState, "Open Load A Pair,");
3101  }
3102  else if (TMCDriverTempValue[current_position] == 'O' && TMCDriverTempValue[current_position + 1] == 'B')
3103  {
3104  strcat(StepperState, "Open Load B Pair,");
3105  }
3106  else if (TMCDriverTempValue[current_position] == 'G' && TMCDriverTempValue[current_position + 1] == 'A')
3107  {
3108  strcat(StepperState, "Short to Ground A Pair,");
3109  }
3110  else if (TMCDriverTempValue[current_position] == 'G' && TMCDriverTempValue[current_position + 1] == 'B')
3111  {
3112  strcat(StepperState, "Short to Ground B Pair,");
3113  }
3114  else if (TMCDriverTempValue[current_position] == 'O' && TMCDriverTempValue[current_position + 1] == 'T')
3115  {
3116  strcat(StepperState, "Over Temp (>150C),");
3117  }
3118  else if (TMCDriverTempValue[current_position] == 'P' && TMCDriverTempValue[current_position + 1] == 'W')
3119  {
3120  strcat(StepperState, "Pre-Warning: Over Temp (>120C),");
3121  }
3122  else if (TMCDriverTempValue[current_position] == 'G' && TMCDriverTempValue[current_position + 1] == 'F')
3123  {
3124  strcat(StepperState, "General Fault,");
3125  }
3126  else
3127  {
3128  unknown_value = true;
3129  break;
3130  }
3131  current_position = current_position + 3;
3132  }
3133  }
3134  if (unknown_value)
3135  {
3136  IUSaveText(&OnstepStat[8 + driver_number], TMCDriverTempValue);
3137  }
3138  else
3139  {
3140  IUSaveText(&OnstepStat[8 + driver_number], StepperState);
3141  }
3142  }
3143  }
3144  else
3145  {
3146  IUSaveText(&OnstepStat[8 + driver_number], "Unknown read error");
3147  }
3148  }
3149  }
3150  }
3151  }
3152 
3153  // Update OnStep Status TAB
3154  IDSetText(&OnstepStatTP, nullptr);
3155  //Align tab, so it doesn't conflict
3156  //May want to reduce frequency of updates
3157  if (!UpdateAlignStatus()) {
3158  LOG_WARN("Fail Align Command");
3159  LOG_WARN("Communication error on Align Status Update, this update aborted, will try again...");
3160  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
3161  }
3162  UpdateAlignErr();
3163 
3164 
3165  if (OSUpdateFocuser() != 0) // Update Focuser Position
3166  {
3167  LOG_WARN("Communication error on Focuser Update, this update aborted, will try again...");
3168  return true; //COMMUNICATION ERROR, BUT DON'T PUT TELESCOPE IN ERROR STATE
3169  }
3170 
3171 #ifndef OnStep_Alpha
3172  if (!OSPECviaGU)
3173  {
3174  PECStatus(0);
3175  }
3176  //#Gu# has this built in
3177 #endif
3178 
3179 
3180  return true;
3181 }
3182 
3183 
3184 bool LX200_OnStep::SetTrackEnabled(bool enabled) //track On/Off events handled by inditelescope Tested
3185 {
3186  char response[RB_MAX_LEN];
3187 
3188  if (enabled)
3189  {
3190  int res = getCommandSingleCharResponse(PortFD, response, ":Te#"); //0 = failure, 1 = success, no # on reply
3191  if(res < 0 || response[0] == '0')
3192  {
3193  LOGF_ERROR("===CMD==> Track On %s", response);
3194  return false;
3195  }
3196  }
3197  else
3198  {
3199  int res = getCommandSingleCharResponse(PortFD, response, ":Td#"); //0 = failure, 1 = success, no # on reply
3200  if(res < 0 || response[0] == '0')
3201  {
3202  LOGF_ERROR("===CMD==> Track Off %s", response);
3203  return false;
3204  }
3205  }
3206  return true;
3207 }
3208 
3209 bool LX200_OnStep::setLocalDate(uint8_t days, uint8_t months, uint16_t years)
3210 {
3211  years = years % 100;
3212  char cmd[CMD_MAX_LEN] = {0};
3213 
3214  snprintf(cmd, CMD_MAX_LEN, ":SC%02d/%02d/%02d#", months, days, years);
3215 
3216  if (!sendOnStepCommand(cmd)) return true;
3217  return false;
3218 }
3219 
3221 {
3222  int error_type;
3223  int nbytes_write = 0;
3224 
3225  DEBUGF(DBG_SCOPE, "CMD <%s>", cmd);
3226 
3227  flushIO(PortFD);
3228  /* Add mutex */
3229  std::unique_lock<std::mutex> guard(lx200CommsLock);
3230  tcflush(PortFD, TCIFLUSH);
3231 
3232 
3233  if ((error_type = tty_write_string(PortFD, cmd, &nbytes_write)) != TTY_OK) {
3234  LOGF_ERROR("CHECK CONNECTION: Error sending command %s", cmd);
3235  return 0; //Fail if we can't write
3236  //return error_type;
3237  }
3238 
3239  return 1;
3240 }
3241 
3243 {
3244  char response[1] = {0};
3245  int error_type;
3246  int nbytes_write = 0, nbytes_read = 0;
3247 
3248  DEBUGF(DBG_SCOPE, "CMD <%s>", cmd);
3249 
3250  flushIO(PortFD);
3251  /* Add mutex */
3252  std::unique_lock<std::mutex> guard(lx200CommsLock);
3253  tcflush(PortFD, TCIFLUSH);
3254 
3255  if ((error_type = tty_write_string(PortFD, cmd, &nbytes_write)) != TTY_OK)
3256  return error_type;
3257 
3258  error_type = tty_read_expanded(PortFD, response, 1, OSTimeoutSeconds, OSTimeoutMicroSeconds, &nbytes_read);
3259 
3260  tcflush(PortFD, TCIFLUSH);
3261  DEBUGF(DBG_SCOPE, "RES <%c>", response[0]);
3262 
3263  if (nbytes_read < 1)
3264  {
3265  LOG_WARN("Timeout/Error on response. Check connection.");
3266  return false;
3267  }
3268 
3269  return (response[0] == '0'); //OnStep uses 0 for success and non zero for failure, in *most* cases;
3270 }
3271 
3272 int LX200_OnStep::getCommandSingleCharResponse(int fd, char *data, const char *cmd)
3273 {
3274  char *term;
3275  int error_type;
3276  int nbytes_write = 0, nbytes_read = 0;
3277 
3278  DEBUGF(DBG_SCOPE, "CMD <%s>", cmd);
3279 
3280  flushIO(fd);
3281  /* Add mutex */
3282  std::unique_lock<std::mutex> guard(lx200CommsLock);
3283 
3284  if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
3285  return error_type;
3286 
3287  error_type = tty_read_expanded(fd, data, 1, OSTimeoutSeconds, OSTimeoutMicroSeconds, &nbytes_read);
3288  tcflush(fd, TCIFLUSH);
3289 
3290  if (error_type != TTY_OK)
3291  return error_type;
3292 
3293  term = strchr(data, '#');
3294  if (term)
3295  *term = '\0';
3296  if (nbytes_read < RB_MAX_LEN) //given this function that should always be true, as should nbytes_read always be 1
3297  {
3298  data[nbytes_read] = '\0';
3299  }
3300  else
3301  {
3302  LOG_DEBUG("got RB_MAX_LEN bytes back (which should never happen), last byte set to null and possible overflow");
3303  data[RB_MAX_LEN - 1] = '\0';
3304  }
3305 
3306  DEBUGF(DBG_SCOPE, "RES <%s>", data);
3307 
3308  return nbytes_read;
3309 }
3310 
3311 
3313 {
3314  tcflush(fd, TCIOFLUSH);
3315  int error_type = 0;
3316  int nbytes_read;
3317  std::unique_lock<std::mutex> guard(lx200CommsLock);
3318  tcflush(fd, TCIOFLUSH);
3319  do {
3320  char discard_data[RB_MAX_LEN] = {0};
3321  error_type = tty_read_section_expanded(fd, discard_data, '#', 0, 1000, &nbytes_read);
3322  if (error_type >= 0) {
3323  LOGF_DEBUG("flushIO: Information in buffer: Bytes: %u, string: %s", nbytes_read, discard_data);
3324  }
3325  //LOGF_DEBUG("flushIO: error_type = %i", error_type);
3326  } while (error_type > 0);
3327  return 0;
3328 }
3329 
3330 int LX200_OnStep::getCommandDoubleResponse(int fd, double *value, char *data, const char *cmd)
3331 {
3332  char *term;
3333  int error_type;
3334  int nbytes_write = 0, nbytes_read = 0;
3335 
3336  DEBUGF(DBG_SCOPE, "CMD <%s>", cmd);
3337 
3338  flushIO(fd);
3339  /* Add mutex */
3340  std::unique_lock<std::mutex> guard(lx200CommsLock);
3341  tcflush(fd, TCIFLUSH);
3342 
3343  if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
3344  return error_type;
3345 
3346  error_type = tty_read_section_expanded(fd, data, '#', OSTimeoutSeconds, OSTimeoutMicroSeconds, &nbytes_read);
3347  tcflush(fd, TCIFLUSH);
3348 
3349  term = strchr(data, '#');
3350  if (term)
3351  *term = '\0';
3352  if (nbytes_read < RB_MAX_LEN) //If within buffer, terminate string with \0 (in case it didn't find the #)
3353  {
3354  data[nbytes_read] = '\0'; //Indexed at 0, so this is the byte passed it
3355  }
3356  else
3357  {
3358  LOG_DEBUG("got RB_MAX_LEN bytes back, last byte set to null and possible overflow");
3359  data[RB_MAX_LEN - 1] = '\0';
3360  }
3361 
3362  DEBUGF(DBG_SCOPE, "RES <%s>", data);
3363 
3364  if (error_type != TTY_OK)
3365  {
3366  LOGF_DEBUG("Error %d", error_type);
3367  LOG_DEBUG("Flushing connection");
3368  tcflush(fd, TCIOFLUSH);
3369  return error_type;
3370  }
3371 
3372  if (sscanf(data, "%lf", value) != 1){
3373  LOG_WARN("Invalid response, check connection");
3374  LOG_DEBUG("Flushing connection");
3375  tcflush(fd, TCIOFLUSH);
3376  return RES_ERR_FORMAT; //-1001, so as not to conflict with TTY_RESPONSE;
3377  }
3378 
3379  return nbytes_read;
3380 
3381 }
3382 
3383 
3384 int LX200_OnStep::getCommandIntResponse(int fd, int *value, char *data, const char *cmd)
3385 {
3386  char *term;
3387  int error_type;
3388  int nbytes_write = 0, nbytes_read = 0;
3389 
3390  DEBUGF(DBG_SCOPE, "CMD <%s>", cmd);
3391 
3392  flushIO(fd);
3393  /* Add mutex */
3394  std::unique_lock<std::mutex> guard(lx200CommsLock);
3395  tcflush(fd, TCIFLUSH);
3396 
3397  if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
3398  return error_type;
3399 
3400  error_type = tty_read_section_expanded(fd, data, '#', OSTimeoutSeconds, OSTimeoutMicroSeconds, &nbytes_read);
3401  tcflush(fd, TCIFLUSH);
3402 
3403  term = strchr(data, '#');
3404  if (term)
3405  *term = '\0';
3406  if (nbytes_read < RB_MAX_LEN) //If within buffer, terminate string with \0 (in case it didn't find the #)
3407  {
3408  data[nbytes_read] = '\0'; //Indexed at 0, so this is the byte passed it
3409  }
3410  else
3411  {
3412  LOG_DEBUG("got RB_MAX_LEN bytes back, last byte set to null and possible overflow");
3413  data[RB_MAX_LEN - 1] = '\0';
3414  }
3415  DEBUGF(DBG_SCOPE, "RES <%s>", data);
3416  if (error_type != TTY_OK)
3417  {
3418  LOGF_DEBUG("Error %d", error_type);
3419  LOG_DEBUG("Flushing connection");
3420  tcflush(fd, TCIOFLUSH);
3421  return error_type;
3422  }
3423  if (sscanf(data, "%i", value) != 1){
3424  LOG_WARN("Invalid response, check connection");
3425  LOG_DEBUG("Flushing connection");
3426  tcflush(fd, TCIOFLUSH);
3427  return RES_ERR_FORMAT; //-1001, so as not to conflict with TTY_RESPONSE;
3428  }
3429  return nbytes_read;
3430 }
3431 
3433 {
3434  char *term;
3435  int error_type;
3436  int nbytes_write = 0, nbytes_read = 0;
3437 
3438  DEBUGF(DBG_SCOPE, "CMD <%s>", cmd);
3439 
3440  flushIO(fd);
3441  /* Add mutex */
3442  std::unique_lock<std::mutex> guard(lx200CommsLock);
3443  tcflush(fd, TCIFLUSH);
3444 
3445  if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
3446  return error_type;
3447 
3448  error_type = tty_read_section_expanded(fd, data, '#', OSTimeoutSeconds, OSTimeoutMicroSeconds, &nbytes_read);
3449  tcflush(fd, TCIFLUSH);
3450 
3451  term = strchr(data, '#');
3452  if (term)
3453  *term = '\0';
3454  if (nbytes_read < RB_MAX_LEN) //If within buffer, terminate string with \0 (in case it didn't find the #)
3455  {
3456  data[nbytes_read] = '\0'; //Indexed at 0, so this is the byte passed it
3457  }
3458  else
3459  {
3460  LOG_DEBUG("got RB_MAX_LEN bytes back, last byte set to null and possible overflow");
3461  data[RB_MAX_LEN - 1] = '\0';
3462  }
3463 
3464  DEBUGF(DBG_SCOPE, "RES <%s>", data);
3465 
3466  if (error_type != TTY_OK)
3467  {
3468  LOGF_DEBUG("Error %d", error_type);
3469  return error_type;
3470  }
3471  return nbytes_read;
3472 
3473 }
3474 
3475 
3476 
3477 bool LX200_OnStep::updateLocation(double latitude, double longitude, double elevation)
3478 {
3479  INDI_UNUSED(elevation);
3480 
3481  if (isSimulation())
3482  return true;
3483 
3484  double onstep_long = 360 - longitude ;
3485  while (onstep_long < 0)
3486  onstep_long += 360;
3487  while (onstep_long > 360)
3488  onstep_long -= 360;
3489 
3490  if (!isSimulation() && setSiteLongitude(PortFD, onstep_long) < 0)
3491  {
3492  LOG_ERROR("Error setting site longitude coordinates");
3493  return false;
3494  }
3495 
3496  if (!isSimulation() && setSiteLatitude(PortFD, latitude) < 0)
3497  {
3498  LOG_ERROR("Error setting site latitude coordinates");
3499  return false;
3500  }
3501 
3502  char l[32] = {0}, L[32] = {0};
3503  fs_sexa(l, latitude, 3, 360000);
3504  fs_sexa(L, longitude, 4, 360000);
3505 
3506  LOGF_INFO("Site location updated to Lat %.32s - Long %.32s", l, L);
3507 
3508  return true;
3509 }
3510 
3511 int LX200_OnStep::setMaxElevationLimit(int fd, int max) // According to standard command is :SoDD*# Tested
3512 {
3513  LOGF_INFO("<%s>", __FUNCTION__);
3514 
3515  char read_buffer[RB_MAX_LEN] = {0};
3516 
3517  snprintf(read_buffer, sizeof(read_buffer), ":So%02d#", max);
3518 
3519  return (setStandardProcedure(fd, read_buffer));
3520 }
3521 
3522 int LX200_OnStep::setSiteLongitude(int fd, double Long)
3523 {
3524  int d, m;
3525  double s;
3526  char read_buffer[32];
3527 
3528  getSexComponentsIID(Long, &d, &m, &s);
3529  if (OSHighPrecision)
3530  {
3531  snprintf(read_buffer, sizeof(read_buffer), ":Sg%.03d:%02d:%.02f#", d, m, s);
3532  int result1 = setStandardProcedure(fd, read_buffer);
3533  if (result1 == 0)
3534  {
3535  return 0;
3536  }
3537  else
3538  {
3539  snprintf(read_buffer, sizeof(read_buffer), ":Sg%03d:%02d#", d, m);
3540  return (setStandardProcedure(fd, read_buffer));
3541  }
3542  }
3543  snprintf(read_buffer, sizeof(read_buffer), ":Sg%03d:%02d#", d, m);
3544  return (setStandardProcedure(fd, read_buffer));
3545 }
3546 
3547 int LX200_OnStep::setSiteLatitude(int fd, double Long)
3548 {
3549  int d, m;
3550  double s;
3551  char read_buffer[32];
3552 
3553  getSexComponentsIID(Long, &d, &m, &s);
3554 
3555  if(OSHighPrecision)
3556  {
3557  snprintf(read_buffer, sizeof(read_buffer), ":St%+.02d:%02d:%.02f#", d, m, s);
3558  int result1 = setStandardProcedure(fd, read_buffer);
3559  if (result1 == 0)
3560  {
3561  return 0;
3562  }
3563  else
3564  {
3565  snprintf(read_buffer, sizeof(read_buffer), ":St%+03d:%02d#", d, m);
3566  return (setStandardProcedure(fd, read_buffer));
3567  }
3568  }
3569  snprintf(read_buffer, sizeof(read_buffer), ":St%+03d:%02d#", d, m);
3570  return (setStandardProcedure(fd, read_buffer));
3571 }
3572 
3573 
3574 
3575 
3576 /***** FOCUSER INTERFACE ******
3577 
3578 NOT USED:
3579 virtual bool SetFocuserSpeed (int speed)
3580 SetFocuserSpeed Set Focuser speed. More...
3581 
3582 USED:
3583 virtual IPState MoveFocuser (FocusDirection dir, int speed, uint16_t duration)
3584 MoveFocuser the focuser in a particular direction with a specific speed for a finite duration. More...
3585 
3586 USED:
3587 virtual IPState MoveAbsFocuser (uint32_t targetTicks)
3588 MoveFocuser the focuser to an absolute position. More...
3589 
3590 USED:
3591 virtual IPState MoveRelFocuser (FocusDirection dir, uint32_t ticks)
3592 MoveFocuser the focuser to an relative position. More...
3593 
3594 USED:
3595 virtual bool AbortFocuser ()
3596 AbortFocuser all focus motion. More...
3597 
3598 */
3599 
3600 
3601 IPState LX200_OnStep::MoveFocuser(FocusDirection dir, int speed, uint16_t duration)
3602 {
3603  INDI_UNUSED(speed);
3604  // :FRsnnn# Set focuser target position relative (in microns)
3605  // Returns: Nothing
3606  double output;
3607  char read_buffer[32];
3608  output = duration;
3609  if (dir == FOCUS_INWARD) output = 0 - output;
3610  snprintf(read_buffer, sizeof(read_buffer), ":FR%5f#", output);
3611  sendOnStepCommandBlind(read_buffer);
3612  return IPS_BUSY; // Normal case, should be set to normal by update.
3613 }
3614 
3616 {
3617  // :FSsnnn# Set focuser target position (in microns)
3618  // Returns: Nothing
3619  if (FocusAbsPosN[0].max >= int(targetTicks) && FocusAbsPosN[0].min <= int(targetTicks))
3620  {
3621  char read_buffer[32];
3622  snprintf(read_buffer, sizeof(read_buffer), ":FS%06d#", int(targetTicks));
3623  sendOnStepCommandBlind(read_buffer);
3624  return IPS_BUSY; // Normal case, should be set to normal by update.
3625  }
3626  else
3627  {
3628  LOG_INFO("Unable to move focuser, out of range");
3629  return IPS_ALERT;
3630  }
3631 }
3632 
3634 {
3635  // :FRsnnn# Set focuser target position relative (in microns)
3636  // Returns: Nothing
3637  int output;
3638  char read_buffer[32];
3639  output = ticks;
3640  if (dir == FOCUS_INWARD) output = 0 - ticks;
3641  snprintf(read_buffer, sizeof(read_buffer), ":FR%04d#", output);
3642  sendOnStepCommandBlind(read_buffer);
3643  return IPS_BUSY; // Normal case, should be set to normal by update.
3644 }
3645 
3647 {
3648  // :FQ# Stop the focuser
3649  // Returns: Nothing
3650  char cmd[CMD_MAX_LEN] = {0};
3651  strncpy(cmd, ":FQ#", sizeof(cmd));
3652  return sendOnStepCommandBlind(cmd);
3653 }
3654 
3656 {
3657 
3658  // double current = 0;
3659  // int temp_value;
3660  // int i;
3661  if (OSFocuser1)
3662  {
3663  // Alternate option:
3664  //if (!sendOnStepCommand(":FA#")) {
3665  char value[RB_MAX_LEN] = {0};
3666  int value_int;
3667  int error_or_fail = getCommandIntResponse(PortFD, &value_int, value, ":FG#");
3668  if (error_or_fail > 1) {
3669  FocusAbsPosN[0].value = value_int;
3670  // double current = FocusAbsPosN[0].value;
3671  IDSetNumber(&FocusAbsPosNP, nullptr);
3672  LOGF_DEBUG("Current focuser: %d, %f", value_int, FocusAbsPosN[0].value);
3673  }
3674  // :FT# get status
3675  // Returns: M# (for moving) or S# (for stopped)
3676  char valueStatus[RB_MAX_LEN] = {0};
3677  error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, valueStatus, ":FT#");
3678  if (error_or_fail > 0 ) {
3679  if (valueStatus[0] == 'S')
3680  {
3682  IDSetNumber(&FocusRelPosNP, nullptr);
3684  IDSetNumber(&FocusAbsPosNP, nullptr);
3685  }
3686  else if (valueStatus[0] == 'M')
3687  {
3689  IDSetNumber(&FocusRelPosNP, nullptr);
3691  IDSetNumber(&FocusAbsPosNP, nullptr);
3692  }
3693  else
3694  {
3695  LOG_WARN("Communication :FT# error, check connection.");
3696  //INVALID REPLY
3698  IDSetNumber(&FocusRelPosNP, nullptr);
3700  IDSetNumber(&FocusAbsPosNP, nullptr);
3701  }
3702  } else {
3703  //INVALID REPLY
3704  LOG_WARN("Communication :FT# error, check connection.");
3706  IDSetNumber(&FocusRelPosNP, nullptr);
3708  IDSetNumber(&FocusAbsPosNP, nullptr);
3709  }
3710  // :FM# Get max position (in microns)
3711  // Returns: n#
3712  char focus_max[RB_MAX_LEN]={0};
3713  int focus_max_int;
3714  int fm_error = getCommandIntResponse(PortFD, &focus_max_int, focus_max, ":FM#");
3715  if (fm_error > 0) {
3716  FocusAbsPosN[0].max = focus_max_int;
3718  IDSetNumber(&FocusAbsPosNP, nullptr);
3719  LOGF_DEBUG("focus_max: %s, %i, fm_error: %i", focus_max, focus_max_int, fm_error);
3720  } else {
3721  LOG_WARN("Communication :FM# error, check connection.");
3722  LOGF_WARN("focus_max: %s, %u, fm_error: %i", focus_max,focus_max[0], fm_error);
3723  flushIO(PortFD); //Unlikely to do anything, but just in case.
3724  }
3725  // :FI# Get full in position (in microns)
3726  // Returns: n#
3727  char focus_min[RB_MAX_LEN]={0};
3728  int focus_min_int ;
3729  int fi_error = getCommandIntResponse(PortFD, &focus_min_int, focus_min, ":FI#");
3730  if (fi_error > 0) {
3731  FocusAbsPosN[0].min = focus_min_int;
3733  IDSetNumber(&FocusAbsPosNP, nullptr);
3734  LOGF_DEBUG("focus_min: %s, %i fi_error: %i", focus_min, focus_min_int, fi_error);
3735  } else {
3736  LOG_WARN("Communication :FI# error, check connection.");
3737  flushIO(PortFD); //Unlikely to do anything, but just in case.
3738  }
3739 
3741  LOGF_DEBUG("After update properties: FocusAbsPosN min: %f max: %f", FocusAbsPosN[0].min, FocusAbsPosN[0].max);
3742  }
3743 
3744 
3745  if(OSFocuser2)
3746  {
3747  char value[RB_MAX_LEN] = {0};
3748  int error_return;
3749  //TODO: Check to see if getCommandIntResponse would be better
3750  error_return = getCommandSingleCharErrorOrLongResponse(PortFD, value, ":fG#");
3751  if (error_return >= 0)
3752  {
3753  if ( strcmp(value, "0") )
3754  {
3755  LOG_INFO("Focuser 2 called, but not present, disabling polling");
3756  LOGF_DEBUG("OSFocuser2: %d, OSNumFocusers: %i", OSFocuser2, OSNumFocusers);
3757  OSFocuser2 = false;
3758  }
3759  else
3760  {
3761  OSFocus2TargNP.np[0].value = atoi(value);
3762  IDSetNumber(&OSFocus2TargNP, nullptr);
3763  }
3764  }
3765  else
3766  {
3767  LOGF_INFO("Focuser 2 called, but returned error %i on read, disabling further polling", error_return);
3768  LOGF_DEBUG("OSFocuser2: %d, OSNumFocusers: %i", OSFocuser2, OSNumFocusers);
3769  OSFocuser2 = false;
3770  }
3771  }
3772 
3773  if(OSNumFocusers > 1)
3774  {
3775  char value[RB_MAX_LEN] = {0};
3776  int error_or_fail = getCommandSingleCharResponse(PortFD, value, ":Fa#"); //0 = failure, 1 = success, no # on reply
3777  if (error_or_fail > 0 && value[0] > '0' && value[0] < '9') {
3778  int temp_value = (unsigned int)(value[0]) - '0';
3779  LOGF_DEBUG(":Fa# return: %d", temp_value);
3780  for (int i = 0; i < 9; i++)
3781  {
3782  OSFocusSelectS[i].s = ISS_OFF;
3783  }
3784  if (temp_value == 0)
3785  {
3786  OSFocusSelectS[1].s = ISS_ON;
3787  }
3788  else if (temp_value > 9 || temp_value < 0) //TODO: Check if completely redundant
3789  {
3790  //To solve issue mentioned https://www.indilib.org/forum/development/1406-driver-onstep-lx200-like-for-indi.html?start=624#71572
3792  LOGF_WARN("Active focuser returned out of range: %s, should be 0-9", temp_value);
3793  IDSetSwitch(&OSFocusSelectSP, nullptr);
3794  return 1;
3795  }
3796  else
3797  {
3798  OSFocusSelectS[temp_value - 1].s = ISS_ON;
3799  }
3801  IDSetSwitch(&OSFocusSelectSP, nullptr);
3802  } else {
3803  LOGF_DEBUG(":Fa# returned outside values: %c, %u", value[0], value[0]);
3804  }
3805  }
3806  return 0;
3807 }
3808 
3809 
3810 //Rotator stuff
3811 // IPState MoveRotator(double angle) override;
3812 // bool SyncRotator(double angle) override;
3813 // IPState HomeRotator() override;
3814 // bool ReverseRotator(bool enabled) override;
3815 // bool AbortRotator() override;
3816 // bool SetRotatorBacklash (int32_t steps) override;
3817 // bool SetRotatorBacklashEnabled(bool enabled) override;
3818 
3819 //OnStep Rotator Commands (For reference, and from 5 1 v 4)
3820 // :r+# Enable derotator
3821 // Returns: Nothing
3822 // :r-# Disable derotator
3823 // Returns: Nothing
3824 // :rP# Move rotator to the parallactic angle
3825 // Returns: Nothing
3826 // :rR# Reverse derotator direction
3827 // Returns: Nothing
3828 // :rT# Get status
3829 // Returns: M# (for moving) or S# (for stopped)
3830 // :rI# Get mIn position (in degrees)
3831 // Returns: n#
3832 // :rM# Get Max position (in degrees)
3833 // Returns: n#
3834 // :rD# Get rotator degrees per step
3835 // Returns: n.n#
3836 // :rb# Get rotator backlash amount in steps
3837 // Return: n#
3838 // :rb[n]#
3839 // Set rotator backlash amount in steps
3840 // Returns: 0 on failure
3841 // 1 on success
3842 // :rF# Reset rotator at the home position
3843 // Returns: Nothing
3844 // :rC# Moves rotator to the home position
3845 // Returns: Nothing
3846 // :rG# Get rotator current position in degrees
3847 // Returns: sDDD*MM#
3848 // :rc# Set continuous move mode (for next move command)
3849 // Returns: Nothing
3850 // :r># Move clockwise as set by :rn# command, default = 1 deg (or 0.1 deg/s in continuous mode)
3851 // Returns: Nothing
3852 // :r<# Move counter clockwise as set by :rn# command
3853 // Returns: Nothing
3854 // :rQ# Stops movement (except derotator)
3855 // Returns: Nothing
3856 // :r[n]# Move increment where n = 1 for 1 degrees, 2 for 2 degrees, 3 for 5 degrees, 4 for 10 degrees
3857 // Move rate where n = 1 for .01 deg/s, 2 for 0.1 deg/s, 3 for 1.0 deg/s, 4 for 5.0 deg/s
3858 // Returns: Nothing
3859 // :rS[sDDD*MM'SS]#
3860 // Set position in degrees
3861 // Returns: 0 on failure
3862 // 1 on success
3863 
3865 {
3866  char value[RB_MAX_LEN];
3867  double double_value;
3868  if(OSRotator1)
3869  {
3870  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, value, ":rG#");
3871  if (error_or_fail == 1 && value[0] == '0') //1 char return, response 0 = no Rotator
3872  {
3873  LOG_INFO("Detected Response that Rotator is not present, disabling further checks");
3874  OSRotator1 = false;
3875  return 0; //Return 0, as this is not a communication error
3876  }
3877  if (error_or_fail < 1) { //This does not neccessarily mean
3878  LOG_WARN("Error talking to rotator, might be timeout (especially on network)");
3879  return -1;
3880  }
3881  if (f_scansexa(value, &double_value))
3882  {
3883  // 0 = good, thus this is the bad
3885  IDSetNumber(&GotoRotatorNP, nullptr);
3886  return -1;
3887  }
3888  GotoRotatorN[0].value = double_value;
3889  double min_rotator, max_rotator;
3890  //NOTE: The following commands are only on V4, V5 & OnStepX, not V3
3891  //TODO: Psudo-state for V3 Rotator?
3892  bool changed_minmax = false;
3894  memset(value, 0, RB_MAX_LEN);
3895  error_or_fail = getCommandDoubleResponse(PortFD, &min_rotator, value, ":rI#");
3896  if (error_or_fail > 1)
3897  {
3898  changed_minmax = true;
3899  GotoRotatorN[0].min = min_rotator;
3900  }
3901  memset(value, 0, RB_MAX_LEN);
3902  error_or_fail = getCommandDoubleResponse(PortFD, &max_rotator, value, ":rM#");
3903  if (error_or_fail > 1)
3904  {
3905  changed_minmax = true;
3906  GotoRotatorN[0].max = max_rotator;
3907  }
3908  if (changed_minmax) {
3910  IDSetNumber(&GotoRotatorNP, nullptr);
3911  }
3912  //GotoRotatorN
3913  memset(value, 0, RB_MAX_LEN);
3914  error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, value, ":rT#");
3915  if (error_or_fail > 1) {
3916  if (value[0] == 'S') /*Stopped normal on EQ mounts */
3917  {
3919  IDSetNumber(&GotoRotatorNP, nullptr);
3920 
3921  }
3922  else if (value[0] == 'M') /* Moving, including de-rotation */
3923  {
3925  IDSetNumber(&GotoRotatorNP, nullptr);
3926  }
3927  else
3928  {
3929  //INVALID REPLY
3931  IDSetNumber(&GotoRotatorNP, nullptr);
3932  }
3933  }
3934  memset(value, 0, RB_MAX_LEN);
3935  int backlash_value;
3936  error_or_fail = getCommandIntResponse(PortFD, &backlash_value, value, ":rb#");
3937  if (error_or_fail > 1) {
3938  RotatorBacklashN[0].value = backlash_value;
3940  IDSetNumber(&RotatorBacklashNP, nullptr);
3941  }
3942  }
3943  }
3944  return 0;
3945 }
3946 
3948 {
3949  char cmd[CMD_MAX_LEN] = {0};
3950  int d, m, s;
3951  getSexComponents(angle, &d, &m, &s);
3952 
3953  snprintf(cmd, sizeof(cmd), ":rS%.03d:%02d:%02d#", d, m, s);
3954  LOGF_INFO("Move Rotator: %s", cmd);
3955 
3956 
3958  {
3959  return IPS_BUSY;
3960  }
3961  else
3962  {
3963  return IPS_ALERT;
3964  }
3965 
3966 
3967  return IPS_BUSY;
3968 }
3969 /*
3970 bool LX200_OnStep::SyncRotator(double angle) {
3971 
3972 }*/
3974 {
3975  //Not entirely sure if this means attempt to use limit switches and home, or goto home
3976  //Assuming MOVE to Home
3977  LOG_INFO("Moving Rotator to Home");
3978  sendOnStepCommandBlind(":rC#");
3979  return IPS_BUSY;
3980 }
3981 // bool LX200_OnStep::ReverseRotator(bool enabled) {
3982 // sendOnStepCommandBlind(":rR#");
3983 // return true;
3984 // } //No way to check which way it's going as Indi expects
3985 
3987 {
3988  LOG_INFO("Aborting Rotation, de-rotation in same state");
3989  sendOnStepCommandBlind(":rQ#"); //Does NOT abort de-rotator
3990  return true;
3991 }
3992 
3994 {
3995  char cmd[CMD_MAX_LEN] = {0};
3996  // char response[RB_MAX_LEN];
3997  snprintf(cmd, sizeof(cmd), ":rb%d#", steps);
3998  if(sendOnStepCommand(cmd))
3999  {
4000  return true;
4001  }
4002  return false;
4003 }
4004 
4006 {
4007  //Nothing required here.
4008  INDI_UNUSED(enabled);
4009  return true;
4010  // As it's always enabled, which would mean setting it like SetRotatorBacklash to 0, and losing any saved values. So for now, leave it as is (always enabled)
4011 }
4012 
4013 // bool SyncRotator(double angle) override;
4014 // IPState HomeRotator(double angle) override;
4015 // bool ReverseRotator(bool enabled) override;
4016 // bool AbortRotator() override;
4017 // bool SetRotatorBacklash (int32_t steps) override;
4018 // bool SetRotatorBacklashEnabled(bool enabled) override;
4019 
4020 // Now, derotation is NOT explicitly handled.
4021 
4022 
4023 
4024 //End Rotator stuff
4025 
4026 
4027 
4028 
4029 //PEC Support
4030 //Should probably be added to inditelescope or another interface, because the PEC that's there... is very limited.
4031 
4033 {
4034  // :$QZ+ Enable RA PEC compensation
4035  // Returns: nothing
4036  INDI_UNUSED(axis); //We only have RA on OnStep
4037  if (OSMountType != MOUNTTYPE_ALTAZ )
4038  {
4039  if (OSPECEnabled == true)
4040  {
4041  char cmd[CMD_MAX_LEN] = {0};
4042  LOG_INFO("Sending Command to Start PEC Playback");
4043  strncpy(cmd, ":$QZ+#", sizeof(cmd));
4045  return IPS_BUSY;
4046  }
4047  else
4048  {
4049  LOG_DEBUG("Command to Playback PEC called when Controller does not support PEC");
4050  }
4051  return IPS_ALERT;
4052  }
4053  else
4054  {
4055  OSPECEnabled = false;
4056  LOG_INFO("Command to Start Playback PEC called when Controller does not support PEC due to being Alt-Az, PEC Ignored going forward");
4057  return IPS_ALERT;
4058  }
4059 
4060 }
4061 
4063 {
4064  // :$QZ- Disable RA PEC Compensation
4065  // Returns: nothing
4066  INDI_UNUSED(axis); //We only have RA on OnStep
4067  if (OSPECEnabled == true)
4068  {
4069  char cmd[CMD_MAX_LEN] = {0};
4070  LOG_INFO("Sending Command to Stop PEC Playback");
4071  strncpy(cmd, ":$QZ-#", sizeof(cmd));
4073  return IPS_BUSY;
4074  }
4075  else
4076  {
4077  LOG_DEBUG("Command to Stop Playing PEC called when Controller does not support PEC");
4078  }
4079  return IPS_ALERT;
4080 }
4081 
4083 {
4084  // :$QZ/ Ready Record PEC
4085  // Returns: nothing
4086  INDI_UNUSED(axis); //We only have RA on OnStep
4087  if (OSPECEnabled == true)
4088  {
4089  char cmd[CMD_MAX_LEN] = {0};
4090  LOG_INFO("Sending Command to Start PEC record");
4091  strncpy(cmd, ":$QZ/#", CMD_MAX_LEN);
4093  return IPS_BUSY;
4094  }
4095  else
4096  {
4097  LOG_DEBUG("Command to Record PEC called when Controller does not support PEC");
4098  }
4099  return IPS_ALERT;
4100 }
4101 
4103 {
4104  // :$QZZ Clear the PEC data buffer
4105  // Return: Nothing
4106  INDI_UNUSED(axis); //We only have RA on OnStep
4107  if (OSPECEnabled == true)
4108  {
4109  char cmd[CMD_MAX_LEN] = {0};
4110  LOG_INFO("Sending Command to Clear PEC record");
4111  strncpy(cmd, ":$QZZ#", CMD_MAX_LEN);
4113  return IPS_BUSY;
4114  }
4115  else
4116  {
4117  LOG_DEBUG("Command to clear PEC called when Controller does not support PEC");
4118  }
4119  return IPS_ALERT;
4120 
4121 }
4122 
4124 {
4125  // :$QZ! Write PEC data to EEPROM
4126  // Returns: nothing
4127  INDI_UNUSED(axis); //We only have RA on OnStep
4128  if (OSPECEnabled == true)
4129  {
4130  char cmd[CMD_MAX_LEN] = {0};
4131  LOG_INFO("Sending Command to Save PEC to EEPROM");
4132  strncpy(cmd, ":$QZ!#", CMD_MAX_LEN);
4134  return IPS_BUSY;
4135  }
4136  else
4137  {
4138  LOG_DEBUG("Command to save PEC called when Controller does not support PEC");
4139  }
4140  return IPS_ALERT;
4141 }
4142 
4143 
4145 {
4146  // if (!OSPECviaGU) {
4147  INDI_UNUSED(axis); //We only have RA on OnStep
4148  if (OSPECEnabled == true && OSPECviaGU == false) //All current versions report via #GU
4149  {
4151  {
4152  OSPECEnabled = false;
4153  LOG_INFO("Command to give PEC called when Controller does not support PEC due to being Alt-Az Disabled");
4154  return IPS_ALERT;
4155  }
4156  //LOG_INFO("Getting PEC Status");
4157  // :$QZ? Get PEC status
4158  // Returns: S#
4159  // Returns status (pecSense) In the form: Status is one of "IpPrR" (I)gnore, get ready to (p)lay, (P)laying, get ready to (r)ecord, (R)ecording. Or an optional (.) to indicate an index detect.
4160  // IUFillSwitch(&OSPECStatusS[0], "OFF", "OFF", ISS_ON);
4161  // IUFillSwitch(&OSPECStatusS[1], "Playing", "Playing", ISS_OFF);
4162  // IUFillSwitch(&OSPECStatusS[2], "Recording", "Recording", ISS_OFF);
4163  // IUFillSwitch(&OSPECStatusS[3], "Will Play", "Will Play", ISS_OFF);
4164  // IUFillSwitch(&OSPECStatusS[4], "Will Record", "Will Record", ISS_OFF);
4165  char value[RB_MAX_LEN] = {0};
4167  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, value, ":$QZ?#");
4168  if (error_or_fail > 1) {
4169  OSPECStatusS[0].s = ISS_OFF ;
4170  OSPECStatusS[1].s = ISS_OFF ;
4171  OSPECStatusS[2].s = ISS_OFF ;
4172  OSPECStatusS[3].s = ISS_OFF ;
4173  OSPECStatusS[4].s = ISS_OFF ;
4174  if (value[0] == 'I') //Ignore
4175  {
4177  OSPECStatusS[0].s = ISS_ON ;
4179  // OSPECEnabled = false;
4180  LOG_INFO("Controller reports PEC Ignored and not supported");
4181  LOG_INFO("No Further PEC Commands will be processed, unless status changed");
4182  }
4183  else if (value[0] == 'R') //Active Recording
4184  {
4186  OSPECStatusS[2].s = ISS_ON ;
4188  }
4189  else if (value[0] == 'r') //Waiting for index before recording
4190  {
4192  OSPECStatusS[4].s = ISS_ON ;
4194  }
4195  else if (value[0] == 'P') //Active Playing
4196  {
4198  OSPECStatusS[1].s = ISS_ON ;
4200  }
4201  else if (value[0] == 'p') //Waiting for index before playing
4202  {
4204  OSPECStatusS[3].s = ISS_ON ;
4206  }
4207  else //INVALID REPLY
4208  {
4211  }
4212  if (value[1] == '.')
4213  {
4214  OSPECIndexSP.s = IPS_OK;
4215  OSPECIndexS[0].s = ISS_OFF;
4216  OSPECIndexS[1].s = ISS_ON;
4217  }
4218  else
4219  {
4220  OSPECIndexS[1].s = ISS_OFF;
4221  OSPECIndexS[0].s = ISS_ON;
4222  }
4223  IDSetSwitch(&OSPECStatusSP, nullptr);
4224  IDSetSwitch(&OSPECRecordSP, nullptr);
4225  IDSetSwitch(&OSPECIndexSP, nullptr);
4226  return IPS_OK;
4227  }
4228  else
4229  {
4230  LOG_DEBUG("Timeout or other error on :$QZ?#");
4231  }
4232  }
4233  else
4234  {
4235  // LOG_DEBUG("PEC status called when Controller does not support PEC");
4236  }
4237  return IPS_ALERT;
4238 }
4239 
4240 
4242 {
4243  INDI_UNUSED(axis); //We only have RA on OnStep
4244  if (OSPECEnabled == true)
4245  {
4246  LOG_WARN("PEC Reading NOT Implemented");
4247  return IPS_OK;
4248  }
4249  else
4250  {
4251  LOG_DEBUG("Command to Read PEC called when Controller does not support PEC");
4252  }
4253  return IPS_ALERT;
4254 }
4255 
4256 
4258 {
4259  INDI_UNUSED(axis); //We only have RA on OnStep
4260  if (OSPECEnabled == true)
4261  {
4262  LOG_WARN("PEC Writing NOT Implemented");
4263  return IPS_OK;
4264  }
4265  else
4266  {
4267  LOG_DEBUG("Command to Read PEC called when Controller does not support PEC");
4268  }
4269  return IPS_ALERT;
4270 }
4271 
4272 // New, Multistar alignment goes here:
4273 
4275 {
4276  //See here https://groups.io/g/onstep/message/3624
4277  char cmd[CMD_MAX_LEN] = {0};
4278 
4279  LOG_INFO("Sending Command to Start Alignment");
4280  IUSaveText(&OSNAlignT[0], "Align STARTED");
4281  IUSaveText(&OSNAlignT[1], "GOTO a star, center it");
4282  IUSaveText(&OSNAlignT[2], "GOTO a star, Solve and Sync");
4283  IUSaveText(&OSNAlignT[3], "Press 'Issue Align' if not solving");
4284  IDSetText(&OSNAlignTP, "==>Align Started");
4285  // Check for max number of stars and gracefully fall back to max, if more are requested.
4286  char read_buffer[RB_MAX_LEN] = {0};
4287  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, read_buffer, ":A?#");
4288  if(error_or_fail != 4 || read_buffer[0] < '0' || read_buffer[0] > '9' || read_buffer[1] < '0' || read_buffer[1] > '9' || read_buffer[2] < '0' || read_buffer[2] > '9')
4289  {
4290  LOGF_INFO("Getting Alignment Status: response Error, response = %s>", read_buffer);
4291  return IPS_ALERT;
4292  }
4293  //Check max_stars
4294 
4295  int max_stars = read_buffer[0] - '0';
4296  if (stars > max_stars)
4297  {
4298  LOG_INFO("Tried to start Align with too many stars.");
4299  LOGF_INFO("Starting Align with %d stars", max_stars);
4300  stars = max_stars;
4301  }
4302  snprintf(cmd, sizeof(cmd), ":A%.1d#", stars);
4303  LOGF_INFO("Started Align with %s, max possible stars: %d", cmd, max_stars);
4304  if(sendOnStepCommand(cmd))
4305  {
4306  LOG_INFO("Starting Align failed");
4307  return IPS_BUSY;
4308  }
4309  return IPS_ALERT;
4310 }
4311 
4312 
4314 {
4315  //Used if centering a star manually, most will use plate-solving
4316  //See here https://groups.io/g/onstep/message/3624
4317  char cmd[CMD_MAX_LEN] = {0};
4318  LOG_INFO("Sending Command to Record Star");
4319  strncpy(cmd, ":A+#", sizeof(cmd));
4320  if(sendOnStepCommand(cmd))
4321  {
4322  LOG_INFO("Adding Align failed");
4323  return IPS_BUSY;
4324  }
4325  return IPS_ALERT;
4326 }
4327 
4329 {
4330  // :A?# Align status
4331  // Returns: mno#
4332  // where m is the maximum number of alignment stars
4333  // n is the current alignment star (0 otherwise)
4334  // o is the last required alignment star when an alignment is in progress (0 otherwise)
4335 
4336  char msg[40] = {0};
4337  char stars[5] = {0};
4338  int max_stars, current_star, align_stars;
4339 
4340  char read_buffer[RB_MAX_LEN] = {0};
4341  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, read_buffer, ":A?#");
4342  if(error_or_fail != 4 || read_buffer[0] < '0' || read_buffer[0] > '9' || read_buffer[1] < '0' || read_buffer[1] > '9' || read_buffer[2] < '0' || read_buffer[2] > '9')
4343  {
4344  LOGF_INFO("Getting Alignment Status: response Error, response = %s>", read_buffer);
4345  return false;
4346  }
4347  max_stars = read_buffer[0] - '0';
4348  current_star = read_buffer[1] - '0';
4349  align_stars = read_buffer[2] - '0';
4350  snprintf(stars, sizeof(stars), "%d", max_stars);
4351  IUSaveText(&OSNAlignT[5], stars);
4352  snprintf(stars, sizeof(stars), "%d", current_star);
4353  IUSaveText(&OSNAlignT[6], stars);
4354  snprintf(stars, sizeof(stars), "%d", align_stars);
4355  IUSaveText(&OSNAlignT[7], stars);
4356  LOGF_DEBUG("Align: max_stars: %i current star: %u, align_stars %u", max_stars, current_star, align_stars);
4357 
4358  if (current_star <= align_stars)
4359  {
4360  snprintf(msg, sizeof(msg), "%s Manual Align: Star %d/%d", read_buffer, current_star, align_stars );
4361  IUSaveText(&OSNAlignT[4], msg);
4362  }
4363  if (current_star > align_stars && max_stars > 1)
4364  {
4365  LOGF_DEBUG("Align: current star: %u, align_stars %u", int(current_star), int(align_stars));
4366  snprintf(msg, sizeof(msg), "Manual Align: Completed");
4367  AlignDone();
4368  IUSaveText(&OSNAlignT[4], msg);
4369  UpdateAlignErr();
4370  }
4371  IDSetText(&OSNAlignTP, nullptr);
4372  return true;
4373 }
4374 
4376 {
4377  // :GXnn# Get OnStep value
4378  // Returns: value
4379 
4380  // 00 ax1Cor
4381  // 01 ax2Cor
4382  // 02 altCor
4383  // 03 azmCor
4384  // 04 doCor
4385  // 05 pdCor
4386  // 06 ffCor
4387  // 07 dfCor
4388  // 08 tfCor
4389  // 09 Number of stars, reset to first star
4390  // 0A Star #n HA
4391  // 0B Star #n Dec
4392  // 0C Mount #n HA
4393  // 0D Mount #n Dec
4394  // 0E Mount PierSide (and increment n)
4395 
4396 
4397 
4398  char read_buffer[RB_MAX_LEN] = {0};
4399  char polar_error[RB_MAX_LEN] = {0};
4400  char sexabuf[RB_MAX_LEN] = {0};
4401  // IUFillText(&OSNAlignT[4], "4", "Current Status", "Not Updated");
4402  // IUFillText(&OSNAlignT[5], "5", "Max Stars", "Not Updated");
4403  // IUFillText(&OSNAlignT[6], "6", "Current Star", "Not Updated");
4404  // IUFillText(&OSNAlignT[7], "7", "# of Align Stars", "Not Updated");
4405 
4406  // LOG_INFO("Getting Align Error Status");
4407  int error_or_fail;
4408  double altCor, azmCor;
4409  error_or_fail = getCommandDoubleResponse(PortFD, &altCor, read_buffer, ":GX02#");
4410  if (error_or_fail < 2)
4411  {
4412  LOGF_INFO("Polar Align Error Status response Error, response = %s>", read_buffer);
4413  return false;
4414  }
4415  error_or_fail = getCommandDoubleResponse(PortFD, &azmCor, read_buffer, ":GX02#");
4416  if (error_or_fail < 2)
4417  {
4418  LOGF_INFO("Polar Align Error Status response Error, response = %s>", read_buffer);
4419  return false;
4420  }
4421  fs_sexa(sexabuf, (double)azmCor / 3600, 4, 3600);
4422  snprintf(polar_error, sizeof(polar_error), "%f'' /%s", azmCor, sexabuf);
4423  IUSaveText(&OSNAlignErrT[1], polar_error);
4424  fs_sexa(sexabuf, (double)altCor / 3600, 4, 3600);
4425  snprintf(polar_error, sizeof(polar_error), "%f'' /%s", altCor, sexabuf);
4426  IUSaveText(&OSNAlignErrT[0], polar_error);
4427  IDSetText(&OSNAlignErrTP, nullptr);
4428 
4429 
4430  return true;
4431 }
4432 
4434 {
4435  //See here https://groups.io/g/onstep/message/3624
4436  if (OSAlignCompleted == false)
4437  {
4438  OSAlignCompleted = true;
4439  LOG_INFO("Alignment Done - May still be calculating");
4440  IUSaveText(&OSNAlignT[0], "Align FINISHED");
4441  IUSaveText(&OSNAlignT[1], "------");
4442  IUSaveText(&OSNAlignT[2], "Optionally press:");
4443  IUSaveText(&OSNAlignT[3], "Write Align to NVRAM/Flash ");
4444  IDSetText(&OSNAlignTP, nullptr);
4445  return IPS_OK;
4446  }
4447  return IPS_BUSY;
4448 }
4449 
4451 {
4452  //See here https://groups.io/g/onstep/message/3624
4453  char cmd[CMD_MAX_LEN] = {0};
4454  LOG_INFO("Sending Command to Finish Alignment and write");
4455  strncpy(cmd, ":AW#", sizeof(cmd));
4456  IUSaveText(&OSNAlignT[0], "Align FINISHED");
4457  IUSaveText(&OSNAlignT[1], "------");
4458  IUSaveText(&OSNAlignT[2], "And Written to EEPROM");
4459  IUSaveText(&OSNAlignT[3], "------");
4460  IDSetText(&OSNAlignTP, nullptr);
4462  {
4463  return IPS_OK;
4464  }
4465  IUSaveText(&OSNAlignT[0], "Align WRITE FAILED");
4466  IDSetText(&OSNAlignTP, nullptr);
4467  return IPS_ALERT;
4468 
4469 }
4470 
4471 #ifdef ONSTEP_NOTDONE
4473 {
4474  // :SXnn,VVVVVV...# Set OnStep value
4475  // Return: 0 on failure
4476  // 1 on success
4477  // if (parameter[0]=='G') { // Gn: General purpose output
4478  // :SXGn,value
4479  // value, 0 = low, other = high
4480  LOG_INFO("Not implemented yet");
4481  return IPS_OK;
4482 }
4483 #endif
4484 
4486 {
4487  LOG_INFO("Not implemented yet");
4488  OSGetOutputState(output);
4489  return IPS_OK;
4490 }
4491 
4492 /*
4493 Reference:
4494  // :GXnn# Get OnStep value
4495  // Returns: value
4496  // Error = 123456789
4497  //
4498  // Double unless noted: integer:i, special:* and values in {}
4499  //
4500  // 00 ax1Cor
4501  // 01 ax2Cor
4502  // 02 altCor //EQ Altitude Correction
4503  // 03 azmCor //EQ Azimuth Correction
4504  // 04 doCor
4505  // 05 pdCor
4506  // 06 ffCor
4507  // 07 dfCor
4508  // 08 tfCor
4509  // 09 Number of stars, reset to first star
4510  // 0A Star #n HA
4511  // 0B Star #n Dec
4512  // 0C Mount #n HA
4513  // 0D Mount #n Dec
4514  // 0E Mount PierSide (and increment n)
4515  // 80 UTC time
4516  // 81 UTC date
4517  // 90 pulse-guide rate
4518  // i 91 pec analog value
4519  // 92 MaxRate
4520  // 93 MaxRate (default) number
4521  // * 94 pierSide (N if never) {Same as :Gm# (E, W, None)}
4522  // i 95 autoMeridianFlip AutoFlip setting {0/1+}
4523  // * 96 preferred pier side {E, W, B}
4524  // 97 slew speed
4525  // * 98 rotator {D, R, N}
4526  // 9A temperature in deg. C
4527  // 9B pressure in mb
4528  // 9C relative humidity in %
4529  // 9D altitude in meters
4530  // 9E dew point in deg. C
4531  // 9F internal MCU temperature in deg. C
4532  // * Un: Get stepper driver statUs
4533  // En: Get settings
4534  // Fn: Debug
4535  // G0-GF (HEX!) = Onstep output status
4536 
4537 */
4538 
4540 {
4541  // :GXnn# Get OnStep value
4542  // Returns: value
4543  // nn= G0-GF (HEX!) - Output status
4544  //
4545  char value[RB_MAX_LEN] = {0};
4546  char command[CMD_MAX_LEN] = {0};
4547  strncpy(command, ":$GXGm#", CMD_MAX_LEN);
4548  LOGF_INFO("Output: %s", char(output));
4549  LOGF_INFO("Command: %s", command);
4550  command[5] = char(output);
4551  LOGF_INFO("Command: %s", command);
4552 
4553  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, value, command);
4554  if (error_or_fail > 0) {
4555  if (value[0] == 0)
4556  {
4557  OSOutput1S[0].s = ISS_ON;
4558  OSOutput1S[1].s = ISS_OFF;
4559  }
4560  else
4561  {
4562  OSOutput1S[0].s = ISS_OFF;
4563  OSOutput1S[1].s = ISS_ON;
4564  }
4565  IDSetSwitch(&OSOutput1SP, nullptr);
4566  return true;
4567  }
4568  return false;
4569 }
4570 
4571 bool LX200_OnStep::SetTrackRate(double raRate, double deRate)
4572 {
4573  char read_buffer[RB_MAX_LEN];
4574  snprintf(read_buffer, sizeof(read_buffer), ":RA%04f#", raRate);
4575  LOGF_INFO("Setting: Custom RA Rate to %04f", raRate);
4576  if (!sendOnStepCommand(read_buffer))
4577  {
4578  return false;
4579  }
4580  snprintf(read_buffer, sizeof(read_buffer), ":RE%04f#", deRate);
4581  LOGF_INFO("Setting: Custom DE Rate to %04f", deRate);
4582  if (!sendOnStepCommand(read_buffer))
4583  {
4584  return false;
4585  }
4586  LOG_INFO("Custom RA and DE Rates successfully set");
4587  return true;
4588 }
4589 
4590 void LX200_OnStep::slewError(int slewCode)
4591 {
4592  // 0=Goto is possible
4593  // 1=below the horizon limit
4594  // 2=above overhead limit
4595  // 3=controller in standby
4596  // 4=mount is parked
4597  // 5=Goto in progress
4598  // 6=outside limits (MaxDec, MinDec, UnderPoleLimit, MeridianLimit)
4599  // 7=hardware fault
4600  // 8=already in motion
4601  // 9=unspecified error
4602  switch(slewCode)
4603  {
4604  case 0:
4605  LOG_ERROR("OnStep slew/syncError called with value 0-goto possible, this is normal operation");
4606  return;
4607  case 1:
4608  LOG_ERROR("OnStep slew/syncError: Below the horizon limit");
4609  break;
4610  case 2:
4611  LOG_ERROR("OnStep slew/syncError: Above Overhead limit");
4612  break;
4613  case 3:
4614  LOG_ERROR("OnStep slew/syncError: Controller in standby, Usual issue fix: Turn tracking on");
4615  break;
4616  case 4:
4617  LOG_ERROR("OnStep slew/syncError: Mount is Parked");
4618  break;
4619  case 5:
4620  LOG_ERROR("OnStep slew/syncError: Goto in progress");
4621  break;
4622  case 6:
4623  LOG_ERROR("OnStep slew/syncError: Outside limits: Max/Min Dec, Under Pole Limit, Meridian Limit, Sync attempted to wrong pier side");
4624  break;
4625  case 7:
4626  LOG_ERROR("OnStep slew/syncError: Hardware Fault");
4627  break;
4628  case 8:
4629  LOG_ERROR("OnStep slew/syncError: Already in motion");
4630  break;
4631  case 9:
4632  LOG_ERROR("OnStep slew/syncError: Unspecified Error");
4633  break;
4634  default:
4635  LOG_ERROR("OnStep slew/syncError: Not in range of values that should be returned! INVALID, Something went wrong!");
4636  }
4637  EqNP.s = IPS_ALERT;
4638  IDSetNumber(&EqNP, nullptr);
4639 }
4640 
4641 
4642 //Override LX200 sync function, to allow for error returns
4643 bool LX200_OnStep::Sync(double ra, double dec)
4644 {
4645 
4646  char read_buffer[RB_MAX_LEN] = {0};
4647  // int error_code;
4648 
4649  if (!isSimulation())
4650  {
4651  if (setObjectRA(PortFD, ra) < 0 || (setObjectDEC(PortFD, dec)) < 0)
4652  {
4653  EqNP.s = IPS_ALERT;
4654  IDSetNumber(&EqNP, "Error setting RA/DEC. Unable to Sync.");
4655  return false;
4656  }
4657  LOG_DEBUG("CMD <:CM#>");
4658  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, read_buffer, ":CM#");
4659  LOGF_DEBUG("RES <%s>", read_buffer);
4660  if (error_or_fail > 1) {
4661  if (strcmp(read_buffer, "N/A"))
4662  {
4663  if (read_buffer[0] == 'E' && read_buffer[1] >= '0' && read_buffer[1] <= '9') //strcmp will be 0 if they match, so this is the case for failure.
4664  {
4665  int error_code = read_buffer[1] - '0';
4666  LOGF_DEBUG("Sync failed with response: %s, Error code: %i", read_buffer, error_code);
4667  slewError(error_code);
4668  EqNP.s = IPS_ALERT;
4669  IDSetNumber(&EqNP, "Synchronization failed.");
4670  return false;
4671  } else {
4672  LOG_ERROR("Unexpected return on sync call!");
4673  LOG_ERROR("Check system & Align if doing align to see if it went through!");
4674  return false;
4675  }
4676  }
4677  } else {
4678  LOG_ERROR("Communication error on sync! Re-issue sync!");
4679  return false;
4680  }
4681  }
4682 
4683  currentRA = ra;
4684  currentDEC = dec;
4685 
4686  LOG_INFO("OnStep: Synchronization successful.");
4687  return true;
4688 }
4689 
4691 {
4693  WI::saveConfigItems(fp);
4694  return true;
4695 }
4696 
4698 {
4699  if (OSHasOutputs) {
4700  // Features names and type are accessed via :GXYn (where n 1 to 8)
4701  // we take these names to display in Output tab
4702  // return value is ssssss,n where ssssss is the name and n is the type
4703  char port_name[MAXINDINAME] = {0}, getoutp[MAXINDINAME] = {0}, configured[MAXINDINAME] = {0}, p_name[MAXINDINAME] = {0};
4704  size_t k {0};
4705  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, configured,
4706  ":GXY0#"); // returns a string with 1 where Feature is configured
4707  // ex: 10010010 means Feature 1,4 and 7 are configured
4708 
4709  if (error_or_fail == -4 && configured[0] == '0') {
4710  OSHasOutputs = false;
4711  LOG_INFO("Outputs not detected, disabling further checks");
4712  }
4713 
4714  IUFillNumber(&OutputPorts[0], "Unconfigured", "Unconfigured", "%g", 0, 255, 1, 0);
4715  for(int i = 1; i < PORTS_COUNT; i++)
4716  {
4717  if(configured[i - 1] == '1') // is Feature is configured
4718  {
4719  snprintf(getoutp, sizeof(getoutp), ":GXY%d#", i);
4720  int error_or_fail = getCommandSingleCharErrorOrLongResponse(PortFD, port_name, getoutp);
4721  if (error_or_fail > 0)
4722  {
4723  for(k = 0; k < strlen(port_name); k++) // remove feature type
4724  {
4725  if(port_name[k] == ',') port_name[k] = '_';
4726  p_name[k] = port_name[k];
4727  p_name[k + 1] = 0;
4728  }
4729  IUFillNumber(&OutputPorts[i], p_name, p_name, "%g", 0, 255, 1, 0);
4730  } else {
4731  LOGF_ERROR("Communication error on %s, ignoring, disconnect and reconnect to clear", getoutp);
4732  IUFillNumber(&