Instrument Neutral Distributed Interface INDI  1.9.2
lakeside.cpp
Go to the documentation of this file.
1 /*
2  Lakeside Focuser
3  Copyright (C) 2017 Phil Shepherd (psjshep@googlemail.com)
4  Technical Information kindly supplied by Peter Chance at LakesideAstro (info@lakeside-astro.com)
5 
6  Code template from original Moonlite code by 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 /*
25 Modifications
26 0.1 psjshep xx-xxx-xxxx - 1st version
27 ..
28 ..
29 0.11 psjshep 17-Mar-2017 - changed PortT[0].text to serialConnection->port()
30 
31 1.1 JM 29-11-2018: Misc fixes and improvements.
32  */
33 
34 #define LAKESIDE_VERSION_MAJOR 1
35 #define LAKESIDE_VERSION_MINOR 1
36 
37 #include "lakeside.h"
38 #include <config.h>
39 #include "indicom.h"
41 
42 #include <stdio.h>
43 #include <termios.h>
44 #include <string.h>
45 #include <sys/time.h>
46 #include <unistd.h>
47 #include <math.h>
48 #include <memory>
49 
50 // tty_read_section timeout in seconds
51 #define LAKESIDE_TIMEOUT 2
52 #define LAKESIDE_LEN 7
53 
54 // Max number of Timeouts for a tty_read_section
55 // This is in case a buffer read is too fast
56 // or nothing in the buffer during GetLakesideStatus()
57 #define LAKESIDE_TIMEOUT_RETRIES 2
58 
59 static std::unique_ptr<Lakeside> lakeside(new Lakeside());
60 
62 {
64 
70 }
71 
72 // Initialise
74 {
76 
77  // Current Direction
78  // IUFillSwitch(&MoveDirectionS[0], "Normal", "", ISS_ON);
79  // IUFillSwitch(&MoveDirectionS[1], "Reverse", "", ISS_OFF);
80  // IUFillSwitchVector(&MoveDirectionSP, MoveDirectionS, 2, getDeviceName(), "","Move Direction", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
81 
82  // Focuser temperature (degrees C) - read only
83  IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Celsius", "%3.2f", -50, 70., 0., 0.);
84  IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature (C)", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
85 
86  // Focuser temperature (Kelvin)- read only & only read once at connect
87  IUFillNumber(&TemperatureKN[0], "TEMPERATUREK", "Kelvin", "%3.2f", 0., 373.15, 0., 0.);
88  IUFillNumberVector(&TemperatureKNP, TemperatureKN, 1, getDeviceName(), "FOCUS_TEMPERATUREK", "Temperature (K)", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
89 
90  // Compensate for temperature
91  IUFillSwitch(&TemperatureTrackingS[0], "Enable", "", ISS_OFF);
92  IUFillSwitch(&TemperatureTrackingS[1], "Disable", "", ISS_ON);
93  IUFillSwitchVector(&TemperatureTrackingSP, TemperatureTrackingS, 2, getDeviceName(), "Temperature Track", "", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
94 
95  // Backlash 0-255
96  // IUFillNumber(&FocusBacklashN[0], "BACKLASH", "(0-255)", "%.f", 0, 255, 0, 0);
97  // IUFillNumberVector(&FocusBacklashNP, FocusBacklashN, 1, getDeviceName(), "BACKLASH", "Backlash", SETTINGS_TAB, IP_RW, 0, IPS_IDLE );
98  FocusBacklashN[0].min = 0;
99  FocusBacklashN[0].max = 255;
100  FocusBacklashN[0].step = 10;
101  FocusBacklashN[0].value = 0;
102 
103  // Maximum Travel - read only
104  // IUFillNumber(&MaxTravelN[0], "MAXTRAVEL", "No. Steps", "%.f", 1, 65536, 0, 10000);
105  // IUFillNumberVector(&MaxTravelNP, MaxTravelN, 1, getDeviceName(), "MAXTRAVEL", "Max travel(Via Ctrlr)", SETTINGS_TAB, IP_RO, 0, IPS_IDLE );
107 
108  // Step Size - read only
109  IUFillNumber(&StepSizeN[0], "STEPSIZE", "No. Steps", "%.f", 1, 65536, 0, 1);
110  IUFillNumberVector(&StepSizeNP, StepSizeN, 1, getDeviceName(), "STEPSIZE", "Step Size(Via Ctrlr)", SETTINGS_TAB, IP_RO, 0, IPS_IDLE);
111 
112  // Active Temperature Slope - select 1 or 2
113  IUFillSwitch(&ActiveTemperatureSlopeS[0], "Slope 1", "", ISS_ON);
114  IUFillSwitch(&ActiveTemperatureSlopeS[1], "Slope 2", "", ISS_OFF);
115  IUFillSwitchVector(&ActiveTemperatureSlopeSP, ActiveTemperatureSlopeS, 2, getDeviceName(), "Active Slope", "Active Slope", SETTINGS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
116 
117  // Slope 1 : Directions
118  IUFillSwitch(&Slope1DirS[0], "0", "", ISS_ON);
119  IUFillSwitch(&Slope1DirS[1], "1", "", ISS_OFF);
120  IUFillSwitchVector(&Slope1DirSP, Slope1DirS, 2, getDeviceName(), "Slope 1 Direction", "Slope 1 Direction", SETTINGS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
121 
122  // Slope 1 : Slope Increments (counts per degree, 0.1 step increments
123  IUFillNumber(&Slope1IncN[0], "SLOPE1INC", "No. Steps (0-655356", "%.f", 0, 65536, 0, 0);
124  IUFillNumberVector(&Slope1IncNP, Slope1IncN, 1, getDeviceName(), "SLOPE1INC", "Slope1 Increments", SETTINGS_TAB, IP_RW, 0, IPS_IDLE );
125 
126  // slope 1 : Deadband - value between 0 and 255
127  IUFillNumber(&Slope1DeadbandN[0], "SLOPE1DEADBAND", "(0-255)", "%.f", 0, 255, 0, 0);
128  IUFillNumberVector(&Slope1DeadbandNP, Slope1DeadbandN, 1, getDeviceName(), "SLOPE1DEADBAND", "Slope 1 Deadband", SETTINGS_TAB, IP_RW, 0, IPS_IDLE );
129 
130  // Slope 1 : Time Period (Minutes, 0.1 step increments
131  IUFillNumber(&Slope1PeriodN[0], "SLOPE1PERIOD", "Minutes (0-99)", "%.f", 0, 99, 0, 0);
132  IUFillNumberVector(&Slope1PeriodNP, Slope1PeriodN, 1, getDeviceName(), "SLOPE1PERIOD", "Slope 1 Period", SETTINGS_TAB, IP_RW, 0, IPS_IDLE );
133 
134  // Slope 2 : Direction
135  IUFillSwitch(&Slope2DirS[0], "0", "", ISS_ON);
136  IUFillSwitch(&Slope2DirS[1], "1", "", ISS_OFF);
137  IUFillSwitchVector(&Slope2DirSP, Slope2DirS, 2, getDeviceName(), "Slope 2 Direction", "", SETTINGS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
138 
139  // slope 2 : Slope Increments (counts per degree, 0.1 step increments
140  IUFillNumber(&Slope2IncN[0], "SLOPE2INC", "No. Steps (0-65536)", "%.f", 0, 65536, 0, 0);
141  IUFillNumberVector(&Slope2IncNP, Slope2IncN, 1, getDeviceName(), "SLOPE2INC", "Slope 2 Increments", SETTINGS_TAB, IP_RW, 0, IPS_IDLE );
142 
143  // slope 2 : Deadband - value between 0 and 255
144  IUFillNumber(&Slope2DeadbandN[0], "SLOPE2DEADBAND", "Steps (0-255)", "%.f", 0, 255, 0, 0);
145  IUFillNumberVector(&Slope2DeadbandNP, Slope2DeadbandN, 1, getDeviceName(), "SLOPE2DEADBAND", "Slope 2 Deadband", SETTINGS_TAB, IP_RW, 0, IPS_IDLE );
146 
147  // slope 2 : Time Period (Minutes, 0.1 step increments)
148  IUFillNumber(&Slope2PeriodN[0], "SLOPE2PERIOD", "Minutes (0-99)", "%.f", 0, 99, 0, 0);
149  IUFillNumberVector(&Slope2PeriodNP, Slope2PeriodN, 1, getDeviceName(), "SLOPE2PERIOD", "Slope 2 Period", SETTINGS_TAB, IP_RW, 0, IPS_IDLE );
150 
151  FocusAbsPosN[0].min = 0.;
152 
153  // shephpj - not used
154  //FocusAbsPosN[0].max = 65536.;
155 
157 
158  addDebugControl();
159 
160  return true;
161 
162 }
163 
165 {
167 
168  if (isConnected())
169  {
170  //defineProperty(&FocusBacklashNP);
171  //defineProperty(&MaxTravelNP);
172  defineProperty(&StepSizeNP);
173  defineProperty(&TemperatureNP);
174  defineProperty(&TemperatureKNP);
175  //defineProperty(&MoveDirectionSP);
176  defineProperty(&TemperatureTrackingSP);
177  defineProperty(&ActiveTemperatureSlopeSP);
178  defineProperty(&Slope1DirSP);
179  defineProperty(&Slope1IncNP);
180  defineProperty(&Slope1DeadbandNP);
181  defineProperty(&Slope1PeriodNP);
182  defineProperty(&Slope2DirSP);
183  defineProperty(&Slope2IncNP);
184  defineProperty(&Slope2DeadbandNP);
185  defineProperty(&Slope2PeriodNP);
186 
187  GetFocusParams();
188 
189  LOG_INFO("Lakeside parameters updated, focuser ready for use.");
190  }
191  else
192  {
193  //deleteProperty(FocusBacklashNP.name);
194  //deleteProperty(MaxTravelNP.name);
195  deleteProperty(StepSizeNP.name);
196  //deleteProperty(MoveDirectionSP.name);
197  deleteProperty(TemperatureNP.name);
198  deleteProperty(TemperatureKNP.name);
199  deleteProperty(TemperatureTrackingSP.name);
200  deleteProperty(ActiveTemperatureSlopeSP.name);
201  deleteProperty(Slope1DirSP.name);
202  deleteProperty(Slope1IncNP.name);
203  deleteProperty(Slope1DeadbandNP.name);
204  deleteProperty(Slope1PeriodNP.name);
205  deleteProperty(Slope2DirSP.name);
206  deleteProperty(Slope2IncNP.name);
207  deleteProperty(Slope2DeadbandNP.name);
208  deleteProperty(Slope2PeriodNP.name);
209  }
210 
211  return true;
212 
213 }
214 
215 #if 0
216 // connect to focuser port
217 //
218 // 9600 baud
219 // 8 bits
220 // 0 parity
221 // 1 stop bit
222 //
223 bool Lakeside::Connect()
224 {
225  int rc = 0;
226  char errorMsg[MAXRBUF];
227 
228  // if ( (rc = tty_connect(PortT[0].text, 9600, 8, 0, 1, &PortFD)) != TTY_OK)
229  if ( (rc = tty_connect(serialConnection->port(), 9600, 8, 0, 1, &PortFD)) != TTY_OK)
230  {
231  tty_error_msg(rc, errorMsg, MAXRBUF);
232  LOGF_INFO("Failed to connect to port %s, with Error %s", serialConnection->port(), errorMsg);
233  return false;
234  }
235 
236  LOGF_INFO("Connected to port %s", serialConnection->port());
237 
238  if (LakesideOnline())
239  {
240  LOGF_INFO("Lakeside is online on port %s", serialConnection->port());
242  return true;
243  }
244  else
245  {
246  LOGF_INFO("Unable to connect to Lakeside Focuser. Please ensure the controller is powered on and the port (%s) is correct.", serialConnection->port());
247  return false;
248  }
249 }
250 
251 
252 // Disconnect from focuser
254 {
255  LOG_INFO("Lakeside is offline.");
256  return INDI::Focuser::Disconnect();
257 }
258 #endif
259 
261 {
262  return LakesideOnline();
263 }
264 
266 {
267  return "Lakeside";
268 }
269 
270 //
271 // Send Lakeside a command
272 //
273 // In :
274 // in_cmd : command to send to the focuser
275 //
276 // Returns true for successful write
277 // false for failed write
278 //
279 bool Lakeside::SendCmd(const char * in_cmd)
280 {
281  int nbytes_written = 0, rc = -1;
282  char errstr[MAXRBUF];
283 
284  LOGF_DEBUG("CMD <%s>", in_cmd);
285 
286  if ( (rc = tty_write_string(PortFD, in_cmd, &nbytes_written)) != TTY_OK)
287  {
288  tty_error_msg(rc, errstr, MAXRBUF);
289  LOGF_ERROR("SendCmd: Write for command (%s) failed - %s", in_cmd, errstr);
290  return false;
291  }
292 
293  return true;
294 
295 }
296 
297 //
298 // Read the Lakeside buffer, setting response to the contents
299 //
300 // Returns
301 // true : something to read in the buffer
302 // false : error reading the buffer
303 //
304 bool Lakeside::ReadBuffer(char * response)
305 {
306  int nbytes_read = 0, rc = -1;
307  char resp[LAKESIDE_LEN] = {0};
308 
309  //strcpy(resp," ");
310  // read until 0x23 (#) received
311  if ( (rc = tty_read_section(PortFD, resp, 0x23, LAKESIDE_TIMEOUT, &nbytes_read)) != TTY_OK)
312  {
313  char errstr[MAXRBUF];
314  tty_error_msg(rc, errstr, MAXRBUF);
315  LOGF_ERROR("ReadBuffer: Read failed - %s", errstr);
316  strncpy(response, "ERROR", LAKESIDE_LEN);
317  return false;
318  }
319 
320  // char hex_cmd[LAKESIDE_LEN * 3] = {0};
321  // hexDump(hex_cmd, resp, LAKESIDE_LEN * 3);
322  // LOGF_DEBUG("RES <%s>", hex_cmd);
323 
324  resp[nbytes_read] = 0;
325  LOGF_DEBUG("RES <%s>", resp);
326 
327  strncpy(response, resp, LAKESIDE_LEN);
328  return true;
329 }
330 
331 //
332 // check for OK# from Lakeside - i.e. it is responding
333 //
334 bool Lakeside::LakesideOnline()
335 {
336  char resp[LAKESIDE_LEN] = {0};
337  const char * cmd = "??#";
338 
339  //strcpy(resp," ");
340 
341  if (!SendCmd(cmd))
342  {
343  return false;
344  }
345 
346  LOGF_DEBUG("LakesideOnline: Successfully sent (%s)", cmd);
347 
348  if (!ReadBuffer(resp))
349  {
350  return false;
351  }
352 
353  // if SendCmd succeeded, resp contains response from the command
354  LOGF_DEBUG("LakesideOnline: Received (%s)", resp);
355 
356  if (!strncmp(resp, "OK#", 3))
357  {
358  LOG_DEBUG("LakesideOnline: Received OK# - Lakeside responded");
359  return true;
360  }
361  else
362  {
363  LOGF_ERROR("LakesideOnline: OK# not found. Instead, received (%s)", resp);
364  return false;
365  }
366 
367 }
368 // get current movement direction
369 //
370 // 0 = Normal
371 // 1 = Reversed
372 bool Lakeside::updateMoveDirection()
373 {
374  int rc = -1, temp = -1;
375  char resp[LAKESIDE_LEN] = {0};
376  char cmd[] = "?D#";
377 
378  if (!SendCmd(cmd))
379  {
380  return false;
381  }
382 
383  if (!ReadBuffer(resp))
384  {
385  return false;
386  }
387 
388  //IUResetSwitch(&MoveDirectionSP);
389 
390  // direction is in form Dnnnnn#
391  // where nnnnn is 0 for normal or 1 for reversed
392  rc = sscanf(resp, "D%5d#", &temp);
393 
394  if ( temp == 0)
395  {
397  LOGF_DEBUG("updateMoveDirection: Move Direction is (%d)", temp);
398  }
399  else if ( temp == 1)
400  {
402  LOGF_DEBUG("updateMoveDirection: Move Direction is (%d)", temp);
403  }
404  else
405  {
406  LOGF_ERROR("updateMoveDirection: Unknown move Direction response (%s)", resp);
407  return false;
408  }
409 
410  return true;
411 }
412 
413 // Decode contents of buffer
414 // Returns:
415 // P : Position update found - FocusAbsPosN[0].value updated
416 // T : Temperature update found - TemperatureN[0].value
417 // K : Temperature in Kelvin update found - TemperatureKN[0].value
418 // D : DONE# received
419 // O : OK# received
420 // E : Error due to unknown/misformed command having been sent
421 // ? : unknown response received
422 char Lakeside::DecodeBuffer(char * in_response)
423 {
424  int temp = 0, pos = 0, rc = -1;
425 
426  LOGF_DEBUG("DecodeBuffer: in_response (%s)", in_response);
427 
428  // if focuser finished moving, DONE# received
429  if (!strncmp(in_response, "DONE#", 5))
430  {
431  return 'D';
432  }
433 
434  // if focuser returned OK#
435  if (!strncmp(in_response, "OK#", 3))
436  {
437  return 'O';
438  }
439 
440  // if focuser returns an error for unknow command
441  if (!strncmp(in_response, "!#", 2))
442  {
443  return 'E';
444  }
445 
446  // Temperature update is Tnnnnnn# where nnnnn is left space padded
447  if (!strcmp("TN/A#", in_response))
448  {
449  TemperatureNP.s = IPS_IDLE;
450  return 'T';
451  }
452  else
453  {
454  rc = sscanf(in_response, "T%5d#", &temp);
455  if (rc > 0)
456  {
457  // need to divide result by 2
458  TemperatureN[0].value = ((int) temp) / 2.0;
459  LOGF_DEBUG("DecodeBuffer: Result (%3.1f)", TemperatureN[0].value);
460 
461  return 'T';
462  }
463  }
464 
465  // Temperature update is Knnnnnn# where nnnnn is left space padded
466  rc = sscanf(in_response, "K%5d#", &temp);
467  if (rc > 0)
468  {
469  // need to divide result by 2
470  TemperatureKN[0].value = ((int) temp) / 2.00;
471  LOGF_DEBUG("DecodeBuffer: Result (%3.2f)", TemperatureKN[0].value);
472 
473  return 'K';
474  }
475 
476  // look for step info Pnnnnn#
477  rc = sscanf(in_response, "P%5d#", &pos);
478  // focuser position returned Pnnnnn#
479  if (rc > 0)
480  {
481  FocusAbsPosN[0].value = pos;
482  IDSetNumber(&FocusAbsPosNP, nullptr);
483 
484  LOGF_DEBUG("DecodeBuffer: Returned position (%d)", pos);
485  return 'P';
486  }
487  else
488  {
489  LOGF_ERROR("DecodeBuffer: Unknown response : (%s)", in_response);
490  return '?';
491  }
492 }
493 
494 // Get Temperature in C from focuser
495 //
496 // Return :
497 // true : successfully got Temperature & updated INDI
498 // false : Unable to get & update Temperature (timeout or other)
499 //
500 bool Lakeside::updateTemperature()
501 {
502  char resp[LAKESIDE_LEN] = {0};
503  char cmd[] = "?T#";
504  char buffer_response = '?';
505 
506  if (!SendCmd(cmd))
507  {
508  return false;
509  }
510 
511  if (!ReadBuffer(resp))
512  {
513  return false;
514  }
515 
516  LOGF_DEBUG("updateTemperature: Read response (%s)", resp);
517 
518  // ascertain contents of buffer & update temp if necessary
519  buffer_response = DecodeBuffer(resp);
520 
521  // if temperature updated, then return true
522  if ( buffer_response == 'T' )
523  {
524  return true;
525  }
526  else
527  {
528  return false;
529  }
530 }
531 
532 // Get Temperature in K from focuser
533 //
534 // Return :
535 // true : successfully got Temperature in K & updated INDI
536 // false : Unable to get & update Temperature in K (timeout or other)
537 //
538 bool Lakeside::updateTemperatureK()
539 {
540  char resp[LAKESIDE_LEN] = {0};
541  char cmd[] = "?K#";
542  char buffer_response = '?';
543 
544  if (!SendCmd(cmd))
545  {
546  return false;
547  }
548 
549  if (!ReadBuffer(resp))
550  {
551  return false;
552  }
553 
554  LOGF_DEBUG("updateTemperatureK: Read response (%s)", resp);
555 
556  // ascertain contents of buffer & update temp in K if necessary
557  buffer_response = DecodeBuffer(resp);
558 
559  // if temperature updated, then return true
560  if ( buffer_response == 'K' )
561  {
562  return true;
563  }
564  else
565  {
566  return false;
567  }
568 }
569 
570 // Get position of focuser
571 //
572 // Return :
573 // true : successfully got focus position & updated INDI
574 // false : Unable to get & update position (timeout or other)
575 //
576 bool Lakeside::updatePosition()
577 {
578  char resp[LAKESIDE_LEN] = {0};
579  char cmd[] = "?P#";
580  char buffer_response = '?';
581 
582  if (!SendCmd(cmd))
583  {
584  return false;
585  }
586 
587  LOGF_DEBUG("updatePosition: Successfully sent (%s)", cmd);
588 
589  if (!ReadBuffer(resp))
590  {
591  return false;
592  }
593 
594  LOGF_DEBUG("updatePosition: Fetched (%s)", resp);
595 
596  // ascertain contents of buffer & update position if necessary
597  buffer_response = DecodeBuffer(resp);
598 
599  if ( buffer_response == 'P' )
600  {
601  return true;
602  }
603  else
604  {
605  return false;
606  }
607 }
608 
609 // Get Backlash compensation
610 bool Lakeside::updateBacklash()
611 {
612  int rc = -1, temp = -1;
613  char resp[LAKESIDE_LEN] = {0};
614  char cmd[] = "?B#";
615 
616  if (!SendCmd(cmd))
617  {
618  return false;
619  }
620 
621  if (!ReadBuffer(resp))
622  {
623  return false;
624  }
625 
626  // Backlash is in form Bnnnnn#
627  // where nnnnn is 0 - 255, space left padded
628  rc = sscanf(resp, "B%5d#", &temp);
629 
630  if ( temp >= 0)
631  {
632  FocusBacklashN[0].value = temp;
633  LOGF_DEBUG("updateBacklash: Backlash is (%d)", temp);
634  }
635  else
636  {
637  LOGF_ERROR("updateBacklash: Backlash request error (%s)", resp);
638  return false;
639  }
640 
641  return true;
642 }
643 
644 // get Slope 1 Increments
645 bool Lakeside::updateSlope1Inc()
646 {
647  int rc = -1, temp = -1;
648  char resp[LAKESIDE_LEN];
649  char cmd[] = "?1#";
650 
651  if (!SendCmd(cmd))
652  {
653  return false;
654  }
655 
656  if (!ReadBuffer(resp))
657  {
658  return false;
659  }
660 
661  // Slope 1 Increment is in form 1nnnnn#
662  // where nnnnn is number of 0.1 step increments, space left padded
663  rc = sscanf(resp, "1%5d#", &temp);
664 
665  if ( temp >= 0)
666  {
667  Slope1IncN[0].value = temp;
668  LOGF_DEBUG("updateSlope1Inc: Slope 1 Increments is (%d)", temp);
669  }
670  else
671  {
672  LOGF_ERROR("updateSlope1Inc: Slope 1 Increment request error (%s)", resp);
673  return false;
674  }
675 
676  return true;
677 }
678 
679 // get Slope 2 Increments
680 bool Lakeside::updateSlope2Inc()
681 {
682  int rc = -1, temp = -1;
683  char resp[LAKESIDE_LEN] = {0};
684  char cmd[] = "?2#";
685 
686  if (!SendCmd(cmd))
687  {
688  return false;
689  }
690 
691  if (!ReadBuffer(resp))
692  {
693  return false;
694  }
695 
696  // Slope 1 Increment is in form 1nnnnn#
697  // where nnnnn is number of 0.1 step increments, space left padded
698  rc = sscanf(resp, "2%5d#", &temp);
699 
700  if ( temp >= 0)
701  {
702  Slope2IncN[0].value = temp;
703  LOGF_DEBUG("updateSlope2Inc: Slope 2 Increments is (%d)", temp);
704  }
705  else
706  {
707  LOGF_ERROR("updateSlope2Inc: Slope 2 Increment request error (%s)", resp);
708  return false;
709  }
710 
711  return true;
712 }
713 
714 // get Slope 1 direction : 0 or 1
715 bool Lakeside::updateSlope1Dir()
716 {
717  int rc = -1, temp = -1;
718  char resp[LAKESIDE_LEN] = {0};
719  char cmd[] = "?a#";
720 
721  if (!SendCmd(cmd))
722  {
723  return false;
724  }
725 
726  if (!ReadBuffer(resp))
727  {
728  return false;
729  }
730 
731  // Slope 1 Direction is in form annnnn#
732  // where nnnnn is either 0 or 1, space left padded
733  rc = sscanf(resp, "a%5d#", &temp);
734 
735  if ( temp == 0)
736  {
737  Slope1DirS[0].s = ISS_ON;
738  LOGF_DEBUG("updateSlope1Dir: Slope 1 Direction is (%d)", temp);
739  }
740  else if ( temp == 1)
741  {
742  Slope1DirS[1].s = ISS_ON;
743  }
744  else
745  {
746  LOGF_ERROR("updateSlope1Dir: Unknown Slope 1 Direction response (%s)", resp);
747  return false;
748  }
749 
750  return true;
751 }
752 
753 // get Slope 2 direction : 0 or 1
754 bool Lakeside::updateSlope2Dir()
755 {
756  int rc = -1, temp = -1;
757  char resp[LAKESIDE_LEN] = {0};
758  char cmd[] = "?b#";
759 
760  if (!SendCmd(cmd))
761  {
762  return false;
763  }
764 
765  if (!ReadBuffer(resp))
766  {
767  return false;
768  }
769 
770  // Slope 2 Direction is in form annnnn#
771  // where nnnnn is either 0 or 1, space left padded
772  rc = sscanf(resp, "b%5d#", &temp);
773 
774  if ( temp == 0)
775  {
776  Slope2DirS[0].s = ISS_ON;
777  LOGF_DEBUG("updateSlope2Dir: Slope 2 Direction is (%d)", temp);
778  }
779  else if ( temp == 1)
780  {
781  Slope2DirS[1].s = ISS_ON;
782  }
783  else
784  {
785  LOGF_ERROR("updateSlope2Dir: Unknown Slope 2 Direction response (%s)", resp);
786  return false;
787  }
788 
789  return true;
790 }
791 
792 // Get slope 1 deadband
793 bool Lakeside::updateSlope1Deadband()
794 {
795  int rc = -1, temp = -1;
796  char resp[LAKESIDE_LEN] = {0};
797  char cmd[] = "?c#";
798 
799  if (!SendCmd(cmd))
800  {
801  return false;
802  }
803 
804  if (!ReadBuffer(resp))
805  {
806  return false;
807  }
808 
809  // Deadband is in form cnnnnn#
810  // where nnnnn is 0 - 255, space left padded
811  rc = sscanf(resp, "c%5d#", &temp);
812 
813  if ( temp >= 0)
814  {
815  Slope1DeadbandN[0].value = temp;
816  LOGF_DEBUG("updateSlope1Deadband: Slope 1 Deadband is (%d)", temp);
817  }
818  else
819  {
820  LOGF_ERROR("updateSlope1Deadband: Slope 1 Deadband request error (%s)", resp);
821  return false;
822  }
823 
824  return true;
825 }
826 
827 // Get slope 2 deadband
828 bool Lakeside::updateSlope2Deadband()
829 {
830  int rc = -1, temp = -1;
831  char resp[LAKESIDE_LEN] = {0};
832  char cmd[] = "?d#";
833 
834  if (!SendCmd(cmd))
835  {
836  return false;
837  }
838 
839  if (!ReadBuffer(resp))
840  {
841  return false;
842  }
843 
844  // Deadband is in form dnnnnn#
845  // where nnnnn is 0 - 255, space left padded
846  rc = sscanf(resp, "d%5d#", &temp);
847 
848  if ( temp >= 0)
849  {
850  Slope2DeadbandN[0].value = temp;
851  LOGF_DEBUG("updateSlope2Deadband: Slope 2 Deadband is (%d)", temp);
852  }
853  else
854  {
855  LOGF_ERROR("updateSlope2Deadband: Slope 2 Deadband request error (%s)", resp);
856  return false;
857  }
858 
859  return true;
860 }
861 
862 // get Slope 1 time period
863 bool Lakeside::updateSlope1Period()
864 {
865  int rc = -1, temp = -1;
866  char resp[LAKESIDE_LEN] = {0};
867  char cmd[] = "?e#";
868 
869  if (!SendCmd(cmd))
870  {
871  return false;
872  }
873 
874  if (!ReadBuffer(resp))
875  {
876  return false;
877  }
878 
879  // Slope 1 Period is in form ennnnn#
880  // where nnnnn is number of 0.1 step increments, space left padded
881  rc = sscanf(resp, "e%5d#", &temp);
882 
883  if ( temp >= 0)
884  {
885  Slope1PeriodN[0].value = temp;
886  LOGF_DEBUG("updateSlope1Period: Slope 1 Period is (%d)", temp);
887  }
888  else
889  {
890  LOGF_ERROR("updateSlope1Period: Slope 1 Period request error (%s)", resp);
891  return false;
892  }
893 
894  return true;
895 }
896 
897 // get Slope 2 time period
898 bool Lakeside::updateSlope2Period()
899 {
900  int rc = -1, temp = -1;
901  char resp[LAKESIDE_LEN] = {0};
902  char cmd[] = "?f#";
903 
904  if (!SendCmd(cmd))
905  {
906  return false;
907  }
908 
909  if (!ReadBuffer(resp))
910  {
911  return false;
912  }
913 
914  // Slope 2 Period is in form ennnnn#
915  // where nnnnn is number of 0.1 step increments, space left padded
916  rc = sscanf(resp, "f%5d#", &temp);
917 
918  if ( temp >= 0)
919  {
920  Slope2PeriodN[0].value = temp;
921  LOGF_DEBUG("updateSlope2Period: Slope 2 Period is (%d)", temp);
922  }
923  else
924  {
925  LOGF_ERROR("updateSlope2Period: Slope 2 Period request error (%s)", resp);
926  return false;
927  }
928 
929  return true;
930 }
931 
932 // Get Max travel
933 bool Lakeside::updateMaxTravel()
934 {
935  int rc = -1, temp = -1;
936  char resp[LAKESIDE_LEN] = {0};
937  char cmd[] = "?I#";
938 
939  if (!SendCmd(cmd))
940  {
941  return false;
942  }
943 
944  if (!ReadBuffer(resp))
945  {
946  return false;
947  }
948 
949  // MaxTravel is in form Innnnn#
950  // where nnnnn is 0 - 65536, space left padded
951  rc = sscanf(resp, "I%5d#", &temp);
952 
953  if ( temp > 0)
954  {
955  FocusMaxPosN[0].value = temp;
956  LOGF_DEBUG("updateMaxTravel: MaxTravel is (%d)", temp);
957  }
958  else
959  {
960  LOGF_ERROR("updateMaxTravel: MaxTravel request error (%s)", resp);
961  return false;
962  }
963 
964  return true;
965 }
966 
967 // get step size
968 bool Lakeside::updateStepSize()
969 {
970  int rc = -1, temp = -1;
971  char resp[LAKESIDE_LEN] = {0};
972  char cmd[] = "?S#";
973 
974  if (!SendCmd(cmd))
975  {
976  return false;
977  }
978 
979  LOGF_DEBUG("updateStepSize: Sent (%s)", cmd);
980 
981  if (!ReadBuffer(resp))
982  {
983  return false;
984  }
985 
986  // StepSize is in form Snnnnn#
987  // where nnnnn is 0 - ??, space left padded
988  rc = sscanf(resp, "S%5d#", &temp);
989 
990  if ( temp > 0)
991  {
992  StepSizeN[0].value = temp;
993  LOGF_DEBUG("updateStepSize: step size is (%d)", temp);
994  }
995  else
996  {
997  LOGF_ERROR("updateStepSize: StepSize request error (%s)", resp);
998  return false;
999  }
1000 
1001  return true;
1002 }
1003 
1004 //
1005 // NOTE : set via hand controller
1006 //
1007 bool Lakeside::setCalibration()
1008 {
1009  return true;
1010 }
1011 
1012 // Move focuser to "position"
1013 bool Lakeside::gotoPosition(uint32_t position)
1014 {
1015  int calc_steps = 0;
1016  char cmd[LAKESIDE_LEN] = {0};
1017 
1018  // Lakeside only uses move NNNNN steps - goto step not available.
1019  // calculate as steps to move = current position - new position
1020  // if -ve then move out, else +ve moves in
1021  calc_steps = FocusAbsPosN[0].value - position;
1022 
1023  // MaxTravelN[0].value is set by "calibrate" via the control box, & read at connect
1024  if ( position > FocusMaxPosN[0].value )
1025  {
1026  LOGF_ERROR("Position requested (%ld) is out of bounds between %g and %g", position, FocusAbsPosN[0].min, FocusMaxPosN[0].value);
1028  return false;
1029  }
1030 
1031  // -ve == Move Out
1032  if ( calc_steps < 0 )
1033  {
1034  sprintf(cmd, "CO%d#", abs(calc_steps));
1035  LOGF_DEBUG("MoveFocuser: move-out cmd to send (%s)", cmd);
1036  }
1037  else
1038  // ve == Move In
1039  if ( calc_steps > 0 )
1040  {
1041  // Move in nnnnn steps = CInnnnn#
1042  sprintf(cmd, "CI%d#", calc_steps);
1043  LOGF_DEBUG("MoveFocuser: move-in cmd to send (%s)", cmd);
1044  }
1045  else
1046  {
1047  // Zero == no steps to move
1048  LOGF_DEBUG("MoveFocuser: No steps to move. calc_steps = %d", calc_steps);
1050  return false;
1051  }
1052 
1053  // flush ready to move
1054  tcflush(PortFD, TCIOFLUSH);
1055 
1056  if (!SendCmd(cmd))
1057  {
1059  return false;
1060  }
1061  else
1062  LOGF_DEBUG("MoveFocuser: Sent cmd (%s)", cmd);
1063 
1064  // At this point, the move command has been sent, so set BUSY & return true
1066  return true;
1067 }
1068 
1069 bool Lakeside::SetFocuserBacklash(int32_t steps)
1070 {
1071  return setBacklash(steps);
1072 }
1073 
1074 //
1075 // Set backlash compensation
1076 //
1077 bool Lakeside::setBacklash(int backlash )
1078 {
1079  char cmd[LAKESIDE_LEN] = {0};
1080  char resp[LAKESIDE_LEN] = {0};
1081 
1082  tcflush(PortFD, TCIOFLUSH);
1083 
1084  //CRBnnn#
1085  sprintf(cmd, "CRB%d#", backlash);
1086 
1087  if (!SendCmd(cmd))
1088  {
1089  return false;
1090  }
1091 
1092  if (!ReadBuffer(resp))
1093  {
1094  return false;
1095  }
1096 
1097  if (!strncmp(resp, "OK#", 3))
1098  {
1099  LOGF_INFO("Backlash steps set to %d", backlash);
1100  }
1101  else
1102  {
1103  LOGF_ERROR("setBacklash: Unknown result (%s)", resp);
1104  return false;
1105  }
1106 
1107  return true;
1108 }
1109 
1110 //
1111 // NOTE : set via hand controller
1112 // Here for example
1113 //
1114 bool Lakeside::setStepSize(int stepsize )
1115 {
1116  char cmd[LAKESIDE_LEN] = {0};
1117  char resp[LAKESIDE_LEN] = {0};
1118 
1119  tcflush(PortFD, TCIOFLUSH);
1120 
1121  // CRSnnnnn#
1122  sprintf(cmd, "CRS%d#", stepsize);
1123 
1124  if (!SendCmd(cmd))
1125  {
1126  return false;
1127  }
1128 
1129  if (!ReadBuffer(resp))
1130  {
1131  return false;
1132  }
1133 
1134  if (!strncmp(resp, "OK#", 3))
1135  {
1136  LOGF_DEBUG("setStepSize: cmd (%s) - %s", cmd, resp);
1137  }
1138  else
1139  {
1140  LOGF_ERROR("setStepSize: Unknown result (%s)", resp);
1141  return false;
1142  }
1143 
1144  return true;
1145 }
1146 
1147 //
1148 // NOTE : set via hand controller
1149 // Use calibrate routine on controller box
1150 //
1151 bool Lakeside::setMaxTravel(int /*maxtravel*/ )
1152 {
1153  return true;
1154 }
1155 
1156 // Change Move Direction
1157 // 0 = Normal direction
1158 // 1 = Reverse direction
1159 // In case motor connection is on reverse side of the focus shaft
1160 // NOTE : This just reverses the voltage sent to the motor
1161 // & does NOT reverse the CI / CO commands
1162 //bool Lakeside::setMoveDirection(int direction)
1163 bool Lakeside::ReverseFocuser(bool enabled)
1164 {
1165  char cmd[LAKESIDE_LEN] = {0};
1166  char resp[LAKESIDE_LEN] = {0};
1167 
1168  tcflush(PortFD, TCIOFLUSH);
1169 
1170  strncpy(cmd, enabled ? "CRD1#" : "CRD0#", LAKESIDE_LEN);
1171 
1172  // if (direction == 0)
1173  // strncpy(cmd, "CRD0#", LAKESIDE_LEN);
1174  // else
1175  // if (direction == 1)
1176  // strncpy(cmd, "CRD1#", LAKESIDE_LEN);
1177  // else
1178  // {
1179  // LOGF_ERROR("setMoveDirection: Unknown direction (%d)", direction);
1180  // return false;
1181  // }
1182 
1183  if (!SendCmd(cmd))
1184  {
1185  return false;
1186  }
1187 
1188  if (!ReadBuffer(resp))
1189  {
1190  return false;
1191  }
1192 
1193  if (!strncmp(resp, "OK#", 3))
1194  {
1195  LOGF_DEBUG("setMoveDirection: Completed cmd (%s). Result - %s", cmd, resp);
1196  if (!enabled)
1197  LOG_INFO("Move Direction : Normal");
1198  else
1199  LOG_INFO("Move Direction : Reversed");
1200  }
1201  else
1202  {
1203  LOGF_ERROR("setMoveDirection: Unknown result (%s)", resp);
1204  return false;
1205  }
1206 
1207  return true;
1208 }
1209 
1210 // Enable/disable Temperature Tracking functionality
1211 bool Lakeside::setTemperatureTracking(bool enable)
1212 {
1213  int nbytes_written = 0, rc = -1;
1214  char errstr[MAXRBUF];
1215  char cmd[LAKESIDE_LEN] = {0};
1216 
1217  // flush all
1218  tcflush(PortFD, TCIOFLUSH);
1219 
1220  if (enable)
1221  strncpy(cmd, "CTN#", LAKESIDE_LEN);
1222  else
1223  strncpy(cmd, "CTF#", LAKESIDE_LEN);
1224 
1225  if ( (rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK)
1226  {
1227  tty_error_msg(rc, errstr, MAXRBUF);
1228  LOGF_ERROR("setTemperatureTracking: Write for command (%s) failed - %s", cmd, errstr);
1229  return false;
1230  }
1231  else
1232  {
1233  LOGF_DEBUG("setTemperatureTracking: Sent (%s)", cmd);
1234  if (enable)
1235  LOG_INFO("Temperature Tracking : Enabled");
1236  else
1237  LOG_INFO("Temperature Tracking : Disabled");
1238  }
1239 
1240  // NOTE: NO reply string is sent back
1241 
1242  return true;
1243 
1244 }
1245 
1246 // Set which Active Temperature slope to use : 1 or 2
1247 bool Lakeside::setActiveTemperatureSlope(uint32_t active_slope)
1248 {
1249  char cmd[LAKESIDE_LEN] = {0};
1250  char resp[LAKESIDE_LEN] = {0};
1251 
1252  // flush all
1253  tcflush(PortFD, TCIOFLUSH);
1254 
1255  // slope in is either 1 or 2
1256  // CRg1# : Slope 1
1257  // CRg2# : Slope 2
1258 
1259  sprintf(cmd, "CRg%d#", active_slope);
1260 
1261  if (!SendCmd(cmd))
1262  {
1263  return false;
1264  }
1265 
1266  LOGF_DEBUG("setActiveTemperatureSlope: Sent (%s)", cmd);
1267 
1268  if (!ReadBuffer(resp))
1269  {
1270  return false;
1271  }
1272 
1273  if (!strncmp(resp, "OK#", 3))
1274  {
1275  LOGF_INFO("Selected Active Temperature Slope is %d", active_slope);
1276  }
1277  else
1278  {
1279  LOGF_ERROR("setActiveTemperatureSlope: Unknown result (%s)", resp);
1280  return false;
1281  }
1282 
1283  return true;
1284 
1285 }
1286 
1287 //
1288 // Set Slope 1 0.1 step increments
1289 //
1290 bool Lakeside::setSlope1Inc(uint32_t slope1_inc)
1291 {
1292  char cmd[LAKESIDE_LEN] = {0};
1293  char resp[LAKESIDE_LEN] = {0};
1294 
1295  tcflush(PortFD, TCIOFLUSH);
1296 
1297  //CR1nnn#
1298  sprintf(cmd, "CR1%d#", slope1_inc);
1299 
1300  if (!SendCmd(cmd))
1301  {
1302  return false;
1303  }
1304 
1305  if (!ReadBuffer(resp))
1306  {
1307  return false;
1308  }
1309 
1310  if (!strncmp(resp, "OK#", 3))
1311  {
1312  LOGF_INFO("Slope 1 0.1 counts per degree set to %d", slope1_inc);
1313  }
1314  else
1315  {
1316  LOGF_ERROR("setSlope1Inc: Unknown result (%s)", resp);
1317  return false;
1318  }
1319 
1320  return true;
1321 }
1322 
1323 //
1324 // Set Slope 2 0.1 step increments
1325 //
1326 bool Lakeside::setSlope2Inc(uint32_t slope2_inc)
1327 {
1328  char cmd[LAKESIDE_LEN] = {0};
1329  char resp[LAKESIDE_LEN] = {0};
1330 
1331  tcflush(PortFD, TCIOFLUSH);
1332 
1333  //CR2nnn#
1334  sprintf(cmd, "CR2%d#", slope2_inc);
1335 
1336  if (!SendCmd(cmd))
1337  {
1338  return false;
1339  }
1340 
1341  if (!ReadBuffer(resp))
1342  {
1343  return false;
1344  }
1345 
1346  if (!strncmp(resp, "OK#", 3))
1347  {
1348  LOGF_INFO("Slope 2 0.1 counts per degree set to %d", slope2_inc);
1349  }
1350  else
1351  {
1352  LOGF_ERROR("setSlope2Inc: Unknown result (%s)", resp);
1353  return false;
1354  }
1355 
1356  return true;
1357 }
1358 
1359 //
1360 // Set slope 1 direction 0 or 1
1361 //
1362 bool Lakeside::setSlope1Dir(uint32_t slope1_direction)
1363 {
1364  char cmd[LAKESIDE_LEN] = {0};
1365  char resp[LAKESIDE_LEN] = {0};
1366 
1367  tcflush(PortFD, TCIOFLUSH);
1368 
1369  //CRannn#
1370  sprintf(cmd, "CRa%d#", slope1_direction);
1371 
1372  if (!SendCmd(cmd))
1373  {
1374  return false;
1375  }
1376 
1377  if (!ReadBuffer(resp))
1378  {
1379  return false;
1380  }
1381 
1382  if (!strncmp(resp, "OK#", 3))
1383  {
1384  LOGF_INFO("Slope 1 Direction set to %d", slope1_direction);
1385  }
1386  else
1387  {
1388  LOGF_ERROR("setSlope1Dir: Unknown result (%s)", resp);
1389  return false;
1390  }
1391 
1392  return true;
1393 }
1394 
1395 //
1396 // Set Slope 2 Direction 0 or 1
1397 //
1398 bool Lakeside::setSlope2Dir(uint32_t slope2_direction)
1399 {
1400  char cmd[LAKESIDE_LEN] = {0};
1401  char resp[LAKESIDE_LEN] = {0};
1402 
1403  tcflush(PortFD, TCIOFLUSH);
1404 
1405  //CRannn#
1406  sprintf(cmd, "CRb%d#", slope2_direction);
1407 
1408  if (!SendCmd(cmd))
1409  {
1410  return false;
1411  }
1412 
1413  if (!ReadBuffer(resp))
1414  {
1415  return false;
1416  }
1417 
1418  if (!strncmp(resp, "OK#", 3))
1419  {
1420  LOGF_INFO("Slope 2 Direction set to %d", slope2_direction);
1421  }
1422  else
1423  {
1424  LOGF_ERROR("setSlope2Dir: Unknown result (%s)", resp);
1425  return false;
1426  }
1427 
1428  return true;
1429 }
1430 
1431 //
1432 // Set Slope 1 Deadband 0 - 255
1433 //
1434 bool Lakeside::setSlope1Deadband(uint32_t slope1_deadband)
1435 {
1436  char cmd[LAKESIDE_LEN] = {0};
1437  char resp[LAKESIDE_LEN] = {0};
1438 
1439  tcflush(PortFD, TCIOFLUSH);
1440 
1441  //CRcnnn#
1442  sprintf(cmd, "CRc%d#", slope1_deadband);
1443 
1444  if (!SendCmd(cmd))
1445  {
1446  return false;
1447  }
1448 
1449  if (!ReadBuffer(resp))
1450  {
1451  return false;
1452  }
1453 
1454  if (!strncmp(resp, "OK#", 3))
1455  {
1456  LOGF_INFO("Slope 1 deadband set to %d", slope1_deadband);
1457  }
1458  else
1459  {
1460  LOGF_ERROR("setSlope1Deadband: Unknown result (%s)", resp);
1461  return false;
1462  }
1463 
1464  return true;
1465 }
1466 
1467 //
1468 // Set Slope 1 Deadband 0 - 255
1469 //
1470 bool Lakeside::setSlope2Deadband(uint32_t slope2_deadband)
1471 {
1472  char cmd[LAKESIDE_LEN] = {0};
1473  char resp[LAKESIDE_LEN] = {0};
1474 
1475  tcflush(PortFD, TCIOFLUSH);
1476 
1477  //CRdnnn#
1478  sprintf(cmd, "CRd%d#", slope2_deadband);
1479 
1480  if (!SendCmd(cmd))
1481  {
1482  return false;
1483  }
1484 
1485  if (!ReadBuffer(resp))
1486  {
1487  return false;
1488  }
1489 
1490  if (!strncmp(resp, "OK#", 3))
1491  {
1492  LOGF_INFO("Slope 2 deadband set to %d", slope2_deadband);
1493  }
1494  else
1495  {
1496  LOGF_ERROR("setSlope2Deadband: Unknown result (%s)", resp);
1497  return false;
1498  }
1499 
1500  return true;
1501 }
1502 
1503 //
1504 // Set Slope 1 Period in minutes
1505 //
1506 bool Lakeside::setSlope1Period(uint32_t slope1_period)
1507 {
1508  char cmd[LAKESIDE_LEN] = {0};
1509  char resp[LAKESIDE_LEN] = {0};
1510 
1511  tcflush(PortFD, TCIOFLUSH);
1512 
1513  //CRennn#
1514  sprintf(cmd, "CRe%d#", slope1_period);
1515 
1516  if (!SendCmd(cmd))
1517  {
1518  return false;
1519  }
1520 
1521  if (!ReadBuffer(resp))
1522  {
1523  return false;
1524  }
1525 
1526  if (!strncmp(resp, "OK#", 3))
1527  {
1528  LOGF_INFO("Slope 1 Period set to %d", slope1_period);
1529  }
1530  else
1531  {
1532  LOGF_ERROR("setSlope1Period: Unknown result (%s)", resp);
1533  return false;
1534  }
1535 
1536  return true;
1537 }
1538 
1539 //
1540 // Set Slope 2 Period in minutes
1541 //
1542 bool Lakeside::setSlope2Period(uint32_t slope2_period)
1543 {
1544  char cmd[LAKESIDE_LEN] = {0};
1545  char resp[LAKESIDE_LEN] = {0};
1546 
1547  tcflush(PortFD, TCIOFLUSH);
1548 
1549  //CRfnnn#
1550  sprintf(cmd, "CRf%d#", slope2_period);
1551 
1552  if (!SendCmd(cmd))
1553  {
1554  return false;
1555  }
1556 
1557  if (!ReadBuffer(resp))
1558  {
1559  return false;
1560  }
1561 
1562  if (!strncmp(resp, "OK#", 3))
1563  {
1564  LOGF_INFO("Slope 2 Period set to %d", slope2_period);
1565  }
1566  else
1567  {
1568  LOGF_ERROR("setSlope2Period: Unknown result (%s)", resp);
1569  return false;
1570  }
1571 
1572  return true;
1573 }
1574 
1575 //
1576 // Process client new switch
1577 //
1578 bool Lakeside::ISNewSwitch (const char * dev, const char * name, ISState * states, char * names[], int n)
1579 {
1580  if(strcmp(dev, getDeviceName()) == 0)
1581  {
1582  // Move Direction
1583  // if (!strcmp(MoveDirectionSP.name, name))
1584  // {
1585  // bool rc=false;
1586  // int current_mode = IUFindOnSwitchIndex(&MoveDirectionSP);
1587  // IUUpdateSwitch(&MoveDirectionSP, states, names, n);
1588  // int target_mode = IUFindOnSwitchIndex(&MoveDirectionSP);
1589  // if (current_mode == target_mode)
1590  // {
1591  // MoveDirectionSP.s = IPS_OK;
1592  // IDSetSwitch(&MoveDirectionSP, nullptr);
1593  // }
1594  // // switch will be either 0 for normal or 1 for reverse
1595  // rc = setMoveDirection(target_mode);
1596 
1597  // if (rc == false)
1598  // {
1599  // IUResetSwitch(&MoveDirectionSP);
1600  // MoveDirectionS[current_mode].s = ISS_ON;
1601  // MoveDirectionSP.s = IPS_ALERT;
1602  // IDSetSwitch(&MoveDirectionSP, nullptr);
1603  // return false;
1604  // }
1605 
1606  // MoveDirectionSP.s = IPS_OK;
1607  // IDSetSwitch(&MoveDirectionSP, nullptr);
1608  // return true;
1609  // }
1610 
1611  // Temperature Tracking
1612  if (!strcmp(TemperatureTrackingSP.name, name))
1613  {
1614  int last_index = IUFindOnSwitchIndex(&TemperatureTrackingSP);
1615  IUUpdateSwitch(&TemperatureTrackingSP, states, names, n);
1616 
1617  bool rc = setTemperatureTracking((TemperatureTrackingS[0].s == ISS_ON));
1618 
1619  if (rc == false)
1620  {
1621  TemperatureTrackingSP.s = IPS_ALERT;
1622  IUResetSwitch(&TemperatureTrackingSP);
1623  TemperatureTrackingS[last_index].s = ISS_ON;
1624  IDSetSwitch(&TemperatureTrackingSP, nullptr);
1625  return false;
1626  }
1627 
1628  TemperatureTrackingSP.s = IPS_OK;
1629  IDSetSwitch(&TemperatureTrackingSP, nullptr);
1630 
1631  return true;
1632  }
1633 
1634  // Active Temperature Slope
1635  if (!strcmp(ActiveTemperatureSlopeSP.name, name))
1636  {
1637  bool rc = false;
1638  int current_slope = IUFindOnSwitchIndex(&ActiveTemperatureSlopeSP);
1639  // current slope Selection will be either 1 or 2
1640  // Need to add 1 to array index, as it starts at 0
1641  current_slope++;
1642  IUUpdateSwitch(&ActiveTemperatureSlopeSP, states, names, n);
1643  int target_slope = IUFindOnSwitchIndex(&ActiveTemperatureSlopeSP);
1644  // target slope Selection will be either 1 or 2
1645  // Need to add 1 to array index, as it starts at 0
1646  target_slope++;
1647  if (current_slope == target_slope)
1648  {
1649  ActiveTemperatureSlopeSP.s = IPS_OK;
1650  IDSetSwitch(&ActiveTemperatureSlopeSP, nullptr);
1651  }
1652 
1653  rc = setActiveTemperatureSlope(target_slope);
1654 
1655  if (rc == false)
1656  {
1657  current_slope--;
1658  IUResetSwitch(&ActiveTemperatureSlopeSP);
1659  ActiveTemperatureSlopeS[current_slope].s = ISS_ON;
1660  ActiveTemperatureSlopeSP.s = IPS_ALERT;
1661  IDSetSwitch(&ActiveTemperatureSlopeSP, nullptr);
1662  return false;
1663  }
1664 
1665  ActiveTemperatureSlopeSP.s = IPS_OK;
1666  IDSetSwitch(&ActiveTemperatureSlopeSP, nullptr);
1667  return true;
1668  }
1669 
1670  // Slope 1 direction - either 0 or 1
1671  if (!strcmp(Slope1DirSP.name, name))
1672  {
1673  bool rc = false;
1674  int current_slope_dir1 = IUFindOnSwitchIndex(&Slope1DirSP);
1675  // current slope 1 Direction will be either 0 or 1
1676 
1677  IUUpdateSwitch(&Slope1DirSP, states, names, n);
1678  int target_slope_dir1 = IUFindOnSwitchIndex(&Slope1DirSP);
1679  // target slope Selection will be either 0 or 1
1680 
1681  if (current_slope_dir1 == target_slope_dir1)
1682  {
1683  Slope1DirSP.s = IPS_OK;
1684  IDSetSwitch(&Slope1DirSP, nullptr);
1685  }
1686 
1687  rc = setSlope1Dir(target_slope_dir1);
1688 
1689  if (rc == false)
1690  {
1691  IUResetSwitch(&Slope1DirSP);
1692  Slope1DirS[current_slope_dir1].s = ISS_ON;
1693  Slope1DirSP.s = IPS_ALERT;
1694  IDSetSwitch(&Slope1DirSP, nullptr);
1695  return false;
1696  }
1697 
1698  Slope1DirSP.s = IPS_OK;
1699  IDSetSwitch(&Slope1DirSP, nullptr);
1700  return true;
1701  }
1702  }
1703 
1704  // Slope 2 direction - either 0 or 1
1705  if (!strcmp(Slope2DirSP.name, name))
1706  {
1707  bool rc = false;
1708  int current_slope_dir2 = IUFindOnSwitchIndex(&Slope2DirSP);
1709  // current slope 2 Direction will be either 0 or 1
1710 
1711  IUUpdateSwitch(&Slope2DirSP, states, names, n);
1712  int target_slope_dir2 = IUFindOnSwitchIndex(&Slope2DirSP);
1713  // target slope 2 Selection will be either 0 or 1
1714 
1715  if (current_slope_dir2 == target_slope_dir2)
1716  {
1717  Slope2DirSP.s = IPS_OK;
1718  IDSetSwitch(&Slope2DirSP, nullptr);
1719  }
1720 
1721  rc = setSlope2Dir(target_slope_dir2);
1722 
1723  if (rc == false)
1724  {
1725  IUResetSwitch(&Slope2DirSP);
1726  Slope2DirS[current_slope_dir2].s = ISS_ON;
1727  Slope2DirSP.s = IPS_ALERT;
1728  IDSetSwitch(&Slope2DirSP, nullptr);
1729  return false;
1730  }
1731 
1732  Slope2DirSP.s = IPS_OK;
1733  IDSetSwitch(&Slope2DirSP, nullptr);
1734  return true;
1735  }
1736 
1737  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
1738 }
1739 
1740 //
1741 // Process client new number
1742 //
1743 bool Lakeside::ISNewNumber (const char * dev, const char * name, double values[], char * names[], int n)
1744 {
1745  int i = 0;
1746 
1747  if(strcmp(dev, getDeviceName()) == 0)
1748  {
1749  // // max travel - read only
1750  // if (!strcmp (name, MaxTravelNP.name))
1751  // {
1752  // IUUpdateNumber(&MaxTravelNP, values, names, n);
1753  // MaxTravelNP.s = IPS_OK;
1754  // IDSetNumber(&MaxTravelNP, nullptr);
1755  // return true;
1756  // }
1757 
1758  // Backlash compensation
1759  // if (!strcmp (name, FocusBacklashNP.name))
1760  // {
1761  // int new_back = 0 ;
1762  // int nset = 0;
1763 
1764  // for (nset = i = 0; i < n; i++)
1765  // {
1766  // //Find numbers with the passed names in SetFocusBacklashNP property
1767  // INumber * eqp = IUFindNumber (&FocusBacklashNP, names[i]);
1768 
1769  // //If the number found is Backlash (FocusBacklashN[0]) then process it
1770  // if (eqp == &FocusBacklashN[0])
1771  // {
1772 
1773  // new_back = (values[i]);
1774 
1775  // // limits
1776  // nset += new_back >= -0xff && new_back <= 0xff;
1777  // }
1778  // if (nset == 1)
1779  // {
1780 
1781  // // Set the Lakeside state to BUSY
1782  // FocusBacklashNP.s = IPS_BUSY;
1783  // IDSetNumber(&FocusBacklashNP, nullptr);
1784 
1785  // if( !setBacklash(new_back))
1786  // {
1787 
1788  // FocusBacklashNP.s = IPS_IDLE;
1789  // IDSetNumber(&FocusBacklashNP, "Setting new backlash failed.");
1790 
1791  // return false ;
1792  // }
1793 
1794  // FocusBacklashNP.s = IPS_OK;
1795  // FocusBacklashN[0].value = new_back;
1796  // IDSetNumber(&FocusBacklashNP, nullptr);
1797 
1798  // return true;
1799  // }
1800  // else
1801  // {
1802 
1803  // FocusBacklashNP.s = IPS_IDLE;
1804  // IDSetNumber(&FocusBacklashNP, "Need exactly one parameter.");
1805 
1806  // return false ;
1807  // }
1808 
1809  // }
1810  // }
1811 
1812  // Step size - read only
1813  if (!strcmp (name, StepSizeNP.name))
1814  {
1815  IUUpdateNumber(&StepSizeNP, values, names, n);
1816  StepSizeNP.s = IPS_OK;
1817  IDSetNumber(&StepSizeNP, nullptr);
1818  return true;
1819  }
1820 
1821  // Slope 1 Increments
1822  if (!strcmp (name, Slope1IncNP.name))
1823  {
1824  int new_Slope1Inc = 0 ;
1825  int nset = 0;
1826 
1827  for (nset = i = 0; i < n; i++)
1828  {
1829  //Find numbers with the passed names in SetSlope1IncNP property
1830  INumber * eqp = IUFindNumber (&Slope1IncNP, names[i]);
1831 
1832  //If the number found is Slope1Inc (Slope1IncN[0]) then process it
1833  if (eqp == &Slope1IncN[0])
1834  {
1835 
1836  new_Slope1Inc = (values[i]);
1837 
1838  // limits
1839  nset += new_Slope1Inc >= -0xff && new_Slope1Inc <= 0xff;
1840  }
1841  if (nset == 1)
1842  {
1843 
1844  // Set the Lakeside state to BUSY
1845  Slope1IncNP.s = IPS_BUSY;
1846  IDSetNumber(&Slope1IncNP, nullptr);
1847 
1848  if( !setSlope1Inc(new_Slope1Inc))
1849  {
1850 
1851  Slope1IncNP.s = IPS_IDLE;
1852  IDSetNumber(&Slope1IncNP, "Setting new Slope1 increment failed.");
1853 
1854  return false ;
1855  }
1856 
1857  Slope1IncNP.s = IPS_OK;
1858  Slope1IncN[0].value = new_Slope1Inc;
1859  IDSetNumber(&Slope1IncNP, nullptr) ;
1860 
1861  return true;
1862  }
1863  else
1864  {
1865 
1866  Slope1IncNP.s = IPS_IDLE;
1867  IDSetNumber(&Slope1IncNP, "Need exactly one parameter.");
1868 
1869  return false ;
1870  }
1871 
1872  }
1873  }
1874 
1875  // Slope 2 Increments
1876  if (!strcmp (name, Slope2IncNP.name))
1877  {
1878  int new_Slope2Inc = 0 ;
1879  int nset = 0;
1880 
1881  for (nset = i = 0; i < n; i++)
1882  {
1883  //Find numbers with the passed names in SetSlope2IncNP property
1884  INumber * eqp = IUFindNumber (&Slope2IncNP, names[i]);
1885 
1886  //If the number found is Slope2Inc (Slope2IncN[0]) then process it
1887  if (eqp == &Slope2IncN[0])
1888  {
1889 
1890  new_Slope2Inc = (values[i]);
1891 
1892  // limits
1893  nset += new_Slope2Inc >= -0xff && new_Slope2Inc <= 0xff;
1894  }
1895  if (nset == 1)
1896  {
1897 
1898  // Set the Lakeside state to BUSY
1899  Slope2IncNP.s = IPS_BUSY;
1900  IDSetNumber(&Slope2IncNP, nullptr);
1901 
1902  if( !setSlope2Inc(new_Slope2Inc))
1903  {
1904 
1905  Slope2IncNP.s = IPS_IDLE;
1906  IDSetNumber(&Slope2IncNP, "Setting new Slope2 increment failed.");
1907 
1908  return false ;
1909  }
1910 
1911  Slope2IncNP.s = IPS_OK;
1912  Slope2IncN[0].value = new_Slope2Inc;
1913  IDSetNumber(&Slope2IncNP, nullptr);
1914 
1915  return true;
1916  }
1917  else
1918  {
1919 
1920  Slope2IncNP.s = IPS_IDLE;
1921  IDSetNumber(&Slope2IncNP, "Need exactly one parameter.");
1922 
1923  return false ;
1924  }
1925 
1926  }
1927  }
1928 
1929  // Slope 1 Deadband
1930  if (!strcmp (name, Slope1DeadbandNP.name))
1931  {
1932  int new_Slope1Deadband = 0 ;
1933  int nset = 0;
1934 
1935  for (nset = i = 0; i < n; i++)
1936  {
1937  //Find numbers with the passed names in SetSlope1DeadbandNP property
1938  INumber * eqp = IUFindNumber (&Slope1DeadbandNP, names[i]);
1939 
1940  //If the number found is Slope1Deadband (Slope1DeadbandN[0]) then process it
1941  if (eqp == &Slope1DeadbandN[0])
1942  {
1943 
1944  new_Slope1Deadband = (values[i]);
1945 
1946  // limits
1947  nset += new_Slope1Deadband >= -0xff && new_Slope1Deadband <= 0xff;
1948  }
1949  if (nset == 1)
1950  {
1951 
1952  // Set the Lakeside state to BUSY
1953  Slope1DeadbandNP.s = IPS_BUSY;
1954  IDSetNumber(&Slope1DeadbandNP, nullptr);
1955 
1956  if( !setSlope1Deadband(new_Slope1Deadband))
1957  {
1958 
1959  Slope1DeadbandNP.s = IPS_IDLE;
1960  IDSetNumber(&Slope1DeadbandNP, "Setting new Slope 1 Deadband failed.");
1961 
1962  return false ;
1963  }
1964 
1965  Slope1DeadbandNP.s = IPS_OK;
1966  Slope1DeadbandN[0].value = new_Slope1Deadband;
1967  IDSetNumber(&Slope1DeadbandNP, nullptr) ;
1968 
1969  return true;
1970  }
1971  else
1972  {
1973 
1974  Slope1DeadbandNP.s = IPS_IDLE;
1975  IDSetNumber(&Slope1DeadbandNP, "Need exactly one parameter.");
1976 
1977  return false ;
1978  }
1979 
1980  }
1981  }
1982 
1983  // Slope 2 Deadband
1984  if (!strcmp (name, Slope2DeadbandNP.name))
1985  {
1986  int new_Slope2Deadband = 0 ;
1987  int nset = 0;
1988 
1989  for (nset = i = 0; i < n; i++)
1990  {
1991  //Find numbers with the passed names in SetSlope2DeadbandNP property
1992  INumber * eqp = IUFindNumber (&Slope2DeadbandNP, names[i]);
1993 
1994  //If the number found is Slope2Deadband (Slope2DeadbandN[0]) then process it
1995  if (eqp == &Slope2DeadbandN[0])
1996  {
1997 
1998  new_Slope2Deadband = (values[i]);
1999 
2000  // limits
2001  nset += new_Slope2Deadband >= -0xff && new_Slope2Deadband <= 0xff;
2002  }
2003  if (nset == 1)
2004  {
2005 
2006  // Set the Lakeside state to BUSY
2007  Slope2DeadbandNP.s = IPS_BUSY;
2008  IDSetNumber(&Slope2DeadbandNP, nullptr);
2009 
2010  if( !setSlope2Deadband(new_Slope2Deadband))
2011  {
2012 
2013  Slope2DeadbandNP.s = IPS_IDLE;
2014  IDSetNumber(&Slope2DeadbandNP, "Setting new Slope 2 Deadband failed.");
2015 
2016  return false ;
2017  }
2018 
2019  Slope2DeadbandNP.s = IPS_OK;
2020  Slope2DeadbandN[0].value = new_Slope2Deadband;
2021  IDSetNumber(&Slope2DeadbandNP, nullptr) ;
2022 
2023  return true;
2024  }
2025  else
2026  {
2027 
2028  Slope2DeadbandNP.s = IPS_IDLE;
2029  IDSetNumber(&Slope2DeadbandNP, "Need exactly one parameter.");
2030 
2031  return false ;
2032  }
2033 
2034  }
2035  }
2036 
2037  // Slope 1 Period Minutes
2038  if (!strcmp (name, Slope1PeriodNP.name))
2039  {
2040  int new_Slope1Period = 0 ;
2041  int nset = 0;
2042 
2043  for (nset = i = 0; i < n; i++)
2044  {
2045  //Find numbers with the passed names in SetSlope1PeriodNP property
2046  INumber * eqp = IUFindNumber (&Slope1PeriodNP, names[i]);
2047 
2048  //If the number found is Slope1Period (Slope1PeriodN[0]) then process it
2049  if (eqp == &Slope1PeriodN[0])
2050  {
2051 
2052  new_Slope1Period = (values[i]);
2053 
2054  // limits
2055  nset += new_Slope1Period >= -0xff && new_Slope1Period <= 0xff;
2056  }
2057  if (nset == 1)
2058  {
2059 
2060  // Set the Lakeside state to BUSY
2061  Slope1PeriodNP.s = IPS_BUSY;
2062  IDSetNumber(&Slope1PeriodNP, nullptr);
2063 
2064  if( !setSlope1Period(new_Slope1Period))
2065  {
2066 
2067  Slope1PeriodNP.s = IPS_IDLE;
2068  IDSetNumber(&Slope1PeriodNP, "Setting new Slope 1 Period failed.");
2069 
2070  return false ;
2071  }
2072 
2073  Slope1PeriodNP.s = IPS_OK;
2074  Slope1PeriodN[0].value = new_Slope1Period;
2075  IDSetNumber(&Slope1PeriodNP, nullptr);
2076 
2077  return true;
2078  }
2079  else
2080  {
2081 
2082  Slope1PeriodNP.s = IPS_IDLE;
2083  IDSetNumber(&Slope1PeriodNP, "Need exactly one parameter.");
2084 
2085  return false ;
2086  }
2087 
2088  }
2089  }
2090 
2091  // Slope 2 Period Minutes
2092  if (!strcmp (name, Slope2PeriodNP.name))
2093  {
2094  int new_Slope2Period = 0 ;
2095  int nset = 0;
2096 
2097  for (nset = i = 0; i < n; i++)
2098  {
2099  //Find numbers with the passed names in SetSlope2PeriodNP property
2100  INumber * eqp = IUFindNumber (&Slope2PeriodNP, names[i]);
2101 
2102  //If the number found is Slope2Period (Slope2PeriodN[0]) then process it
2103  if (eqp == &Slope2PeriodN[0])
2104  {
2105 
2106  new_Slope2Period = (values[i]);
2107 
2108  // limits
2109  nset += new_Slope2Period >= -0xff && new_Slope2Period <= 0xff;
2110  }
2111  if (nset == 1)
2112  {
2113 
2114  // Set the Lakeside state to BUSY
2115  Slope2PeriodNP.s = IPS_BUSY;
2116  IDSetNumber(&Slope2PeriodNP, nullptr);
2117 
2118  if( !setSlope2Period(new_Slope2Period))
2119  {
2120 
2121  Slope2PeriodNP.s = IPS_IDLE;
2122  IDSetNumber(&Slope2PeriodNP, "Setting new Slope 2 Period failed.");
2123 
2124  return false ;
2125  }
2126 
2127  Slope2PeriodNP.s = IPS_OK;
2128  Slope2PeriodN[0].value = new_Slope2Period;
2129  IDSetNumber(&Slope2PeriodNP, nullptr);
2130 
2131  return true;
2132  }
2133  else
2134  {
2135 
2136  Slope2PeriodNP.s = IPS_IDLE;
2137  IDSetNumber(&Slope2PeriodNP, "Need exactly one parameter.");
2138 
2139  return false ;
2140  }
2141 
2142  }
2143  }
2144  }
2145 
2146  return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
2147 }
2148 
2149 //
2150 // Get focus paraameters
2151 //
2152 void Lakeside::GetFocusParams ()
2153 {
2154  if (updatePosition())
2155  IDSetNumber(&FocusAbsPosNP, nullptr);
2156 
2157  if (updateTemperature())
2158  IDSetNumber(&TemperatureNP, nullptr);
2159 
2160  // This is currently the only time Kelvin is read - just a nice to have
2161  if (updateTemperatureK())
2162  IDSetNumber(&TemperatureKNP, nullptr);
2163 
2164  if (updateBacklash())
2165  IDSetNumber(&FocusBacklashNP, nullptr);
2166 
2167  if (updateMaxTravel())
2168  IDSetNumber(&FocusMaxPosNP, nullptr);
2169 
2170  if (updateStepSize())
2171  IDSetNumber(&StepSizeNP, nullptr);
2172 
2173  if (updateMoveDirection())
2174  IDSetSwitch(&FocusReverseSP, nullptr);
2175 
2176  if (updateSlope1Inc())
2177  IDSetNumber(&Slope1IncNP, nullptr);
2178 
2179  if (updateSlope2Inc())
2180  IDSetNumber(&Slope2IncNP, nullptr);
2181 
2182  if (updateSlope1Dir())
2183  IDSetSwitch(&Slope1DirSP, nullptr);
2184 
2185  if (updateSlope2Dir())
2186  IDSetSwitch(&Slope2DirSP, nullptr);
2187 
2188  if (updateSlope1Deadband())
2189  IDSetNumber(&Slope1DeadbandNP, nullptr);
2190 
2191  if (updateSlope2Deadband())
2192  IDSetNumber(&Slope2DeadbandNP, nullptr);
2193 
2194  if (updateSlope1Period())
2195  IDSetNumber(&Slope1PeriodNP, nullptr);
2196 
2197  if (updateSlope1Period())
2198  IDSetNumber(&Slope2PeriodNP, nullptr);
2199 
2200 }
2201 
2203 {
2204  return MoveAbsFocuser(dir == FOCUS_INWARD ? FocusAbsPosN[0].value - ticks : FocusAbsPosN[0].value + ticks);
2205 }
2206 
2207 //
2208 // Main Lakeside Absolute movement routine
2209 //
2210 IPState Lakeside::MoveAbsFocuser(uint32_t targetTicks)
2211 {
2212  targetPos = targetTicks;
2213  bool rc = false;
2214 
2215  rc = gotoPosition(targetPos);
2216 
2217  return (rc ? IPS_BUSY : IPS_ALERT);
2218 }
2219 
2220 //
2221 // Main timer hit routine
2222 //
2224 {
2225  bool IsMoving = false;
2226  int rc = -1;
2227 
2228  if (isConnected() == false)
2229  {
2231  return;
2232  }
2233 
2234  // focuser supposedly moving...
2235  if (FocusAbsPosNP.s == IPS_BUSY )
2236  {
2237  // Get actual status from focuser
2238  // Note: GetLakesideStatus sends position count when moving.
2239  // Status returns IMoving if moving
2240  IsMoving = GetLakesideStatus();
2241  if ( IsMoving )
2242  {
2243  // GetLakesideStatus() shows position as it is moving
2244  LOG_DEBUG("Focuser is in motion...");
2245  }
2246  else
2247  {
2248  // no longer moving, so reset state to IPS_OK or IDLE?
2249  // IPS_OK turns light green
2251  // update position
2252  // This is necessary in case user clicks short step moves in quick succession
2253  // Lakeside will abort move if command received during move
2254  rc = updatePosition();
2255  IDSetNumber(&FocusAbsPosNP, nullptr);
2256  LOGF_INFO("Focuser reached requested position %.f", FocusAbsPosN[0].value);
2257  }
2258  }
2259 
2260  // focuser not moving, get temperature updates instead
2262  {
2263  // Get a temperature
2264  rc = updateTemperature();
2265  if (rc && fabs(lastTemperature - TemperatureN[0].value) > TEMPERATURE_THRESHOLD)
2266  {
2267  IDSetNumber(&TemperatureNP, nullptr);
2268  lastTemperature = TemperatureN[0].value;
2269  }
2270  }
2271 
2272  // IPS_ALERT - any alert situation generated
2273  // if ( FocusAbsPosNP.s == IPS_ALERT )
2274  // {
2275  // LOG_DEBUG("TimerHit: Focuser state = IPS_ALERT");
2276  // }
2277 
2279 
2280 }
2281 
2282 //
2283 // This will check the status is the focuser - used to check if moving
2284 //
2285 // Returns Pnnnnn# : Focuser Moving : return true
2286 // empty (time out) : Focuser Idle - NOT moving : return false
2287 // Returns DONE# : Focuser Finished moving : return false
2288 // Returns OK# : Focuser NOT moving (catchall) : return false
2289 bool Lakeside::GetLakesideStatus()
2290 {
2291  int rc = -1, nbytes_read = 0, count_timeouts = 1, pos = 0;
2292  char errstr[MAXRBUF];
2293  char resp[LAKESIDE_LEN] = {0};
2294  bool read_buffer = true;
2295  char buffer_response = '?';
2296 
2297  // read buffer up to LAKESIDE_TIMEOUT_RETRIES times
2298  while (read_buffer)
2299  {
2300  //strcpy(resp," ");
2301  memset(resp, 0, sizeof(resp));
2302  // read until 0x23 (#) received
2303  if ( (rc = tty_read_section(PortFD, resp, 0x23, LAKESIDE_TIMEOUT, &nbytes_read)) != TTY_OK)
2304  {
2305  // Retry LAKESIDE_TIMEOUT_RETRIES times to make sure focuser
2306  // is not in between status returns
2307  count_timeouts++;
2308  LOGF_DEBUG("GetLakesideStatus: read buffer retry attempts : %d, error=%s", count_timeouts, errstr);
2309 
2310  if (count_timeouts > LAKESIDE_TIMEOUT_RETRIES)
2311  {
2312  tty_error_msg(rc, errstr, MAXRBUF);
2313  LOGF_DEBUG("GetLakesideStatus: Timeout limit (%d) reached reading buffer. Error - %s", LAKESIDE_TIMEOUT_RETRIES, errstr);
2314 
2315  // force a get focuser position update
2316  rc = updatePosition();
2317 
2318  // return false as focuser is NOT known to be moving
2319  return false;
2320  } // if (count_timeouts > LAKESIDE_TIMEOUT_RETRIES)
2321  }
2322  else
2323  read_buffer = false; // break out of loop as buffer has been read
2324  } // end while
2325 
2326  // At this point, something has been returned from the buffer
2327  // Therefore, decode response
2328 
2329  LOGF_DEBUG("GetLakesideStatus: Read buffer contains : %s", resp);
2330 
2331  // decode the contents of the buffer (Temp & Pos are also updated)
2332  buffer_response = DecodeBuffer(resp);
2333 
2334  // If DONE# then focuser has finished a move, so get position
2335  if ( buffer_response == 'D' )
2336  {
2337  LOG_DEBUG("GetLakesideStatus: Found DONE# after move request");
2338 
2339  // update the current position
2340  rc = updatePosition();
2341 
2342  // IPS_IDLE turns off light, IPS_OK turns light green
2344 
2345  // return false as focuser is not known to be moving
2346  return false;
2347  }
2348 
2349  // If focuser moving > 200 steps, DecodeBuffer returns 'P'
2350  // & updates position
2351  if ( buffer_response == 'P' )
2352  {
2353  // get step position for update message
2354  rc = sscanf(resp, "P%5d#", &pos);
2355  LOGF_INFO("Focuser Moving... position : %d", pos);
2356  // Update current position
2357  FocusAbsPosN[0].value = pos;
2358  IDSetNumber(&FocusAbsPosNP, nullptr);
2359 
2360  // return true as focuser IS moving
2361  return true;
2362  }
2363 
2364  // Possible that Temperature response still in the buffer?
2365  if ( buffer_response == 'T' )
2366  {
2367  LOGF_DEBUG("GetLakesideStatus: Temperature status response found - %s", resp);
2368  // return false as focuser is not known to be moving
2369 
2370  // IPS_IDLE turns off light, IPS_OK turns light green
2372 
2373  return false;
2374  }
2375 
2376  // Possible that Temperature in K response still in the buffer?
2377  if ( buffer_response == 'K' )
2378  {
2379  LOGF_DEBUG("GetLakesideStatus: Temperature in K status response found - %s", resp);
2380  // return false as focuser is not known to be moving
2381 
2382  // IPS_IDLE turns off light, IPS_OK turns light green
2384 
2385  return false;
2386  }
2387 
2388  // At this point, something else is returned
2389  LOGF_DEBUG("GetLakesideStatus: Unknown response from buffer read : (%s)", resp);
2391 
2392  // return false as focuser is not known to be moving
2393  return false;
2394 
2395 }
2396 
2397 //
2398 // send abort command
2399 //
2401 {
2402  int rc = -1;
2403  char errstr[MAXRBUF];
2404  char cmd[] = "CH#";
2405 
2406  if (SendCmd(cmd))
2407  {
2408  // IPS_IDLE turns off light, IPS_OK turns light green
2411  LOG_INFO("Focuser Abort Sent");
2412  return true;
2413  }
2414  else
2415  {
2416  tty_error_msg(rc, errstr, MAXRBUF);
2417  LOGF_ERROR("AbortFocuser: Write command (%s) failed - %s", cmd, errstr);
2418  return false;
2419  }
2420 }
2421 
2425 void Lakeside::hexDump(char * buf, const char * data, int size)
2426 {
2427  for (int i = 0; i < size; i++)
2428  sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
2429 
2430  if (size > 0)
2431  buf[3 * size - 1] = '\0';
2432 }
2433 
2434 
2435 // End Lakeside Focuser
Connection::Serial::port
virtual const char * port()
Definition: connectionserial.h:108
IP_RO
@ IP_RO
Definition: indiapi.h:183
LAKESIDE_VERSION_MAJOR
#define LAKESIDE_VERSION_MAJOR
Definition: lakeside.cpp:34
Lakeside::Handshake
virtual bool Handshake() override
perform handshake with device to check communication
Definition: lakeside.cpp:260
INDI::FocuserInterface::FocusAbsPosNP
INumberVectorProperty FocusAbsPosNP
Definition: indifocuserinterface.h:282
INDI::FocuserInterface::FOCUSER_CAN_REL_MOVE
@ FOCUSER_CAN_REL_MOVE
Definition: indifocuserinterface.h:75
cmd
__u8 cmd[4]
Definition: pwc-ioctl.h:4
INDI::FocuserInterface::FocusMaxPosNP
INumberVectorProperty FocusMaxPosNP
Definition: indifocuserinterface.h:290
Lakeside::initProperties
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: lakeside.cpp:73
IPState
IPState
Property state.
Definition: indiapi.h:158
INDI::FocuserInterface::FocusBacklashN
INumber FocusBacklashN[1]
Definition: indifocuserinterface.h:311
LOGF_ERROR
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
IPS_OK
@ IPS_OK
Definition: indiapi.h:161
Lakeside::MoveRelFocuser
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
Definition: lakeside.cpp:2202
_INumberVectorProperty::s
IPState s
Definition: indiapi.h:332
min
double min(void)
ISS_OFF
@ ISS_OFF
Definition: indiapi.h:150
indicom.h
Implementations for common driver routines.
IPS_ALERT
@ IPS_ALERT
Definition: indiapi.h:163
INumber
One number descriptor.
IUFillNumber
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidriver.c:348
INDI::DefaultDevice::defineProperty
void defineProperty(INumberVectorProperty *property)
Definition: defaultdevice.cpp:997
MAIN_CONTROL_TAB
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
Definition: defaultdevice.cpp:34
INDI::FocuserInterface::FOCUSER_CAN_ABS_MOVE
@ FOCUSER_CAN_ABS_MOVE
Definition: indifocuserinterface.h:74
Lakeside
Definition: lakeside.h:30
INDI::DefaultDevice::setDefaultPollingPeriod
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
Definition: defaultdevice.cpp:1157
INDI::DefaultDevice::setVersion
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
Definition: defaultdevice.cpp:1219
INDI::BaseDevice::getDeviceName
const char * getDeviceName() const
Definition: basedevice.cpp:799
Lakeside::TimerHit
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition: lakeside.cpp:2223
INDI::Focuser::serialConnection
Connection::Serial * serialConnection
Definition: indifocuser.h:113
INDI::FocuserInterface::FOCUSER_HAS_BACKLASH
@ FOCUSER_HAS_BACKLASH
Definition: indifocuserinterface.h:80
tty_read_section
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:541
Lakeside::getDefaultName
const char * getDefaultName() override
Definition: lakeside.cpp:265
INDI::FocuserInterface::FocusReverseSP
ISwitchVectorProperty FocusReverseSP
Definition: indifocuserinterface.h:302
LOG_INFO
#define LOG_INFO(txt)
Definition: indilogger.h:74
MAXRBUF
#define MAXRBUF
Definition: indidriver.c:52
IUResetSwitch
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indicom.c:1421
tty_error_msg
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1135
Lakeside::ISNewNumber
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: lakeside.cpp:1743
Lakeside::ISNewSwitch
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: lakeside.cpp:1578
INDI::DefaultDevice::getCurrentPollingPeriod
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
Definition: defaultdevice.cpp:1139
LAKESIDE_VERSION_MINOR
#define LAKESIDE_VERSION_MINOR
Definition: lakeside.cpp:35
Lakeside::AbortFocuser
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
Definition: lakeside.cpp:2400
LOGF_DEBUG
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
INDI::DefaultDevice::SetTimer
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
Definition: defaultdevice.cpp:865
INDI::Focuser::PortFD
int PortFD
Definition: indifocuser.h:116
LAKESIDE_TIMEOUT_RETRIES
#define LAKESIDE_TIMEOUT_RETRIES
Definition: lakeside.cpp:57
LAKESIDE_LEN
#define LAKESIDE_LEN
Definition: lakeside.cpp:52
INDI::DefaultDevice::Connect
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
Definition: defaultdevice.cpp:1058
Lakeside::Lakeside
Lakeside()
Definition: lakeside.cpp:61
INDI::FocuserInterface::FOCUS_INWARD
@ FOCUS_INWARD
Definition: indifocuserinterface.h:68
Lakeside::SetFocuserBacklash
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
Definition: lakeside.cpp:1069
Lakeside::updateProperties
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: lakeside.cpp:164
IUFillSwitchVector
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidriver.c:412
IUFillNumberVector
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidriver.c:455
IPS_BUSY
@ IPS_BUSY
Definition: indiapi.h:162
ISR_1OFMANY
@ ISR_1OFMANY
Definition: indiapi.h:172
connectionserial.h
IPS_IDLE
@ IPS_IDLE
Definition: indiapi.h:160
tty_connect
int tty_connect(const char *device, int bit_rate, int word_size, int parity, int stop_bits, int *fd)
Establishes a tty connection to a terminal device.
Definition: indicom.c:916
INDI::FocuserInterface::FOCUSER_CAN_REVERSE
@ FOCUSER_CAN_REVERSE
Definition: indifocuserinterface.h:77
INDI::Focuser::updateProperties
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: indifocuser.cpp:120
_INumberVectorProperty::name
char name[MAXINDINAME]
Definition: indiapi.h:322
IUUpdateSwitch
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:171
INDI::BaseDevice::isConnected
bool isConnected() const
Definition: basedevice.cpp:518
IUFindNumber
INumber * IUFindNumber(const INumberVectorProperty *nvp, const char *name)
Find an INumber member in a number text property.
Definition: indicom.c:1351
LOG_DEBUG
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
LOGF_INFO
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
lakeside.h
INDI::FocuserInterface::SetCapability
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
Definition: indifocuserinterface.h:95
INDI::BaseDevice::INDI_ENABLED
@ INDI_ENABLED
Definition: basedevice.h:64
_ISwitchVectorProperty::s
IPState s
Definition: indiapi.h:382
INDI::FocuserInterface::FocusReverseS
ISwitch FocusReverseS[2]
Definition: indifocuserinterface.h:303
INDI::FocuserInterface::FocusMaxPosN
INumber FocusMaxPosN[1]
Definition: indifocuserinterface.h:291
INDI::FocuserInterface::FOCUSER_CAN_ABORT
@ FOCUSER_CAN_ABORT
Definition: indifocuserinterface.h:76
_INumberVectorProperty::p
IPerm p
Definition: indiapi.h:328
LAKESIDE_TIMEOUT
#define LAKESIDE_TIMEOUT
Definition: lakeside.cpp:51
IUUpdateNumber
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:225
INDI::FocuserInterface::FocusDirection
FocusDirection
Definition: indifocuserinterface.h:66
IP_RW
@ IP_RW
Definition: indiapi.h:185
INDI::BaseDevice::INDI_DISABLED
@ INDI_DISABLED
Definition: basedevice.h:65
ISState
ISState
Switch state.
Definition: indiapi.h:148
IUFindOnSwitchIndex
int IUFindOnSwitchIndex(const ISwitchVectorProperty *sp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indicom.c:1403
INDI::DefaultDevice::addDebugControl
void addDebugControl()
Add Debug control to the driver.
Definition: defaultdevice.cpp:639
INDI::Focuser::initProperties
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indifocuser.cpp:58
INDI::DefaultDevice::Disconnect
virtual bool Disconnect()
Disconnect from device.
Definition: defaultdevice.cpp:1083
tty_write_string
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition: indicom.c:454
Lakeside::ReverseFocuser
virtual bool ReverseFocuser(bool enabled) override
ReverseFocuser Reverse focuser motion direction.
Definition: lakeside.cpp:1163
TTY_OK
@ TTY_OK
Definition: indicom.h:94
INDI::FocuserInterface::FocusBacklashNP
INumberVectorProperty FocusBacklashNP
Definition: indifocuserinterface.h:310
INDI::DefaultDevice::deleteProperty
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
Definition: defaultdevice.cpp:965
INDI::Focuser::ISNewSwitch
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: indifocuser.cpp:168
INDI::FocuserInterface::FocusAbsPosN
INumber FocusAbsPosN[1]
Definition: indifocuserinterface.h:283
IDSetNumber
void void void IDSetNumber(const INumberVectorProperty *n, const char *msg,...) ATTRIBUTE_FORMAT_PRINTF(2
Tell client to update an existing number vector property.
IDSetSwitch
void void void void void IDSetSwitch(const ISwitchVectorProperty *s, const char *msg,...) ATTRIBUTE_FORMAT_PRINTF(2
Tell client to update an existing switch vector property.
IUFillSwitch
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidriver.c:320
INDI::Focuser::ISNewNumber
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: indifocuser.cpp:145
Lakeside::MoveAbsFocuser
virtual IPState MoveAbsFocuser(uint32_t ticks) override
MoveFocuser the focuser to an absolute position.
Definition: lakeside.cpp:2210
_ISwitchVectorProperty::name
char name[MAXINDINAME]
Definition: indiapi.h:370
ISS_ON
@ ISS_ON
Definition: indiapi.h:151