1 /*
2   FocuserDriver Focuser
3 
4   Copyright(c) 2019 Jasem Mutlaq. All rights reserved.
5 
6   This library is free software; you can redistribute it and/or
7   modify it under the terms of the GNU Lesser General Public
8   License as published by the Free Software Foundation; either
9   version 2.1 of the License, or (at your option) any later version.
10 
11   This library is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Lesser General Public License for more details.
15 
16   You should have received a copy of the GNU Lesser General Public
17   License along with this library; if not, write to the Free Software
18   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19 */
20 
21 #include "focuser_driver.h"
22 
23 #include "indicom.h"
24 
25 #include <cstring>
26 #include <termios.h>
27 #include <memory>
28 #include <thread>
29 #include <chrono>
30 
31 static std::unique_ptr<FocuserDriver> focuserDriver(new FocuserDriver());
32 
FocuserDriver()33 FocuserDriver::FocuserDriver()
34 {
35     // Let's specify the driver version
36     setVersion(1, 0);
37 
38     // What capabilities do we support?
39     FI::SetCapability(FOCUSER_CAN_ABORT |
40                       FOCUSER_CAN_ABS_MOVE |
41                       FOCUSER_CAN_REL_MOVE |
42                       FOCUSER_CAN_SYNC);
43 }
44 
initProperties()45 bool FocuserDriver::initProperties()
46 {
47     INDI::Focuser::initProperties();
48 
49     // Focuser temperature
50     IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Celsius", "%6.2f", -100, 100, 0, 0);
51     IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature",
52                        MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
53 
54     // Stepping Modes
55     IUFillSwitch(&SteppingModeS[STEPPING_FULL], "STEPPING_FULL", "Full", ISS_ON);
56     IUFillSwitch(&SteppingModeS[STEPPING_HALF], "STEPPING_HALF", "Half", ISS_OFF);
57     IUFillSwitchVector(&SteppingModeSP, SteppingModeS, 2, getDeviceName(), "STEPPING_MODE", "Mode",
58                        STEPPING_TAB, IP_RW, ISR_1OFMANY, 0, IPS_OK);
59 
60 
61     addDebugControl();
62 
63     // Set limits as per documentation
64     FocusAbsPosN[0].min  = 0;
65     FocusAbsPosN[0].max  = 999999;
66     FocusAbsPosN[0].step = 1000;
67 
68     FocusRelPosN[0].min  = 0;
69     FocusRelPosN[0].max  = 999;
70     FocusRelPosN[0].step = 100;
71 
72     FocusSpeedN[0].min  = 1;
73     FocusSpeedN[0].max  = 254;
74     FocusSpeedN[0].step = 10;
75 
76     return true;
77 }
78 
getDefaultName()79 const char *FocuserDriver::getDefaultName()
80 {
81     return "Focuser Driver";
82 }
83 
updateProperties()84 bool FocuserDriver::updateProperties()
85 {
86     if (isConnected())
87     {
88         // Read these values before defining focuser interface properties
89         readPosition();
90     }
91 
92     INDI::Focuser::updateProperties();
93 
94     if (isConnected())
95     {
96         if (readTemperature())
97             defineProperty(&TemperatureNP);
98 
99         bool rc = getStartupValues();
100 
101         // Settings
102         defineProperty(&SteppingModeSP);
103 
104         if (rc)
105             LOG_INFO("FocuserDriver is ready.");
106         else
107             LOG_WARN("Failed to query startup values.");
108     }
109     else
110     {
111         if (TemperatureNP.s == IPS_OK)
112             deleteProperty(TemperatureNP.name);
113 
114         deleteProperty(SteppingModeSP.name);
115     }
116 
117     return true;
118 }
119 
Handshake()120 bool FocuserDriver::Handshake()
121 {
122     // This functin is ensure that we have communication with the focuser
123     // Below we send it 0x6 byte and check for 'S' in the return. Change this
124     // to be valid for your driver. It could be anything, you can simply put this below
125     // return readPosition()
126     // since this will try to read the position and if successful, then communicatoin is OK.
127     char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
128 
129     // Ack
130     cmd[0] = 0x6;
131 
132     bool rc = sendCommand(cmd, res, 1, 1);
133     if (rc == false)
134         return false;
135 
136     return res[0] == 'S';
137 }
138 
sendCommand(const char * cmd,char * res,int cmd_len,int res_len)139 bool FocuserDriver::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
140 {
141     int nbytes_written = 0, nbytes_read = 0, rc = -1;
142 
143     tcflush(PortFD, TCIOFLUSH);
144 
145     if (cmd_len > 0)
146     {
147         char hex_cmd[DRIVER_LEN * 3] = {0};
148         hexDump(hex_cmd, cmd, cmd_len);
149         LOGF_DEBUG("CMD <%s>", hex_cmd);
150         rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
151     }
152     else
153     {
154         LOGF_DEBUG("CMD <%s>", cmd);
155         rc = tty_write_string(PortFD, cmd, &nbytes_written);
156     }
157 
158     if (rc != TTY_OK)
159     {
160         char errstr[MAXRBUF] = {0};
161         tty_error_msg(rc, errstr, MAXRBUF);
162         LOGF_ERROR("Serial write error: %s.", errstr);
163         return false;
164     }
165 
166     if (res == nullptr)
167         return true;
168 
169     if (res_len > 0)
170         rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
171     else
172         rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
173 
174     if (rc != TTY_OK)
175     {
176         char errstr[MAXRBUF] = {0};
177         tty_error_msg(rc, errstr, MAXRBUF);
178         LOGF_ERROR("Serial read error: %s.", errstr);
179         return false;
180     }
181 
182     if (res_len > 0)
183     {
184         char hex_res[DRIVER_LEN * 3] = {0};
185         hexDump(hex_res, res, res_len);
186         LOGF_DEBUG("RES <%s>", hex_res);
187     }
188     else
189     {
190         LOGF_DEBUG("RES <%s>", res);
191     }
192 
193     tcflush(PortFD, TCIOFLUSH);
194 
195     return true;
196 }
197 
hexDump(char * buf,const char * data,int size)198 void FocuserDriver::hexDump(char * buf, const char * data, int size)
199 {
200     for (int i = 0; i < size; i++)
201         sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
202 
203     if (size > 0)
204         buf[3 * size - 1] = '\0';
205 }
206 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)207 bool FocuserDriver::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
208 {
209     if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
210     {
211         // Stepping Mode
212         if (!strcmp(name, SteppingModeSP.name))
213         {
214             IUUpdateSwitch(&SteppingModeSP, states, names, n);
215             SteppingModeSP.s = IPS_OK;
216             IDSetSwitch(&SteppingModeSP, nullptr);
217             return true;
218         }
219     }
220 
221     return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
222 }
223 
getStartupValues()224 bool FocuserDriver::getStartupValues()
225 {
226     bool rc1 = readStepping();
227     //bool rc2 = readFoo();
228     //bool rc2 = readBar();
229 
230     //return (rc1 && rc2 && rc3);
231 
232     return rc1;
233 }
234 
MoveAbsFocuser(uint32_t targetTicks)235 IPState FocuserDriver::MoveAbsFocuser(uint32_t targetTicks)
236 {
237     // Issue here the command necessary to move the focuser to targetTicks
238     return IPS_BUSY;
239 }
240 
MoveRelFocuser(FocusDirection dir,uint32_t ticks)241 IPState FocuserDriver::MoveRelFocuser(FocusDirection dir, uint32_t ticks)
242 {
243     m_TargetDiff = ticks * ((dir == FOCUS_INWARD) ? -1 : 1);
244     return MoveAbsFocuser(FocusAbsPosN[0].value + m_TargetDiff);
245 }
246 
AbortFocuser()247 bool FocuserDriver::AbortFocuser()
248 {
249     return sendCommand("FOOBAR");
250 }
251 
TimerHit()252 void FocuserDriver::TimerHit()
253 {
254     if (isConnected() == false)
255         return;
256 
257     // What is the last read position?
258     double currentPosition = FocusAbsPosN[0].value;
259 
260     // Read the current position
261     readPosition();
262 
263     // Check if we have a pending motion
264     // if isMoving() is false, then we stopped, so we need to set the Focus Absolute
265     // and relative properties to OK
266     if ( (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY) && isMoving() == false)
267     {
268         FocusAbsPosNP.s = IPS_OK;
269         FocusRelPosNP.s = IPS_OK;
270         IDSetNumber(&FocusAbsPosNP, nullptr);
271         IDSetNumber(&FocusRelPosNP, nullptr);
272     }
273     // If there was a different between last and current positions, let's update all clients
274     else if (currentPosition != FocusAbsPosN[0].value)
275     {
276         IDSetNumber(&FocusAbsPosNP, nullptr);
277     }
278 
279     // Read temperature periodically
280     if (TemperatureNP.s == IPS_OK && m_TemperatureCounter++ == DRIVER_TEMPERATURE_FREQ)
281     {
282         m_TemperatureCounter = 0;
283         if (readTemperature())
284             IDSetNumber(&TemperatureNP, nullptr);
285     }
286 
287     SetTimer(getCurrentPollingPeriod());
288 }
289 
isMoving()290 bool FocuserDriver::isMoving()
291 {
292     char res[DRIVER_LEN] = {0};
293 
294     bool rc = sendCommand("FOOBAR", res, 1, 1);
295 
296     if (rc && !strcmp(res, "STOPPED"))
297         return true;
298 
299     return false;
300 }
301 
readTemperature()302 bool FocuserDriver::readTemperature()
303 {
304     char res[DRIVER_LEN] = {0};
305 
306     // This assumes we need to read 4 BYTES for the temperature. It can be anything
307     // If the response is terminated by the DRIVER_STOP_CHAR, we can simply call
308     // sendCommand("FOOBAR", res)
309     if (sendCommand("FOOBAR", res, strlen("FOOBAR"), 4) == false)
310         return false;
311 
312     float temperature = -1000;
313     sscanf(res, "%f", &temperature);
314 
315     if (temperature < -100)
316         return false;
317 
318     TemperatureN[0].value = temperature;
319     TemperatureNP.s = IPS_OK;
320 
321     return true;
322 }
323 
readPosition()324 bool FocuserDriver::readPosition()
325 {
326     char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
327 
328     cmd[0] = 0xA;
329     cmd[1] = 0xB;
330     cmd[2] = 0xC;
331 
332     // since the command above is not NULL-TERMINATED, we need to specify the number of bytes (3)
333     // in the send command below. We also specify 7 bytes to be read which can be changed to any value.
334     if (sendCommand(cmd, res, 3, 7) == false)
335         return false;
336 
337     // For above, in case instead the response is terminated by DRIVER_STOP_CHAR, then the command would be
338     // (sendCommand(cmd, res, 3) == false)
339     //    return false;
340 
341     int32_t pos = 1e6;
342     sscanf(res, "%d", &pos);
343 
344     if (pos == 1e6)
345         return false;
346 
347     FocusAbsPosN[0].value = pos;
348 
349     return true;
350 }
351 
readStepping()352 bool FocuserDriver::readStepping()
353 {
354     char res[DRIVER_LEN] = {0};
355 
356     if (sendCommand("FOOBAR", res, 3, 1) == false)
357         return false;
358 
359     int32_t mode = 1e6;
360     sscanf(res, "%d", &mode);
361 
362     if (mode == 1e6)
363         return false;
364 
365     // Assuming the above function returns 10 for full step, and 11 for half step
366     // we can update the switch status as follows
367     SteppingModeS[STEPPING_FULL].s = (mode == 10) ? ISS_ON : ISS_OFF;
368     SteppingModeS[STEPPING_HALF].s = (mode == 10) ? ISS_OFF : ISS_ON;
369     SteppingModeSP.s = IPS_OK;
370 
371     return true;
372 }
373 
374 
SyncFocuser(uint32_t ticks)375 bool FocuserDriver::SyncFocuser(uint32_t ticks)
376 {
377     char cmd[DRIVER_LEN] = {0};
378     snprintf(cmd, DRIVER_LEN, "#:SYNC+%06d#", ticks);
379     return sendCommand(cmd);
380 }
381 
setStepping(SteppingMode mode)382 bool FocuserDriver::setStepping(SteppingMode mode)
383 {
384     char cmd[DRIVER_LEN] = {0};
385     snprintf(cmd, DRIVER_LEN, "#FOOBAR%01d#", mode);
386     return sendCommand(cmd);
387 }
388 
saveConfigItems(FILE * fp)389 bool FocuserDriver::saveConfigItems(FILE *fp)
390 {
391     INDI::Focuser::saveConfigItems(fp);
392 
393     // We need to reserve and save stepping mode
394     // so that the next time the driver is loaded, it is remembered and applied.
395     IUSaveConfigSwitch(fp, &SteppingModeSP);
396 
397     return true;
398 }
399