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