1 /*
2     MyFocuserPro2 Focuser
3     Copyright (c) 2019 Alan Townshend
4 
5     Based on Moonlite focuser
6     Copyright (C) 2013-2019 Jasem Mutlaq (mutlaqja@ikarustech.com)
7 
8     This library is free software; you can redistribute it and/or
9     modify it under the terms of the GNU Lesser General Public
10     License as published by the Free Software Foundation; either
11     version 2.1 of the License, or (at your option) any later version.
12 
13     This library is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16     Lesser General Public License for more details.
17 
18     You should have received a copy of the GNU Lesser General Public
19     License along with this library; if not, write to the Free Software
20     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21 
22 */
23 
24 #include "myfocuserpro2.h"
25 #include "connectionplugins/connectiontcp.h"
26 #include "indicom.h"
27 
28 #include <cmath>
29 #include <cstring>
30 #include <memory>
31 
32 #include <termios.h>
33 #include <unistd.h>
34 
35 static std::unique_ptr<MyFocuserPro2> myFocuserPro2(new MyFocuserPro2());
36 
MyFocuserPro2()37 MyFocuserPro2::MyFocuserPro2()
38 {
39     // Can move in Absolute & Relative motions, can AbortFocuser motion, and has variable speed.
40     FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE | FOCUSER_CAN_ABORT | FOCUSER_CAN_REVERSE |
41                       FOCUSER_HAS_VARIABLE_SPEED |
42                       FOCUSER_CAN_SYNC);
43 
44     setSupportedConnections(CONNECTION_SERIAL | CONNECTION_TCP);
45 
46     setVersion(0, 7);
47 }
48 
initProperties()49 bool MyFocuserPro2::initProperties()
50 {
51     INDI::Focuser::initProperties();
52 
53     FocusSpeedN[0].min   = 0;
54     FocusSpeedN[0].max   = 2;
55     FocusSpeedN[0].value = 1;
56 
57     /* Relative and absolute movement */
58     FocusRelPosN[0].min   = 0.;
59     FocusRelPosN[0].max   = 50000.;
60     FocusRelPosN[0].value = 0.;
61     FocusRelPosN[0].step  = 1000;
62 
63     FocusAbsPosN[0].min   = 0.;
64     FocusAbsPosN[0].max   = 200000.;
65     FocusAbsPosN[0].value = 0.;
66     FocusAbsPosN[0].step  = 1000;
67 
68     FocusMaxPosN[0].min   = 1024.;
69     FocusMaxPosN[0].max   = 200000.;
70     FocusMaxPosN[0].value = 0.;
71     FocusMaxPosN[0].step  = 1000;
72 
73     //Backlash
74     BacklashInStepsN[0].min   = 0;
75     BacklashInStepsN[0].max   = 512;
76     BacklashInStepsN[0].value = 0;
77     BacklashInStepsN[0].step  = 2;
78 
79     BacklashOutStepsN[0].min   = 0;
80     BacklashOutStepsN[0].max   = 512;
81     BacklashOutStepsN[0].value = 0;
82     BacklashOutStepsN[0].step  = 2;
83 
84 
85     // Backlash In
86     IUFillSwitch(&BacklashInS[INDI_ENABLED], "INDI_ENABLED", "On", ISS_OFF);
87     IUFillSwitch(&BacklashInS[INDI_DISABLED], "INDI_DISABLED", "Off", ISS_OFF);
88     IUFillSwitchVector(&BacklashInSP, BacklashInS, 2, getDeviceName(), "Backlash In", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
89                        IPS_IDLE);
90 
91     IUFillNumber(&BacklashInStepsN[0], "Steps", "", "%3.0f", 0, 512, 2, 0);
92     IUFillNumberVector(&BacklashInStepsNP, BacklashInStepsN, 1, getDeviceName(), "Backlash-In", "", OPTIONS_TAB, IP_RW, 0,
93                        IPS_IDLE);
94 
95     // Backlash Out
96     IUFillSwitch(&BacklashOutS[INDI_ENABLED], "INDI_ENABLED", "On", ISS_OFF);
97     IUFillSwitch(&BacklashOutS[INDI_DISABLED], "INDI_DISABLED", "Off", ISS_OFF);
98     IUFillSwitchVector(&BacklashOutSP, BacklashOutS, 2, getDeviceName(), "Backlash Out", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
99                        IPS_IDLE);
100 
101     IUFillNumber(&BacklashOutStepsN[0], "Steps", "", "%3.0f", 0, 512, 2, 0);
102     IUFillNumberVector(&BacklashOutStepsNP, BacklashOutStepsN, 1, getDeviceName(), "Backlash-Out", "", OPTIONS_TAB, IP_RW, 0,
103                        IPS_IDLE);
104 
105     // Focuser temperature
106     IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Celsius", "%6.2f", -40, 80., 0., 0.);
107     IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", MAIN_CONTROL_TAB,
108                        IP_RO, 0, IPS_IDLE);
109 
110     // Temperature Settings
111     IUFillNumber(&TemperatureSettingN[0], "Coefficient", "", "%6.2f", 0, 50, 1, 0);
112     IUFillNumberVector(&TemperatureSettingNP, TemperatureSettingN, 1, getDeviceName(), "T. Settings", "", OPTIONS_TAB, IP_RW, 0,
113                        IPS_IDLE);
114 
115     // Compensate for temperature
116     IUFillSwitch(&TemperatureCompensateS[TEMP_COMPENSATE_ENABLE], "TEMP_COMPENSATE_ENABLE", "Enable", ISS_OFF);
117     IUFillSwitch(&TemperatureCompensateS[TEMP_COMPENSATE_DISABLE], "TEMP_COMPENSATE_DISABLE", "Disable", ISS_OFF);
118     IUFillSwitchVector(&TemperatureCompensateSP, TemperatureCompensateS, 2, getDeviceName(), "T. Compensate", "", OPTIONS_TAB,
119                        IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
120 
121     // Step Mode
122     IUFillSwitch(&StepModeS[FOCUS_THIRTYSECOND_STEP], "FOCUS_THIRTYSECOND_STEP", "1/32 Step", ISS_OFF);
123     IUFillSwitch(&StepModeS[FOCUS_SIXTEENTH_STEP], "FOCUS_SIXTEENTH_STEP", "1/16 Step", ISS_OFF);
124     IUFillSwitch(&StepModeS[FOCUS_EIGHTH_STEP], "FOCUS_EIGHTH_STEP", "1/8 Step", ISS_OFF);
125     IUFillSwitch(&StepModeS[FOCUS_QUARTER_STEP], "FOCUS_QUARTER_STEP", "1/4 Step", ISS_OFF);
126     IUFillSwitch(&StepModeS[FOCUS_HALF_STEP], "FOCUS_HALF_STEP", "1/2 Step", ISS_OFF);
127     IUFillSwitch(&StepModeS[FOCUS_FULL_STEP], "FOCUS_FULL_STEP", "Full Step", ISS_OFF);
128     IUFillSwitchVector(&StepModeSP, StepModeS, 6, getDeviceName(), "Step Mode", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
129                        IPS_IDLE);
130 
131 
132     IUFillSwitch(&CoilPowerS[COIL_POWER_ON], "COIL_POWER_ON", "On", ISS_OFF);
133     IUFillSwitch(&CoilPowerS[COIL_POWER_OFF], "COIL_POWER_OFF", "Off", ISS_OFF);
134     IUFillSwitchVector(&CoilPowerSP, CoilPowerS, 2, getDeviceName(), "Coil Power", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
135                        IPS_IDLE);
136 
137     IUFillSwitch(&DisplayS[DISPLAY_OFF], "DISPLAY_OFF", "Off", ISS_OFF);
138     IUFillSwitch(&DisplayS[DISPLAY_ON], "DISPLAY_ON", "On", ISS_OFF);
139     IUFillSwitchVector(&DisplaySP, DisplayS, 2, getDeviceName(), "Display", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
140 
141 
142     IUFillSwitch(&GotoHomeS[0], "GOTO_HOME", "Go", ISS_OFF);
143     IUFillSwitchVector(&GotoHomeSP, GotoHomeS, 1, getDeviceName(), "Goto Home Position", "", MAIN_CONTROL_TAB, IP_RW,
144                        ISR_ATMOST1, 0, IPS_IDLE);
145 
146     setPollingPeriodRange(1000, 30000);
147     setDefaultPollingPeriod(1000);
148 
149     tcpConnection->setDefaultHost("192.168.4.1");
150     tcpConnection->setDefaultPort(2020);
151 
152     return true;
153 }
154 
updateProperties()155 bool MyFocuserPro2::updateProperties()
156 {
157     INDI::Focuser::updateProperties();
158 
159     if (isConnected())
160     {
161         defineProperty(&GotoHomeSP);
162         defineProperty(&TemperatureNP);
163         defineProperty(&TemperatureSettingNP);
164         defineProperty(&TemperatureCompensateSP);
165         defineProperty(&BacklashInSP);
166         defineProperty(&BacklashInStepsNP);
167         defineProperty(&BacklashOutSP);
168         defineProperty(&BacklashOutStepsNP);
169         defineProperty(&StepModeSP);
170         defineProperty(&DisplaySP);
171         defineProperty(&CoilPowerSP);
172 
173         setTemperatureCelsius();
174 
175         LOG_INFO("MyFocuserPro2 parameters updated, focuser ready for use.");
176     }
177     else
178     {
179         deleteProperty(GotoHomeSP.name);
180         deleteProperty(TemperatureNP.name);
181         deleteProperty(TemperatureSettingNP.name);
182         deleteProperty(TemperatureCompensateSP.name);
183         deleteProperty(BacklashInSP.name);
184         deleteProperty(BacklashInStepsNP.name);
185         deleteProperty(BacklashOutSP.name);
186         deleteProperty(BacklashOutStepsNP.name);
187         deleteProperty(StepModeSP.name);
188         deleteProperty(DisplaySP.name);
189         deleteProperty(CoilPowerSP.name);
190     }
191 
192     return true;
193 }
194 
Handshake()195 bool MyFocuserPro2::Handshake()
196 {
197     if (Ack())
198     {
199         LOG_INFO("MyFocuserPro2 is online. Getting focus parameters...");
200 
201         getStartupValues();
202 
203         return true;
204     }
205 
206     LOG_INFO(
207         "Error retrieving data from MyFocuserPro2, please ensure MyFocuserPro2 controller is powered and the port is correct.");
208     return false;
209 }
210 
getDefaultName()211 const char * MyFocuserPro2::getDefaultName()
212 {
213     return "MyFocuserPro2";
214 }
215 
Ack()216 bool MyFocuserPro2::Ack()
217 {
218     int nbytes_written = 0, nbytes_read = 0, rc = -1;
219     char errstr[MAXRBUF];
220     char resp[5] = {0};
221     int firmWareVersion = 0;
222 
223     tcflush(PortFD, TCIOFLUSH);
224 
225     //Try to request the firmware version
226     //Test for success on transmission and response
227     //If either one fails, try again, up to 3 times, waiting 1 sec each time
228     //If that fails, then return false.
229     //If success then check the firmware version
230 
231     int numChecks = 0;
232     bool success = false;
233     while(numChecks < 3 && !success)
234     {
235         numChecks++;
236         sleep(1); //wait 1 second between each test.
237 
238         bool transmissionSuccess = (rc = tty_write(PortFD, ":03#", 4, &nbytes_written)) == TTY_OK;
239         if(!transmissionSuccess)
240         {
241             tty_error_msg(rc, errstr, MAXRBUF);
242             LOGF_ERROR("Handshake Attempt %i, tty transmission error: %s.", numChecks, errstr);
243         }
244 
245         bool responseSuccess = (rc = tty_read(PortFD, resp, 5, ML_TIMEOUT, &nbytes_read)) == TTY_OK;
246         if(!responseSuccess)
247         {
248             tty_error_msg(rc, errstr, MAXRBUF);
249             LOGF_ERROR("Handshake Attempt %i, updatePosition response error: %s.", numChecks, errstr);
250         }
251 
252         success = transmissionSuccess && responseSuccess;
253     }
254 
255     if(!success)
256     {
257         LOG_INFO("Handshake failed after 3 attempts");
258         return false;
259     }
260 
261     tcflush(PortFD, TCIOFLUSH);
262 
263     rc = sscanf(resp, "F%d#", &firmWareVersion);
264 
265     if (rc > 0)
266     {
267         // Minimum check only applicable to serial version
268         if (getActiveConnection()->type() == Connection::Interface::CONNECTION_SERIAL)
269         {
270             if(firmWareVersion >= MINIMUM_FIRMWARE_VERSION)
271             {
272                 LOGF_INFO("MyFP2 reported firmware %d", firmWareVersion);
273                 return true;
274 
275             }
276             else
277             {
278                 LOGF_ERROR("Invalid Firmware: focuser firmware version value %d, minimum supported is %d", firmWareVersion,
279                            MINIMUM_FIRMWARE_VERSION );
280             }
281         }
282         else
283         {
284             LOG_INFO("Connection to network focuser is successful.");
285             return true;
286         }
287     }
288     else
289     {
290         LOGF_ERROR("Invalid Response: focuser firmware version value (%s)", resp);
291     }
292     return false;
293 }
294 
readCoilPowerState()295 bool MyFocuserPro2::readCoilPowerState()
296 {
297     char res[ML_RES] = {0};
298 
299     if (sendCommand(":11#", res) == false)
300         return false;
301 
302     uint32_t temp = 0;
303 
304     int rc = sscanf(res, "O%u#", &temp);
305 
306     if (rc > 0)
307 
308         if(temp == 0)
309             CoilPowerS[COIL_POWER_OFF].s = ISS_ON;
310         else if (temp == 1)
311             CoilPowerS[COIL_POWER_ON].s = ISS_ON;
312         else
313         {
314             LOGF_ERROR("Invalid Response: focuser Coil Power value (%s)", res);
315             return false;
316         }
317     else
318     {
319         LOGF_ERROR("Unknown error: focuser Coil Power value (%s)", res);
320         return false;
321     }
322 
323 
324     return true;
325 }
326 
readReverseDirection()327 bool MyFocuserPro2::readReverseDirection()
328 {
329     char res[ML_RES] = {0};
330 
331     if (sendCommand(":13#", res) == false)
332         return false;
333 
334     uint32_t temp = 0;
335 
336     int rc = sscanf(res, "R%u#", &temp);
337 
338     if (rc > 0)
339 
340         if(temp == 0)
341         {
342             FocusReverseS[INDI_DISABLED].s = ISS_ON;
343         }
344         else if (temp == 1)
345         {
346             FocusReverseS[INDI_ENABLED].s = ISS_ON;
347         }
348         else
349         {
350             LOGF_ERROR("Invalid Response: focuser Reverse direction value (%s)", res);
351             return false;
352         }
353     else
354     {
355         LOGF_ERROR("Unknown error: focuser Reverse direction value (%s)", res);
356         return false;
357     }
358 
359     return true;
360 }
361 
readStepMode()362 bool MyFocuserPro2::readStepMode()
363 {
364     char res[ML_RES] = {0};
365 
366     if (sendCommand(":29#", res) == false)
367         return false;
368 
369     if (strcmp(res, "S1#") == 0)
370         StepModeS[FOCUS_FULL_STEP].s = ISS_ON;
371     else if (strcmp(res, "S2#") == 0)
372         StepModeS[FOCUS_HALF_STEP].s = ISS_ON;
373     else if (strcmp(res, "S4#") == 0)
374         StepModeS[FOCUS_QUARTER_STEP].s = ISS_ON;
375     else if (strcmp(res, "S8#") == 0)
376         StepModeS[FOCUS_EIGHTH_STEP].s = ISS_ON;
377     else if (strcmp(res, "S16#") == 0)
378         StepModeS[FOCUS_SIXTEENTH_STEP].s = ISS_ON;
379     else if (strcmp(res, "S32#") == 0)
380         StepModeS[FOCUS_THIRTYSECOND_STEP].s = ISS_ON;
381     else
382     {
383         LOGF_ERROR("Unknown error: focuser Step Mode value (%s)", res);
384         return false;
385     }
386 
387     return true;
388 }
389 
readTemperature()390 bool MyFocuserPro2::readTemperature()
391 {
392     char res[ML_RES] = {0};
393 
394     if (sendCommand(":06#", res) == false)
395         return false;
396 
397     double temp = 0;
398     int rc = sscanf(res, "Z%lf#", &temp);
399     if (rc > 0)
400         // Signed hex
401         TemperatureN[0].value = temp;
402     else
403     {
404         LOGF_ERROR("Unknown error: focuser temperature value (%s)", res);
405         return false;
406     }
407 
408     return true;
409 }
410 
readTempCompensateEnable()411 bool MyFocuserPro2::readTempCompensateEnable()
412 {
413     char res[ML_RES] = {0};
414 
415     if (sendCommand(":24#", res) == false)
416         return false;
417 
418     uint32_t temp = 0;
419 
420     int rc = sscanf(res, "1%u#", &temp);
421 
422     if (rc > 0)
423 
424         if(temp == 0)
425             TemperatureCompensateS[TEMP_COMPENSATE_DISABLE].s = ISS_ON;
426         else if (temp == 1)
427             TemperatureCompensateS[TEMP_COMPENSATE_ENABLE].s = ISS_ON;
428         else
429         {
430             LOGF_ERROR("Invalid Response: focuser T.Compensate value (%s)", res);
431             return false;
432         }
433     else
434     {
435         LOGF_ERROR("Unknown error: focuser T.Compensate value (%s)", res);
436         return false;
437     }
438 
439     return true;
440 }
441 
442 
readPosition()443 bool MyFocuserPro2::readPosition()
444 {
445     char res[ML_RES] = {0};
446 
447     if (sendCommand(":00#", res) == false)
448         return false;
449 
450     int32_t pos;
451     int rc = sscanf(res, "%*c%d#", &pos);
452 
453     if (rc > 0)
454         FocusAbsPosN[0].value = pos;
455     else
456     {
457         LOGF_ERROR("Unknown error: focuser position value (%s)", res);
458         return false;
459     }
460 
461     return true;
462 }
463 
readTempeartureCoefficient()464 bool MyFocuserPro2::readTempeartureCoefficient()
465 {
466     char res[ML_RES] = {0};
467 
468     if (sendCommand(":26#", res) == false)
469         return false;
470 
471     int32_t val;
472     int rc = sscanf(res, "B%d#", &val);
473 
474     if (rc > 0)
475         TemperatureSettingN[0].value = val;
476     else
477     {
478         LOGF_ERROR("Unknown error: Temperature Coefficient value (%s)", res);
479         return false;
480     }
481 
482     return true;
483 }
484 
readSpeed()485 bool MyFocuserPro2::readSpeed()
486 {
487     char res[ML_RES] = {0};
488 
489     if (sendCommand(":43#", res) == false)
490         return false;
491 
492     int speed = 0;
493     int rc = sscanf(res, "C%d#", &speed);
494 
495     if (rc > 0)
496     {
497         FocusSpeedN[0].value = speed;
498     }
499     else
500     {
501         LOGF_ERROR("Unknown error: focuser speed value (%s)", res);
502         return false;
503     }
504 
505     return true;
506 }
507 
readMaxPos()508 bool MyFocuserPro2::readMaxPos()
509 {
510     char res[ML_RES] = {0};
511 
512     if (sendCommand(":08#", res) == false)
513         return false;
514 
515     uint32_t maxPos = 0;
516     int rc = sscanf(res, "M%u#", &maxPos);
517 
518     if (rc > 0)
519     {
520         FocusMaxPosN[0].value = maxPos;
521         Focuser::SyncPresets(maxPos);
522     }
523     else
524     {
525         LOGF_ERROR("Unknown error: focuser max position value (%s)", res);
526         return false;
527     }
528 
529     return true;
530 }
531 
readBacklashInSteps()532 bool MyFocuserPro2::readBacklashInSteps()
533 {
534     char res[ML_RES] = {0};
535 
536     if (sendCommand(":78#", res) == false)
537         return false;
538 
539     uint32_t backlash = 0;
540     int rc = sscanf(res, "6%u#", &backlash);
541 
542     if (rc > 0)
543     {
544         BacklashInStepsN[0].value = backlash;
545     }
546     else
547     {
548         BacklashInStepsN[0].value = 0;
549         LOGF_ERROR("Unknown error: focuser Backlash IN value (%s)", res);
550         return false;
551     }
552 
553     return true;
554 }
555 
readBacklashInEnabled()556 bool MyFocuserPro2::readBacklashInEnabled()
557 {
558     char res[ML_RES] = {0};
559 
560     if (sendCommand(":74#", res) == false)
561         return false;
562 
563     uint32_t temp = 0;
564     int rc = sscanf(res, "4%u#", &temp);
565 
566     if (rc > 0)
567     {
568         if(temp == 0)
569             BacklashInS[INDI_DISABLED].s = ISS_ON;
570         else if (temp == 1)
571             BacklashInS[INDI_ENABLED].s = ISS_ON;
572         else
573             LOGF_ERROR("Unknown Repsonse: focuser Backlash IN enabled (%s)", res);
574         return false;
575     }
576     else
577     {
578         LOGF_ERROR("Unknown error: focuser Backlash IN enabled (%s)", res);
579         return false;
580     }
581 }
582 
readBacklashOutSteps()583 bool MyFocuserPro2::readBacklashOutSteps()
584 {
585     char res[ML_RES] = {0};
586 
587     if (sendCommand(":80#", res) == false)
588         return false;
589 
590     uint32_t backlash = 0;
591     int rc = sscanf(res, "7%u#", &backlash);
592 
593     if (rc > 0)
594     {
595         BacklashOutStepsN[0].value = backlash;
596     }
597     else
598     {
599         LOGF_ERROR("Unknown error: focuser Backlash OUT value (%s)", res);
600         return false;
601     }
602 
603     return true;
604 }
605 
readBacklashOutEnabled()606 bool MyFocuserPro2::readBacklashOutEnabled()
607 {
608     char res[ML_RES] = {0};
609 
610     if (sendCommand(":76#", res) == false)
611         return false;
612 
613     uint32_t temp = 0;
614     int rc = sscanf(res, "5%u#", &temp);
615 
616     if (rc > 0)
617     {
618         if(temp == 0)
619             BacklashOutS[INDI_DISABLED].s = ISS_ON;
620         else if (temp == 1)
621             BacklashOutS[INDI_ENABLED].s = ISS_ON;
622         else
623             LOGF_ERROR("Unknown response: focuser Backlash OUT enabled (%s)", res);
624         return false;
625     }
626     else
627     {
628         LOGF_ERROR("Unknown error: focuser Backlash OUT enabled (%s)", res);
629         return false;
630     }
631 
632 }
633 
readDisplayVisible()634 bool MyFocuserPro2::readDisplayVisible()
635 {
636     char res[ML_RES] = {0};
637 
638     if (sendCommand(":37#", res) == false)
639         return false;
640 
641     uint32_t temp = 0;
642 
643     int rc = sscanf(res, "D%u#", &temp);
644 
645     if (rc > 0)
646     {
647         if(temp == 0)
648             DisplayS[DISPLAY_OFF].s = ISS_ON;
649         else if (temp == 1)
650             DisplayS[DISPLAY_ON].s = ISS_ON;
651         else
652         {
653             LOGF_ERROR("Invalid Response: focuser Display value (%s)", res);
654             return false;
655         }
656     }
657     else
658     {
659         LOGF_ERROR("Unknown error: focuser Display value (%s)", res);
660         return false;
661     }
662 
663     return true;
664 }
665 
isMoving()666 bool MyFocuserPro2::isMoving()
667 {
668     char res[ML_RES] = {0};
669 
670     if (sendCommand(":01#", res) == false)
671         return false;
672 
673     uint32_t temp = 0;
674 
675     int rc = sscanf(res, "I%u#", &temp);
676 
677     if (rc > 0)
678     {
679         if(temp == 0)
680             return false;
681         else if (temp == 1)
682             return true;
683         else
684         {
685             LOGF_ERROR("Invalid Response: focuser isMoving value (%s)", res);
686             return false;
687         }
688     }
689     else
690     {
691         LOGF_ERROR("Unknown error: focuser isMoving value (%s)", res);
692         return false;
693     }
694 }
695 
696 
setTemperatureCelsius()697 bool MyFocuserPro2::setTemperatureCelsius()
698 {
699     char cmd[ML_RES] = {0};
700     snprintf(cmd, ML_RES, ":161#");
701     return sendCommand(cmd);
702 }
703 
setTemperatureCoefficient(double coefficient)704 bool MyFocuserPro2::setTemperatureCoefficient(double coefficient)
705 {
706     char cmd[ML_RES] = {0};
707     int coeff = coefficient;
708     snprintf(cmd, ML_RES, ":22%d#", coeff);
709     return sendCommand(cmd);
710 }
711 
SyncFocuser(uint32_t ticks)712 bool MyFocuserPro2::SyncFocuser(uint32_t ticks)
713 {
714     char cmd[ML_RES] = {0};
715     snprintf(cmd, ML_RES, ":31%u#", ticks);
716     return sendCommand(cmd);
717 }
718 
MoveFocuser(uint32_t position)719 bool MyFocuserPro2::MoveFocuser(uint32_t position)
720 {
721     char cmd[ML_RES] = {0};
722     if(isMoving())
723     {
724         AbortFocuser();
725     }
726     snprintf(cmd, ML_RES, ":05%u#", position);
727     return sendCommand(cmd);
728 }
729 
setBacklashInSteps(int16_t steps)730 bool MyFocuserPro2::setBacklashInSteps(int16_t steps)
731 {
732     char cmd[ML_RES] = {0};
733     snprintf(cmd, ML_RES, ":77%d#", steps);
734     return sendCommand(cmd);
735 }
736 
setBacklashInEnabled(bool enabled)737 bool MyFocuserPro2::setBacklashInEnabled(bool enabled)
738 {
739     char cmd[ML_RES] = {0};
740     snprintf(cmd, ML_RES, ":73%c#", enabled ? '1' : '0');
741     return sendCommand(cmd);
742 }
743 
setBacklashOutSteps(int16_t steps)744 bool MyFocuserPro2::setBacklashOutSteps(int16_t steps)
745 {
746     char cmd[ML_RES] = {0};
747     snprintf(cmd, ML_RES, ":79%d#", steps);
748     return sendCommand(cmd);
749 }
750 
setBacklashOutEnabled(bool enabled)751 bool MyFocuserPro2::setBacklashOutEnabled(bool enabled)
752 {
753     char cmd[ML_RES] = {0};
754     snprintf(cmd, ML_RES, ":75%c#", enabled ? '1' : '0');
755     return sendCommand(cmd);
756 }
757 
setCoilPowerState(CoilPower enable)758 bool MyFocuserPro2::setCoilPowerState(CoilPower enable)
759 {
760     char cmd[ML_RES] = {0};
761     snprintf(cmd, ML_RES, ":12%d#", static_cast<int>(enable));
762     return sendCommand(cmd);
763 }
764 
765 
ReverseFocuser(bool enable)766 bool MyFocuserPro2::ReverseFocuser(bool enable)
767 {
768     char cmd[ML_RES] = {0};
769     snprintf(cmd, ML_RES, ":14%c#", enable ? '1' : '0');
770     return sendCommand(cmd);
771 }
772 
773 
setDisplayVisible(DisplayMode enable)774 bool MyFocuserPro2::setDisplayVisible(DisplayMode enable)
775 {
776     char cmd[ML_RES] = {0};
777     snprintf(cmd, ML_RES, ":36%d#", enable);
778     return sendCommand(cmd);
779 }
780 
setGotoHome()781 bool MyFocuserPro2::setGotoHome()
782 {
783     char cmd[ML_RES] = {0};
784     if(isMoving())
785     {
786         AbortFocuser();
787     }
788     snprintf(cmd, ML_RES, ":28#");
789     return sendCommand(cmd);
790 }
791 
setStepMode(FocusStepMode mode)792 bool MyFocuserPro2::setStepMode(FocusStepMode mode)
793 {
794     char cmd[ML_RES] = {0};
795     int setMode = 1 << static_cast<int>(mode);
796     snprintf(cmd, ML_RES, ":30%02d#", setMode);
797     return sendCommand(cmd);
798 }
799 
setSpeed(uint16_t speed)800 bool MyFocuserPro2::setSpeed(uint16_t speed)
801 {
802     char cmd[ML_RES] = {0};
803     snprintf(cmd, ML_RES, ":150%d#", speed);
804     return sendCommand(cmd);
805 }
806 
setTemperatureCompensation(bool enable)807 bool MyFocuserPro2::setTemperatureCompensation(bool enable)
808 {
809     char cmd[ML_RES] = {0};
810     snprintf(cmd, ML_RES, ":23%c#", enable ? '1' : '0');
811     return sendCommand(cmd);
812 }
813 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)814 bool MyFocuserPro2::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
815 {
816     if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
817     {
818         // Focus Step Mode
819         if (strcmp(StepModeSP.name, name) == 0)
820         {
821             int current_mode = IUFindOnSwitchIndex(&StepModeSP);
822 
823             IUUpdateSwitch(&StepModeSP, states, names, n);
824 
825             int target_mode = IUFindOnSwitchIndex(&StepModeSP);
826 
827             if (current_mode == target_mode)
828             {
829                 StepModeSP.s = IPS_OK;
830                 IDSetSwitch(&StepModeSP, nullptr);
831             }
832 
833             bool rc = setStepMode(static_cast<FocusStepMode>(target_mode));
834             if (!rc)
835             {
836                 IUResetSwitch(&StepModeSP);
837                 StepModeS[current_mode].s = ISS_ON;
838                 StepModeSP.s              = IPS_ALERT;
839                 IDSetSwitch(&StepModeSP, nullptr);
840                 return false;
841             }
842 
843             StepModeSP.s = IPS_OK;
844             IDSetSwitch(&StepModeSP, nullptr);
845             return true;
846         }
847 
848         // Goto Home Position
849         if (strcmp(GotoHomeSP.name, name) == 0)
850         {
851             bool rc = setGotoHome();
852             if (!rc)
853             {
854                 IUResetSwitch(&GotoHomeSP);
855                 CoilPowerSP.s              = IPS_ALERT;
856                 IDSetSwitch(&GotoHomeSP, nullptr);
857                 return false;
858             }
859 
860             GotoHomeSP.s = IPS_OK;
861             IDSetSwitch(&GotoHomeSP, nullptr);
862             return true;
863         }
864 
865         // Coil Power Mode
866         if (strcmp(CoilPowerSP.name, name) == 0)
867         {
868             int current_mode = IUFindOnSwitchIndex(&CoilPowerSP);
869 
870             IUUpdateSwitch(&CoilPowerSP, states, names, n);
871 
872             int target_mode = IUFindOnSwitchIndex(&CoilPowerSP);
873 
874             if (current_mode == target_mode)
875             {
876                 CoilPowerSP.s = IPS_OK;
877                 IDSetSwitch(&CoilPowerSP, nullptr);
878             }
879 
880             bool rc = setCoilPowerState(static_cast<CoilPower>(target_mode));
881             if (!rc)
882             {
883                 IUResetSwitch(&CoilPowerSP);
884                 CoilPowerS[current_mode].s = ISS_ON;
885                 CoilPowerSP.s              = IPS_ALERT;
886                 IDSetSwitch(&CoilPowerSP, nullptr);
887                 return false;
888             }
889 
890             CoilPowerSP.s = IPS_OK;
891             IDSetSwitch(&CoilPowerSP, nullptr);
892             return true;
893         }
894 
895 
896         // Display Control
897         if (strcmp(DisplaySP.name, name) == 0)
898         {
899             int current_mode = IUFindOnSwitchIndex(&DisplaySP);
900 
901             IUUpdateSwitch(&DisplaySP, states, names, n);
902 
903             int target_mode = IUFindOnSwitchIndex(&DisplaySP);
904 
905             if (current_mode == target_mode)
906             {
907                 DisplaySP.s = IPS_OK;
908                 IDSetSwitch(&DisplaySP, nullptr);
909             }
910 
911             bool rc = setDisplayVisible(static_cast<DisplayMode>(target_mode));
912             if (!rc)
913             {
914                 IUResetSwitch(&DisplaySP);
915                 DisplayS[current_mode].s = ISS_ON;
916                 DisplaySP.s              = IPS_ALERT;
917                 IDSetSwitch(&DisplaySP, nullptr);
918                 return false;
919             }
920 
921             DisplaySP.s = IPS_OK;
922             IDSetSwitch(&DisplaySP, nullptr);
923             return true;
924         }
925 
926         // Backlash In Enable
927         if (strcmp(BacklashInSP.name, name) == 0)
928         {
929             int current_mode = IUFindOnSwitchIndex(&BacklashInSP);
930 
931             IUUpdateSwitch(&BacklashInSP, states, names, n);
932 
933             int target_mode = IUFindOnSwitchIndex(&BacklashInSP);
934 
935             if (current_mode == target_mode)
936             {
937                 BacklashInSP.s = IPS_OK;
938                 IDSetSwitch(&BacklashInSP, nullptr);
939             }
940 
941             bool rc = setBacklashInEnabled(target_mode == INDI_ENABLED);
942             if (!rc)
943             {
944                 IUResetSwitch(&BacklashInSP);
945                 BacklashInS[current_mode].s = ISS_ON;
946                 BacklashInSP.s              = IPS_ALERT;
947                 IDSetSwitch(&BacklashInSP, nullptr);
948                 return false;
949             }
950 
951             BacklashInSP.s = IPS_OK;
952             IDSetSwitch(&BacklashInSP, nullptr);
953             return true;
954         }
955 
956         // Backlash Out Enable
957         if (strcmp(BacklashOutSP.name, name) == 0)
958         {
959             int current_mode = IUFindOnSwitchIndex(&BacklashOutSP);
960 
961             IUUpdateSwitch(&BacklashOutSP, states, names, n);
962 
963             int target_mode = IUFindOnSwitchIndex(&BacklashOutSP);
964 
965             if (current_mode == target_mode)
966             {
967                 BacklashOutSP.s = IPS_OK;
968                 IDSetSwitch(&BacklashOutSP, nullptr);
969             }
970 
971             bool rc = setBacklashOutEnabled(target_mode == INDI_ENABLED);
972             if (!rc)
973             {
974                 IUResetSwitch(&BacklashOutSP);
975                 BacklashOutS[current_mode].s = ISS_ON;
976                 BacklashOutSP.s              = IPS_ALERT;
977                 IDSetSwitch(&BacklashOutSP, nullptr);
978                 return false;
979             }
980 
981             BacklashOutSP.s = IPS_OK;
982             IDSetSwitch(&BacklashOutSP, nullptr);
983             return true;
984         }
985 
986         // Temperature Compensation Mode
987         if (strcmp(TemperatureCompensateSP.name, name) == 0)
988         {
989             int last_index = IUFindOnSwitchIndex(&TemperatureCompensateSP);
990             IUUpdateSwitch(&TemperatureCompensateSP, states, names, n);
991 
992             bool rc = setTemperatureCompensation((TemperatureCompensateS[0].s == ISS_ON));
993 
994             if (!rc)
995             {
996                 TemperatureCompensateSP.s = IPS_ALERT;
997                 IUResetSwitch(&TemperatureCompensateSP);
998                 TemperatureCompensateS[last_index].s = ISS_ON;
999                 IDSetSwitch(&TemperatureCompensateSP, nullptr);
1000                 return false;
1001             }
1002 
1003             TemperatureCompensateSP.s = IPS_OK;
1004             IDSetSwitch(&TemperatureCompensateSP, nullptr);
1005             return true;
1006         }
1007     }
1008 
1009     return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
1010 }
1011 
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)1012 bool MyFocuserPro2::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
1013 {
1014     if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1015     {
1016         // Temperature Settings
1017         if (strcmp(name, TemperatureSettingNP.name) == 0)
1018         {
1019             IUUpdateNumber(&TemperatureSettingNP, values, names, n);
1020             if (!setTemperatureCoefficient(TemperatureSettingN[0].value))
1021             {
1022                 TemperatureSettingNP.s = IPS_ALERT;
1023                 IDSetNumber(&TemperatureSettingNP, nullptr);
1024                 return false;
1025             }
1026 
1027             TemperatureSettingNP.s = IPS_OK;
1028             IDSetNumber(&TemperatureSettingNP, nullptr);
1029             return true;
1030         }
1031 
1032         // Backlash In
1033         if (strcmp(name, BacklashInStepsNP.name) == 0)
1034         {
1035             IUUpdateNumber(&BacklashInStepsNP, values, names, n);
1036             if (!setBacklashInSteps(BacklashInStepsN[0].value))
1037             {
1038                 BacklashInStepsNP.s = IPS_ALERT;
1039                 IDSetNumber(&BacklashInStepsNP, nullptr);
1040                 return false;
1041             }
1042 
1043             BacklashInStepsNP.s = IPS_OK;
1044             IDSetNumber(&BacklashInStepsNP, nullptr);
1045             return true;
1046         }
1047 
1048         // Backlash Out
1049         if (strcmp(name, BacklashOutStepsNP.name) == 0)
1050         {
1051             IUUpdateNumber(&BacklashOutStepsNP, values, names, n);
1052             if (!setBacklashOutSteps(BacklashOutStepsN[0].value))
1053             {
1054                 BacklashOutStepsNP.s = IPS_ALERT;
1055                 IDSetNumber(&BacklashOutStepsNP, nullptr);
1056                 return false;
1057             }
1058 
1059             BacklashOutStepsNP.s = IPS_OK;
1060             IDSetNumber(&BacklashOutStepsNP, nullptr);
1061             return true;
1062         }
1063 
1064     }
1065 
1066     return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
1067 }
1068 
getStartupValues()1069 void MyFocuserPro2::getStartupValues()
1070 {
1071     readMaxPos();
1072     readPosition();
1073     readTemperature();
1074     readTempeartureCoefficient();
1075     readSpeed();
1076     readTempCompensateEnable();
1077     readStepMode();
1078     readCoilPowerState();
1079     readDisplayVisible();
1080     readReverseDirection();
1081     readBacklashInEnabled();
1082     readBacklashOutEnabled();
1083     readBacklashInSteps();
1084     readBacklashOutSteps();
1085 }
1086 
SetFocuserSpeed(int speed)1087 bool MyFocuserPro2::SetFocuserSpeed(int speed)
1088 {
1089     return setSpeed(speed);
1090 }
1091 
1092 
SetFocuserMaxPosition(uint32_t maxPos)1093 bool MyFocuserPro2::SetFocuserMaxPosition(uint32_t maxPos)
1094 {
1095     char cmd[ML_RES] = {0};
1096 
1097     snprintf(cmd, ML_RES, ":07%06d#", maxPos);
1098 
1099     if(sendCommand(cmd))
1100     {
1101         Focuser::SyncPresets(maxPos);
1102 
1103         return true;
1104     }
1105     return false;
1106 }
1107 
MoveFocuser(FocusDirection dir,int speed,uint16_t duration)1108 IPState MyFocuserPro2::MoveFocuser(FocusDirection dir, int speed, uint16_t duration)
1109 {
1110     if (speed != static_cast<int>(FocusSpeedN[0].value))
1111     {
1112         if (!setSpeed(speed))
1113             return IPS_ALERT;
1114     }
1115 
1116     // either go all the way in or all the way out
1117     // then use timer to stop
1118     if (dir == FOCUS_INWARD)
1119         MoveFocuser(0);
1120     else
1121         MoveFocuser(FocusMaxPosN[0].value);
1122 
1123     IEAddTimer(duration, &MyFocuserPro2::timedMoveHelper, this);
1124     return IPS_BUSY;
1125 }
1126 
timedMoveHelper(void * context)1127 void MyFocuserPro2::timedMoveHelper(void * context)
1128 {
1129     static_cast<MyFocuserPro2 *>(context)->timedMoveCallback();
1130 }
1131 
timedMoveCallback()1132 void MyFocuserPro2::timedMoveCallback()
1133 {
1134     AbortFocuser();
1135     FocusAbsPosNP.s = IPS_IDLE;
1136     FocusRelPosNP.s = IPS_IDLE;
1137     FocusTimerNP.s = IPS_IDLE;
1138     FocusTimerN[0].value = 0;
1139     IDSetNumber(&FocusAbsPosNP, nullptr);
1140     IDSetNumber(&FocusRelPosNP, nullptr);
1141     IDSetNumber(&FocusTimerNP, nullptr);
1142 }
1143 
MoveAbsFocuser(uint32_t targetTicks)1144 IPState MyFocuserPro2::MoveAbsFocuser(uint32_t targetTicks)
1145 {
1146     targetPos = targetTicks;
1147 
1148     if (!MoveFocuser(targetPos))
1149         return IPS_ALERT;
1150 
1151     return IPS_BUSY;
1152 }
1153 
MoveRelFocuser(FocusDirection dir,uint32_t ticks)1154 IPState MyFocuserPro2::MoveRelFocuser(FocusDirection dir, uint32_t ticks)
1155 {
1156     int32_t newPosition = 0;
1157 
1158     if (dir == FOCUS_INWARD)
1159         newPosition = FocusAbsPosN[0].value - ticks;
1160     else
1161         newPosition = FocusAbsPosN[0].value + ticks;
1162 
1163     // Clamp
1164     newPosition = std::max(0, std::min(static_cast<int32_t>(FocusAbsPosN[0].max), newPosition));
1165     if (!MoveFocuser(newPosition))
1166         return IPS_ALERT;
1167 
1168     FocusRelPosN[0].value = ticks;
1169     FocusRelPosNP.s       = IPS_BUSY;
1170 
1171     return IPS_BUSY;
1172 }
1173 
TimerHit()1174 void MyFocuserPro2::TimerHit()
1175 {
1176     if (!isConnected())
1177     {
1178         SetTimer(getCurrentPollingPeriod());
1179         return;
1180     }
1181 
1182     bool rc = readPosition();
1183     if (rc)
1184     {
1185         if (fabs(lastPos - FocusAbsPosN[0].value) > 5)
1186         {
1187             IDSetNumber(&FocusAbsPosNP, nullptr);
1188             lastPos = FocusAbsPosN[0].value;
1189         }
1190     }
1191 
1192     rc = readTemperature();
1193     if (rc)
1194     {
1195         if (fabs(lastTemperature - TemperatureN[0].value) >= 0.5)
1196         {
1197             IDSetNumber(&TemperatureNP, nullptr);
1198             lastTemperature = TemperatureN[0].value;
1199         }
1200     }
1201 
1202     if (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY)
1203     {
1204         if (!isMoving())
1205         {
1206             FocusAbsPosNP.s = IPS_OK;
1207             FocusRelPosNP.s = IPS_OK;
1208             IDSetNumber(&FocusAbsPosNP, nullptr);
1209             IDSetNumber(&FocusRelPosNP, nullptr);
1210             lastPos = FocusAbsPosN[0].value;
1211             LOG_INFO("Focuser reached requested position.");
1212         }
1213     }
1214 
1215     SetTimer(getCurrentPollingPeriod());
1216 }
1217 
AbortFocuser()1218 bool MyFocuserPro2::AbortFocuser()
1219 {
1220     return sendCommand(":27#");
1221 }
1222 
saveConfigItems(FILE * fp)1223 bool MyFocuserPro2::saveConfigItems(FILE * fp)
1224 {
1225     Focuser::saveConfigItems(fp);
1226 
1227     IUSaveConfigSwitch(fp, &StepModeSP);
1228 
1229     return true;
1230 }
1231 
sendCommand(const char * cmd,char * res)1232 bool MyFocuserPro2::sendCommand(const char * cmd, char * res)
1233 {
1234     int nbytes_written = 0, nbytes_read = 0, rc = -1;
1235 
1236     tcflush(PortFD, TCIOFLUSH);
1237 
1238     LOGF_DEBUG("CMD <%s>", cmd);
1239 
1240     if ((rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK)
1241     {
1242         char errstr[MAXRBUF] = {0};
1243         tty_error_msg(rc, errstr, MAXRBUF);
1244         LOGF_ERROR("Serial write error: %s.", errstr);
1245         return false;
1246     }
1247 
1248     if (res == nullptr)
1249     {
1250         tcdrain(PortFD);
1251         return true;
1252     }
1253 
1254     if ((rc = tty_nread_section(PortFD, res, ML_RES, ML_DEL, ML_TIMEOUT, &nbytes_read)) != TTY_OK)
1255     {
1256         char errstr[MAXRBUF] = {0};
1257         tty_error_msg(rc, errstr, MAXRBUF);
1258         LOGF_ERROR("Serial read error: %s.", errstr);
1259         return false;
1260     }
1261 
1262     LOGF_DEBUG("RES <%s>", res);
1263 
1264     tcflush(PortFD, TCIOFLUSH);
1265 
1266     return true;
1267 }
1268