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(¤tTicks);
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