1 /*******************************************************************************
2   Copyright(c) 2015 Jasem Mutlaq. All rights reserved.
3 
4  PerfectStar Focuser
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License version 2 as published by the Free Software Foundation.
9  .
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Library General Public License for more details.
14  .
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB.  If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 *******************************************************************************/
20 
21 #include "perfectstar.h"
22 
23 #include <cmath>
24 #include <cstring>
25 #include <memory>
26 
27 #define PERFECTSTAR_TIMEOUT 1000 /* 1000 ms */
28 
29 #define FOCUS_SETTINGS_TAB "Settings"
30 
31 // We declare an auto pointer to PerfectStar.
32 static std::unique_ptr<PerfectStar> perfectStar(new PerfectStar());
33 
PerfectStar()34 PerfectStar::PerfectStar()
35 {
36     FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE | FOCUSER_CAN_ABORT | FOCUSER_CAN_SYNC);
37     setSupportedConnections(CONNECTION_NONE);
38 }
39 
Connect()40 bool PerfectStar::Connect()
41 {
42     sim = isSimulation();
43 
44     if (sim)
45     {
46         SetTimer(getCurrentPollingPeriod());
47         return true;
48     }
49 
50     handle = hid_open(0x04D8, 0xF812, nullptr);
51 
52     if (handle == nullptr)
53     {
54         LOG_ERROR("No PerfectStar focuser found.");
55         return false;
56     }
57     else
58         SetTimer(getCurrentPollingPeriod());
59 
60     return (handle != nullptr);
61 }
62 
Disconnect()63 bool PerfectStar::Disconnect()
64 {
65     if (!sim)
66     {
67         hid_close(handle);
68         hid_exit();
69     }
70 
71     return true;
72 }
73 
getDefaultName()74 const char *PerfectStar::getDefaultName()
75 {
76     return (const char *)"PerfectStar";
77 }
78 
initProperties()79 bool PerfectStar::initProperties()
80 {
81     INDI::Focuser::initProperties();
82 
83     // Max Position
84 //    IUFillNumber(&MaxPositionN[0], "Steps", "", "%.f", 0, 500000, 0., 10000);
85 //    IUFillNumberVector(&MaxPositionNP, MaxPositionN, 1, getDeviceName(), "Max Position", "", FOCUS_SETTINGS_TAB, IP_RW,
86 //                       0, IPS_IDLE);
87 
88 //    // Sync to a particular position
89 //    IUFillNumber(&SyncN[0], "Ticks", "", "%.f", 0, 100000, 100., 0.);
90 //    IUFillNumberVector(&SyncNP, SyncN, 1, getDeviceName(), "Sync", "", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
91 
92 //    FocusAbsPosN[0].min = SyncN[0].min = 0;
93 //    FocusAbsPosN[0].max = SyncN[0].max = MaxPositionN[0].value;
94 //    FocusAbsPosN[0].step = SyncN[0].step = MaxPositionN[0].value / 50.0;
95 //    FocusAbsPosN[0].value                = 0;
96 
97 //    FocusRelPosN[0].max   = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 2;
98 //    FocusRelPosN[0].step  = FocusRelPosN[0].max / 100.0;
99 //    FocusRelPosN[0].value = 100;
100 
101     addSimulationControl();
102 
103     return true;
104 }
105 
updateProperties()106 bool PerfectStar::updateProperties()
107 {
108     INDI::Focuser::updateProperties();
109 
110 //    if (isConnected())
111 //    {
112 //        defineProperty(&SyncNP);
113 //        defineProperty(&MaxPositionNP);
114 //    }
115 //    else
116 //    {
117 //        deleteProperty(SyncNP.name);
118 //        deleteProperty(MaxPositionNP.name);
119 //    }
120 
121     return true;
122 }
123 
TimerHit()124 void PerfectStar::TimerHit()
125 {
126     if (!isConnected())
127         return;
128 
129     uint32_t currentTicks = 0;
130 
131     bool rc = getPosition(&currentTicks);
132 
133     if (rc)
134         FocusAbsPosN[0].value = currentTicks;
135 
136     getStatus(&status);
137 
138     if (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY)
139     {
140         if (sim)
141         {
142             if (FocusAbsPosN[0].value < targetPosition)
143                 simPosition += 500;
144             else
145                 simPosition -= 500;
146 
147             if (std::abs((int64_t)simPosition - (int64_t)targetPosition) < 500)
148             {
149                 FocusAbsPosN[0].value = targetPosition;
150                 simPosition           = FocusAbsPosN[0].value;
151                 status                = PS_NOOP;
152             }
153 
154             FocusAbsPosN[0].value = simPosition;
155         }
156 
157         if (status == PS_HALT && targetPosition == FocusAbsPosN[0].value)
158         {
159             if (FocusRelPosNP.s == IPS_BUSY)
160             {
161                 FocusRelPosNP.s = IPS_OK;
162                 IDSetNumber(&FocusRelPosNP, nullptr);
163             }
164 
165             FocusAbsPosNP.s = IPS_OK;
166             LOG_DEBUG("Focuser reached target position.");
167         }
168         else if (status == PS_NOOP)
169         {
170             if (FocusRelPosNP.s == IPS_BUSY)
171             {
172                 FocusRelPosNP.s = IPS_OK;
173                 IDSetNumber(&FocusRelPosNP, nullptr);
174             }
175 
176             FocusAbsPosNP.s = IPS_OK;
177             LOG_INFO("Focuser reached home position.");
178         }
179     }
180 
181     IDSetNumber(&FocusAbsPosNP, nullptr);
182 
183     SetTimer(getCurrentPollingPeriod());
184 }
185 
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)186 bool PerfectStar::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
187 {
188     if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
189     {
190         // Max Travel
191 //        if (strcmp(MaxPositionNP.name, name) == 0)
192 //        {
193 //            IUUpdateNumber(&MaxPositionNP, values, names, n);
194 
195 //            if (MaxPositionN[0].value > 0)
196 //            {
197 //                FocusAbsPosN[0].min = SyncN[0].min = 0;
198 //                FocusAbsPosN[0].max = SyncN[0].max = MaxPositionN[0].value;
199 //                FocusAbsPosN[0].step = SyncN[0].step = MaxPositionN[0].value / 50.0;
200 
201 //                FocusRelPosN[0].max  = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 2;
202 //                FocusRelPosN[0].step = FocusRelPosN[0].max / 100.0;
203 //                FocusRelPosN[0].min  = 0;
204 
205 //                IUUpdateMinMax(&FocusAbsPosNP);
206 //                IUUpdateMinMax(&FocusRelPosNP);
207 //                IUUpdateMinMax(&SyncNP);
208 
209 //                LOGF_INFO("Focuser absolute limits: min (%g) max (%g)", FocusAbsPosN[0].min,
210 //                       FocusAbsPosN[0].max);
211 //            }
212 
213 //            MaxPositionNP.s = IPS_OK;
214 //            IDSetNumber(&MaxPositionNP, nullptr);
215 //            return true;
216 //        }
217 
218         // Sync
219 //        if (strcmp(SyncNP.name, name) == 0)
220 //        {
221 //            IUUpdateNumber(&SyncNP, values, names, n);
222 //            if (!sync(SyncN[0].value))
223 //                SyncNP.s = IPS_ALERT;
224 //            else
225 //                SyncNP.s = IPS_OK;
226 
227 //            IDSetNumber(&SyncNP, nullptr);
228 //            return true;
229 //        }
230     }
231 
232     return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
233 }
234 
MoveAbsFocuser(uint32_t targetTicks)235 IPState PerfectStar::MoveAbsFocuser(uint32_t targetTicks)
236 {
237     bool rc = setPosition(targetTicks);
238 
239     if (!rc)
240         return IPS_ALERT;
241 
242     targetPosition = targetTicks;
243 
244     rc = setStatus(PS_GOTO);
245 
246     if (!rc)
247         return IPS_ALERT;
248 
249     FocusAbsPosNP.s = IPS_BUSY;
250 
251     return IPS_BUSY;
252 }
253 
MoveRelFocuser(FocusDirection dir,uint32_t ticks)254 IPState PerfectStar::MoveRelFocuser(FocusDirection dir, uint32_t ticks)
255 {
256     uint32_t finalTicks = FocusAbsPosN[0].value + (ticks * (dir == FOCUS_INWARD ? -1 : 1));
257 
258     return MoveAbsFocuser(finalTicks);
259 }
260 
setPosition(uint32_t ticks)261 bool PerfectStar::setPosition(uint32_t ticks)
262 {
263     int rc = 0;
264     unsigned char command[3];
265     unsigned char response[3];
266 
267     // 20 bit resolution position. 4 high bits + 16 lower bits
268 
269     // Send 4 high bits first
270     command[0] = 0x28;
271     command[1] = (ticks & 0x40000) >> 16;
272 
273     LOGF_DEBUG("Set Position (%ld)", ticks);
274     LOGF_DEBUG("CMD (%02X %02X)", command[0], command[1]);
275 
276     if (sim)
277         rc = 2;
278     else
279         rc = hid_write(handle, command, 2);
280 
281     if (rc < 0)
282     {
283         LOGF_ERROR("setPosition: Error writing to device (%s)", hid_error(handle));
284         return false;
285     }
286 
287     if (sim)
288     {
289         rc          = 2;
290         response[0] = 0x28;
291         response[1] = command[1];
292     }
293     else
294         rc = hid_read_timeout(handle, response, 2, PERFECTSTAR_TIMEOUT);
295 
296     if (rc < 0)
297     {
298         LOGF_ERROR("setPosition: Error reading from device (%s)", hid_error(handle));
299         return false;
300     }
301 
302     LOGF_DEBUG("RES (%02X %02X)", response[0], response[1]);
303 
304     // Send lower 16 bit
305     command[0] = 0x20;
306     // Low Byte
307     command[1] = ticks & 0xFF;
308     // High Byte
309     command[2] = (ticks & 0xFF00) >> 8;
310 
311     LOGF_DEBUG("CMD (%02X %02X %02X)", command[0], command[1], command[2]);
312 
313     if (sim)
314         rc = 3;
315     else
316         rc = hid_write(handle, command, 3);
317 
318     if (rc < 0)
319     {
320         LOGF_ERROR("setPosition: Error writing to device (%s)", hid_error(handle));
321         return false;
322     }
323 
324     if (sim)
325     {
326         rc          = 3;
327         response[0] = command[0];
328         response[1] = command[1];
329         response[2] = command[2];
330     }
331     else
332         rc = hid_read_timeout(handle, response, 3, PERFECTSTAR_TIMEOUT);
333 
334     if (rc < 0)
335     {
336         LOGF_ERROR("setPosition: Error reading from device (%s)", hid_error(handle));
337         return false;
338     }
339 
340     LOGF_DEBUG("RES (%02X %02X %02X)", response[0], response[1], response[2]);
341 
342     targetPosition = ticks;
343 
344     // TODO add checking later
345     return true;
346 }
347 
getPosition(uint32_t * ticks)348 bool PerfectStar::getPosition(uint32_t *ticks)
349 {
350     int rc       = 0;
351     uint32_t pos = 0;
352     unsigned char command[1];
353     unsigned char response[3];
354 
355     // 20 bit resolution position. 4 high bits + 16 lower bits
356 
357     // Get 4 high bits first
358     command[0] = 0x29;
359 
360     LOG_DEBUG("Get Position (High 4 bits)");
361     LOGF_DEBUG("CMD (%02X)", command[0]);
362 
363     if (sim)
364         rc = 2;
365     else
366         rc = hid_write(handle, command, 1);
367 
368     if (rc < 0)
369     {
370         LOGF_ERROR("getPosition: Error writing to device (%s)", hid_error(handle));
371         return false;
372     }
373 
374     if (sim)
375     {
376         rc          = 2;
377         response[0] = command[0];
378         response[1] = simPosition >> 16;
379     }
380     else
381         rc = hid_read_timeout(handle, response, 2, PERFECTSTAR_TIMEOUT);
382 
383     if (rc < 0)
384     {
385         LOGF_ERROR("getPosition: Error reading from device (%s)", hid_error(handle));
386         return false;
387     }
388 
389     LOGF_DEBUG("RES (%02X %02X)", response[0], response[1]);
390 
391     // Store 4 high bits part of a 20 bit number
392     pos = response[1] << 16;
393 
394     // Get 16 lower bits
395     command[0] = 0x21;
396 
397     LOG_DEBUG("Get Position (Lower 16 bits)");
398     LOGF_DEBUG("CMD (%02X)", command[0]);
399 
400     if (sim)
401         rc = 1;
402     else
403         rc = hid_write(handle, command, 1);
404 
405     if (rc < 0)
406     {
407         LOGF_ERROR("getPosition: Error writing to device (%s)", hid_error(handle));
408         return false;
409     }
410 
411     if (sim)
412     {
413         rc          = 3;
414         response[0] = command[0];
415         response[1] = simPosition & 0xFF;
416         response[2] = (simPosition & 0xFF00) >> 8;
417     }
418     else
419         rc = hid_read_timeout(handle, response, 3, PERFECTSTAR_TIMEOUT);
420 
421     if (rc < 0)
422     {
423         LOGF_ERROR("getPosition: Error reading from device (%s)", hid_error(handle));
424         return false;
425     }
426 
427     LOGF_DEBUG("RES (%02X %02X %02X)", response[0], response[1], response[2]);
428 
429     // Res[1] is lower byte and Res[2] is high byte. Combine them and add them to ticks.
430     pos |= response[1] | response[2] << 8;
431 
432     *ticks = pos;
433 
434     LOGF_DEBUG("Position: %ld", pos);
435 
436     return true;
437 }
438 
setStatus(PS_STATUS targetStatus)439 bool PerfectStar::setStatus(PS_STATUS targetStatus)
440 {
441     int rc = 0;
442     unsigned char command[2];
443     unsigned char response[3];
444 
445     command[0] = 0x10;
446     command[1] = (targetStatus == PS_HALT) ? 0xFF : targetStatus;
447 
448     LOGF_DEBUG("CMD (%02X %02X)", command[0], command[1]);
449 
450     if (sim)
451         rc = 2;
452     else
453         rc = hid_write(handle, command, 2);
454 
455     if (rc < 0)
456     {
457         LOGF_ERROR("setStatus: Error writing to device (%s)", hid_error(handle));
458         return false;
459     }
460 
461     if (sim)
462     {
463         rc          = 3;
464         response[0] = command[0];
465         response[1] = 0;
466         response[2] = command[1];
467         status      = targetStatus;
468         // Convert Goto to either "moving in" or "moving out" status
469         if (status == PS_GOTO)
470         {
471             // Moving in state
472             if (targetPosition < FocusAbsPosN[0].value)
473                 status = PS_IN;
474             else
475                 // Moving out state
476                 status = PS_OUT;
477         }
478     }
479     else
480         rc = hid_read_timeout(handle, response, 3, PERFECTSTAR_TIMEOUT);
481 
482     if (rc < 0)
483     {
484         LOGF_ERROR("setStatus: Error reading from device (%s)", hid_error(handle));
485         return false;
486     }
487 
488     LOGF_DEBUG("RES (%02X %02X %02X)", response[0], response[1], response[2]);
489 
490     if (response[1] == 0xFF)
491     {
492         LOG_ERROR("setStatus: Invalid state change.");
493         return false;
494     }
495 
496     return true;
497 }
498 
getStatus(PS_STATUS * currentStatus)499 bool PerfectStar::getStatus(PS_STATUS *currentStatus)
500 {
501     int rc = 0;
502     unsigned char command[1];
503     unsigned char response[2];
504 
505     command[0] = 0x11;
506 
507     LOGF_DEBUG("CMD (%02X)", command[0]);
508 
509     if (sim)
510         rc = 1;
511     else
512         rc = hid_write(handle, command, 1);
513 
514     if (rc < 0)
515     {
516         LOGF_ERROR("getStatus: Error writing to device (%s)", hid_error(handle));
517         return false;
518     }
519 
520     if (sim)
521     {
522         rc          = 2;
523         response[0] = command[0];
524         response[1] = status;
525         // Halt/SetPos is state = 0 "not moving".
526         if (response[1] == PS_HALT || response[1] == PS_SETPOS)
527             response[1] = 0;
528     }
529     else
530         rc = hid_read_timeout(handle, response, 2, PERFECTSTAR_TIMEOUT);
531 
532     if (rc < 0)
533     {
534         LOGF_ERROR("getStatus: Error reading from device (%s)", hid_error(handle));
535         return false;
536     }
537 
538     LOGF_DEBUG("RES (%02X %02X)", response[0], response[1]);
539 
540     switch (response[1])
541     {
542         case 0:
543             *currentStatus = PS_HALT;
544             LOG_DEBUG("State: Not moving.");
545             break;
546 
547         case 1:
548             *currentStatus = PS_IN;
549             LOG_DEBUG("State: Moving in.");
550             break;
551 
552         case 3:
553             *currentStatus = PS_GOTO;
554             LOG_DEBUG("State: Goto.");
555             break;
556 
557         case 2:
558             *currentStatus = PS_OUT;
559             LOG_DEBUG("State: Moving out.");
560             break;
561 
562         case 5:
563             *currentStatus = PS_LOCKED;
564             LOG_DEBUG("State: Locked.");
565             break;
566 
567         default:
568             LOGF_WARN("Warning: Unknown status (%d)", response[1]);
569             return false;
570             break;
571     }
572 
573     return true;
574 }
575 
AbortFocuser()576 bool PerfectStar::AbortFocuser()
577 {
578     return setStatus(PS_HALT);
579 }
580 
581 //bool PerfectStar::sync(uint32_t ticks)
SyncFocuser(uint32_t ticks)582 bool PerfectStar::SyncFocuser(uint32_t ticks)
583 {
584     bool rc = setPosition(ticks);
585 
586     if (!rc)
587         return false;
588 
589     simPosition = ticks;
590 
591     rc = setStatus(PS_SETPOS);
592 
593     return rc;
594 }
595 
596 //bool PerfectStar::saveConfigItems(FILE *fp)
597 //{
598 //    INDI::Focuser::saveConfigItems(fp);
599 
600 //    IUSaveConfigNumber(fp, &MaxPositionNP);
601 
602 //    return true;
603 //}
604