1 /*******************************************************************************
2   Copyright(c) 2019 Jasem Mutlaq. All rights reserved.
3 
4  Starlight Instruments EFS 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 "si_efs.h"
22 
23 #include <cmath>
24 #include <cstring>
25 #include <memory>
26 
27 #define FOCUS_SETTINGS_TAB "Settings"
28 
29 static std::unique_ptr<SIEFS> siefs(new SIEFS());
30 
31 const std::map<SIEFS::SI_COMMANDS, std::string> SIEFS::CommandsMap =
32 {
33     {SIEFS::SI_NOOP, "No Operation"},
34     {SIEFS::SI_IN, "Moving Inwards"},
35     {SIEFS::SI_OUT, "Moving Outwards"},
36     {SIEFS::SI_GOTO, "Goto"},
37     {SIEFS::SI_SET_POS, "Set Position"},
38     {SIEFS::SI_MAX_POS, "Set Max Position"},
39     {SIEFS::SI_FAST_IN, "Fast In"},
40     {SIEFS::SI_FAST_OUT, "Fast Out"},
41     {SIEFS::SI_HALT, "Halt"},
42 };
43 
44 const std::map<SIEFS::SI_MOTOR, std::string> SIEFS::MotorMap =
45 {
46     {SIEFS::SI_NOT_MOVING, "Idle"},
47     {SIEFS::SI_MOVING_IN, "Moving Inwards"},
48     {SIEFS::SI_MOVING_OUT, "Moving Outwards"},
49     {SIEFS::SI_LOCKED, "Locked"},
50 };
51 
SIEFS()52 SIEFS::SIEFS()
53 {
54     setVersion(0, 1);
55 
56     FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE | FOCUSER_CAN_ABORT | FOCUSER_CAN_SYNC);
57     setSupportedConnections(CONNECTION_NONE);
58 }
59 
Connect()60 bool SIEFS::Connect()
61 {
62     if (isSimulation())
63     {
64         SetTimer(getCurrentPollingPeriod());
65         return true;
66     }
67 
68     handle = hid_open(0x04D8, 0xF056, nullptr);
69 
70     if (handle == nullptr)
71     {
72         LOG_ERROR("No SIEFS focuser found.");
73         return false;
74     }
75     else
76     {
77         uint32_t maximumPosition = 0;
78         bool rc = getMaxPosition(&maximumPosition);
79         if (rc)
80         {
81             FocusMaxPosN[0].value = maximumPosition;
82 
83             FocusAbsPosN[0].min = 0;
84             FocusAbsPosN[0].max = FocusMaxPosN[0].value;
85             FocusAbsPosN[0].step = FocusMaxPosN[0].value / 50.0;
86 
87             FocusSyncN[0].min = 0;
88             FocusSyncN[0].max = FocusMaxPosN[0].value;
89             FocusSyncN[0].step = FocusMaxPosN[0].value / 50.0;
90 
91             FocusRelPosN[0].max  = FocusMaxPosN[0].value / 2;
92             FocusRelPosN[0].step = FocusMaxPosN[0].value / 100.0;
93             FocusRelPosN[0].min  = 0;
94         }
95 
96         SetTimer(getCurrentPollingPeriod());
97     }
98 
99     return (handle != nullptr);
100 }
101 
Disconnect()102 bool SIEFS::Disconnect()
103 {
104     if (isSimulation() == false)
105     {
106         hid_close(handle);
107         hid_exit();
108     }
109 
110     return true;
111 }
112 
getDefaultName()113 const char *SIEFS::getDefaultName()
114 {
115     return "SI EFS";
116 }
117 
initProperties()118 bool SIEFS::initProperties()
119 {
120     INDI::Focuser::initProperties();
121 
122     addSimulationControl();
123 
124     return true;
125 }
126 
TimerHit()127 void SIEFS::TimerHit()
128 {
129     if (!isConnected())
130         return;
131 
132     uint32_t currentTicks = 0;
133 
134     bool rc = getAbsPosition(&currentTicks);
135 
136     if (rc)
137         FocusAbsPosN[0].value = currentTicks;
138 
139     getStatus();
140 
141     if (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY)
142     {
143         if (isSimulation())
144         {
145             if (FocusAbsPosN[0].value < targetPosition)
146                 simPosition += 500;
147             else
148                 simPosition -= 500;
149 
150             if (std::abs(simPosition - static_cast<int32_t>(targetPosition)) < 500)
151             {
152                 FocusAbsPosN[0].value = targetPosition;
153                 simPosition = FocusAbsPosN[0].value;
154                 m_Motor = SI_NOT_MOVING;
155             }
156 
157             FocusAbsPosN[0].value = simPosition;
158         }
159 
160         if (m_Motor == SI_NOT_MOVING && targetPosition == FocusAbsPosN[0].value)
161         {
162             if (FocusRelPosNP.s == IPS_BUSY)
163             {
164                 FocusRelPosNP.s = IPS_OK;
165                 IDSetNumber(&FocusRelPosNP, nullptr);
166             }
167 
168             FocusAbsPosNP.s = IPS_OK;
169             LOG_DEBUG("Focuser reached target position.");
170         }
171     }
172 
173     IDSetNumber(&FocusAbsPosNP, nullptr);
174 
175     SetTimer(getCurrentPollingPeriod());
176 }
177 
MoveAbsFocuser(uint32_t targetTicks)178 IPState SIEFS::MoveAbsFocuser(uint32_t targetTicks)
179 {
180     bool rc = setAbsPosition(targetTicks);
181 
182     if (!rc)
183         return IPS_ALERT;
184 
185     targetPosition = targetTicks;
186 
187     rc = sendCommand(SI_GOTO);
188 
189     if (!rc)
190         return IPS_ALERT;
191 
192     FocusAbsPosNP.s = IPS_BUSY;
193 
194     return IPS_BUSY;
195 }
196 
MoveRelFocuser(FocusDirection dir,uint32_t ticks)197 IPState SIEFS::MoveRelFocuser(FocusDirection dir, uint32_t ticks)
198 {
199     int direction = (dir == FOCUS_INWARD) ? -1 : 1;
200     int reversed = (FocusReverseS[INDI_ENABLED].s == ISS_ON) ? -1 : 1;
201     int relative = static_cast<int>(ticks);
202 
203     int targetAbsPosition = FocusAbsPosN[0].value + (relative * direction * reversed);
204 
205     targetAbsPosition = std::min(static_cast<uint32_t>(FocusMaxPosN[0].value)
206                                  , static_cast<uint32_t>(std::max(static_cast<int>(FocusAbsPosN[0].min), targetAbsPosition)));
207 
208     return MoveAbsFocuser(targetAbsPosition);
209 }
210 
setPosition(uint32_t ticks,uint8_t cmdCode)211 bool SIEFS::setPosition(uint32_t ticks, uint8_t cmdCode)
212 {
213     int rc = 0;
214     uint8_t command[3];
215     uint8_t response[3];
216 
217     // 20 bit resolution position. 4 high bits + 16 lower bits
218 
219     // Send 4 high bits first
220     command[0] = cmdCode + 8;
221     command[1] = (ticks & 0x40000) >> 16;
222 
223     LOGF_DEBUG("Set %s Position (%ld)", cmdCode == 0x20 ? "Absolute" : "Maximum", ticks);
224     LOGF_DEBUG("CMD <%02X %02X>", command[0], command[1]);
225 
226     if (isSimulation())
227         rc = 2;
228     else
229         rc = hid_write(handle, command, 2);
230 
231     if (rc < 0)
232     {
233         LOGF_ERROR("setPosition: Error writing to device (%s)", hid_error(handle));
234         return false;
235     }
236 
237     if (isSimulation())
238     {
239         rc          = 2;
240         response[0] = cmdCode + 8;
241         response[1] = command[1];
242     }
243     else
244         rc = hid_read_timeout(handle, response, 2, SI_TIMEOUT);
245 
246     if (rc < 0)
247     {
248         LOGF_ERROR("setPosition: Error reading from device (%s)", hid_error(handle));
249         return false;
250     }
251 
252     LOGF_DEBUG("RES <%02X %02X>", response[0], response[1]);
253 
254     // Send lower 16 bit
255     command[0] = cmdCode;
256     // Low Byte
257     command[1] = ticks & 0xFF;
258     // High Byte
259     command[2] = (ticks & 0xFF00) >> 8;
260 
261     LOGF_DEBUG("CMD <%02X %02X %02X>", command[0], command[1], command[2]);
262 
263     if (isSimulation())
264         rc = 3;
265     else
266         rc = hid_write(handle, command, 3);
267 
268     if (rc < 0)
269     {
270         LOGF_ERROR("setPosition: Error writing to device (%s)", hid_error(handle));
271         return false;
272     }
273 
274     if (isSimulation())
275     {
276         rc          = 3;
277         response[0] = command[0];
278         response[1] = command[1];
279         response[2] = command[2];
280     }
281     else
282         rc = hid_read_timeout(handle, response, 3, SI_TIMEOUT);
283 
284     if (rc < 0)
285     {
286         LOGF_ERROR("setPosition: Error reading from device (%s)", hid_error(handle));
287         return false;
288     }
289 
290     LOGF_DEBUG("RES <%02X %02X %02X>", response[0], response[1], response[2]);
291 
292     targetPosition = ticks;
293 
294     // TODO add checking later
295     return true;
296 }
297 
getPosition(uint32_t * ticks,uint8_t cmdCode)298 bool SIEFS::getPosition(uint32_t *ticks, uint8_t cmdCode)
299 {
300     int rc       = 0;
301     uint32_t pos = 0;
302     uint8_t command[1] = {0};
303     uint8_t response[3] = {0};
304 
305     // 20 bit resolution position. 4 high bits + 16 lower bits
306 
307     // Get 4 high bits first
308     command[0] = cmdCode + 8;
309 
310     LOGF_DEBUG("Get %s Position (High 4 bits)", cmdCode == 0x21 ? "Absolute" : "Maximum");
311     LOGF_DEBUG("CMD <%02X>", command[0]);
312 
313     if (isSimulation())
314         rc = 2;
315     else
316         rc = hid_write(handle, command, 1);
317 
318     if (rc < 0)
319     {
320         LOGF_ERROR("getPosition: Error writing to device (%s)", hid_error(handle));
321         return false;
322     }
323 
324     if (isSimulation())
325     {
326         rc          = 2;
327         response[0] = command[0];
328         response[1] = simPosition >> 16;
329     }
330     else
331         rc = hid_read_timeout(handle, response, 2, SI_TIMEOUT);
332 
333     if (rc < 0)
334     {
335         LOGF_ERROR("getPosition: Error reading from device (%s)", hid_error(handle));
336         return false;
337     }
338 
339     LOGF_DEBUG("RES <%02X %02X>", response[0], response[1]);
340 
341     // Store 4 high bits part of a 20 bit number
342     pos = response[1] << 16;
343 
344     // Get 16 lower bits
345     command[0] = cmdCode;
346 
347     LOGF_DEBUG("Get %s Position (Lower 16 bits)", cmdCode == 0x21 ? "Absolute" : "Maximum");
348     LOGF_DEBUG("CMD <%02X>", command[0]);
349 
350     if (isSimulation())
351         rc = 1;
352     else
353         rc = hid_write(handle, command, 1);
354 
355     if (rc < 0)
356     {
357         LOGF_ERROR("getPosition: Error writing to device (%s)", hid_error(handle));
358         return false;
359     }
360 
361     if (isSimulation())
362     {
363         rc          = 3;
364         response[0] = command[0];
365         response[1] = simPosition & 0xFF;
366         response[2] = (simPosition & 0xFF00) >> 8;
367     }
368     else
369         rc = hid_read_timeout(handle, response, 3, SI_TIMEOUT);
370 
371     if (rc < 0)
372     {
373         LOGF_ERROR("getPosition: Error reading from device (%s)", hid_error(handle));
374         return false;
375     }
376 
377     LOGF_DEBUG("RES <%02X %02X %02X>", response[0], response[1], response[2]);
378 
379     // Res[1] is lower byte and Res[2] is high byte. Combine them and add them to ticks.
380     pos |= response[1] | response[2] << 8;
381 
382     *ticks = pos;
383 
384     LOGF_DEBUG("%s Position: %ld", cmdCode == 0x21 ? "Absolute" : "Maximum", pos);
385 
386     return true;
387 }
388 
setAbsPosition(uint32_t ticks)389 bool SIEFS::setAbsPosition(uint32_t ticks)
390 {
391     return setPosition(ticks, 0x20);
392 }
393 
getAbsPosition(uint32_t * ticks)394 bool SIEFS::getAbsPosition(uint32_t *ticks)
395 {
396     return getPosition(ticks, 0x21);
397 }
398 
setMaxPosition(uint32_t ticks)399 bool SIEFS::setMaxPosition(uint32_t ticks)
400 {
401     return setPosition(ticks, 0x22);
402 }
403 
getMaxPosition(uint32_t * ticks)404 bool SIEFS::getMaxPosition(uint32_t *ticks)
405 {
406     return getPosition(ticks, 0x23);
407 }
408 
sendCommand(SI_COMMANDS targetCommand)409 bool SIEFS::sendCommand(SI_COMMANDS targetCommand)
410 {
411     int rc = 0;
412     uint8_t command[2] = {0};
413     uint8_t response[3] = {0};
414 
415     command[0] = 0x10;
416     command[1] = targetCommand;
417 
418     LOGF_DEBUG("CMD <%02X %02X>", command[0], command[1]);
419 
420     if (isSimulation())
421         rc = 2;
422     else
423         rc = hid_write(handle, command, 2);
424 
425     if (rc < 0)
426     {
427         LOGF_ERROR("setStatus: Error writing to device (%s)", hid_error(handle));
428         return false;
429     }
430 
431     if (isSimulation())
432     {
433         rc          = 3;
434         response[0] = command[0];
435         response[1] = 0;
436         response[2] = command[1];
437         //        m_Motor = targetCommand;
438     }
439     else
440         rc = hid_read_timeout(handle, response, 3, SI_TIMEOUT);
441 
442     if (rc < 0)
443     {
444         LOGF_ERROR("setStatus: Error reading from device (%s)", hid_error(handle));
445         return false;
446     }
447 
448     LOGF_DEBUG("RES <%02X %02X %02X>", response[0], response[1], response[2]);
449 
450     if (response[1] == 0xFF)
451     {
452         LOG_ERROR("setStatus: Invalid state change.");
453         return false;
454     }
455 
456     return true;
457 }
458 
getStatus()459 bool SIEFS::getStatus()
460 {
461     int rc = 0;
462     uint8_t command[1] = {0};
463     uint8_t response[2] = {0};
464 
465     command[0] = 0x11;
466 
467     LOGF_DEBUG("CMD <%02X>", command[0]);
468 
469     if (isSimulation())
470         rc = 1;
471     else
472         rc = hid_write(handle, command, 1);
473 
474     if (rc < 0)
475     {
476         LOGF_ERROR("getStatus: Error writing to device (%s)", hid_error(handle));
477         return false;
478     }
479 
480     if (isSimulation())
481     {
482         rc          = 2;
483         response[0] = command[0];
484         response[1] = m_Motor;
485         // Halt/SetPos is state = 0 "not moving".
486         //        if (response[1] == SI_HALT || response[1] == SI_SET_POS)
487         //            response[1] = 0;
488     }
489     else
490         rc = hid_read_timeout(handle, response, 2, SI_TIMEOUT);
491 
492     if (rc < 0)
493     {
494         LOGF_ERROR("getStatus: Error reading from device (%s)", hid_error(handle));
495         return false;
496     }
497 
498     LOGF_DEBUG("RES <%02X %02X>", response[0], response[1]);
499 
500     m_Motor = static_cast<SI_MOTOR>(response[1]);
501     if (MotorMap.count(m_Motor) > 0)
502         LOGF_DEBUG("State: %s", MotorMap.at(m_Motor).c_str());
503     else
504     {
505         LOGF_WARN("Warning: Unknown status (%d)", response[1]);
506         return false;
507     }
508 
509     return true;
510 }
511 
AbortFocuser()512 bool SIEFS::AbortFocuser()
513 {
514     return sendCommand(SI_HALT);
515 }
516 
SyncFocuser(uint32_t ticks)517 bool SIEFS::SyncFocuser(uint32_t ticks)
518 {
519     bool rc = setAbsPosition(ticks);
520 
521     if (!rc)
522         return false;
523 
524     simPosition = ticks;
525 
526     rc = sendCommand(SI_SET_POS);
527 
528     return rc;
529 }
530 
SetFocuserMaxPosition(uint32_t ticks)531 bool SIEFS::SetFocuserMaxPosition(uint32_t ticks)
532 {
533     bool rc = setAbsPosition(ticks);
534 
535     if (!rc)
536         return false;
537 
538     rc = sendCommand(SI_MAX_POS);
539 
540     return rc;
541 }
542