Instrument Neutral Distributed Interface INDI 2.2.5
alluna_tcs2.cpp
Go to the documentation of this file.
1/*
2 Alluna TCS2 Focus, Dust Cover, Climate, Rotator, and Settings
3 (Dust Cover and Rotator are not implemented)
4
5 Copyright(c) 2022 Peter Englmaier. All rights reserved.
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20*/
21
22#include "alluna_tcs2.h"
23
24#include "indicom.h"
25
26#include <cstring>
27#include <termios.h>
28#include <memory>
29#include <thread>
30#include <chrono>
31
32// create an instance of this driver
33static std::unique_ptr<AllunaTCS2> allunaTCS2(new AllunaTCS2 ());
34
36{
37 LOG_DEBUG("Init AllunaTCS2");
38 // Let's specify the driver version
39 setVersion(1, 0);
40
41 // we know only about serial (USB) connections
43 // Connection parameters should be 19200 @ 8-N-1
44 // FIXME: add some code to warn if settings are not ok
45
46 // What capabilities do we support?
49 FOCUSER_CAN_REL_MOVE ); //FIXME: maybe remove CAN_REL_MOVE
50}
51
52
54{
56 DI::initProperties(DUSTCOVER_TAB);
57
58
59 // Focuser temperature / ambient temperature, ekos uses first number of "FOCUS_TEMPERATURE" property
60 TemperatureNP[0].fill("TEMPERATURE", "Focuser Temp [C]", "%6.2f", -100, 100, 0, 0);
61 TemperatureNP[1].fill("TEMPERATURE_PRIMARY", "Primary Temp [C]", "%6.2f", -100, 100, 0, 0);
62 TemperatureNP[2].fill("TEMPERATURE_SECONDARY", "Secondary Temp [C]", "%6.2f", -100, 100, 0, 0);
63 TemperatureNP[3].fill("HUMIDITY", "Humidity [%]", "%6.2f", 0, 100, 0, 0);
64 TemperatureNP.fill(getDeviceName(), "FOCUS_TEMPERATURE", "Climate", CLIMATE_TAB, IP_RO, 0, IPS_IDLE);
65
66 // Climate control
67 ClimateControlSP[AUTO].fill("CLIMATE_AUTO", "On", ISS_OFF);
68 ClimateControlSP[MANUAL].fill("CLIMATE_MANUAL", "Off", ISS_ON);
69 ClimateControlSP.fill(getDeviceName(), "CLIMATE_CONTROL", "Climate Control", CLIMATE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
70
71 PrimaryDewHeaterSP[ON].fill("PRIMARY_HEATER_ON", "On", ISS_OFF);
72 PrimaryDewHeaterSP[OFF].fill("PRIMARY_HEATER_OFF", "Off", ISS_ON);
73 PrimaryDewHeaterSP.fill(getDeviceName(), "PRIMARY_HEATER", "Heat primary", CLIMATE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
74
75 SecondaryDewHeaterSP[ON].fill("SECONDARY_HEATER_ON", "On", ISS_OFF);
76 SecondaryDewHeaterSP[OFF].fill("SECONDARY_HEATER_OFF", "Off", ISS_ON);
77 SecondaryDewHeaterSP.fill(getDeviceName(), "SECONDARY_HEATER", "Heat secondary", CLIMATE_TAB, IP_RW, ISR_1OFMANY, 60,
78 IPS_IDLE);
79
80 FanPowerNP[0].fill("FANPOWER", "Fan power [130..255]", "%3.0f", 130, 255, 1, 255);
81 FanPowerNP.fill(getDeviceName(), "FANPOWER", "Fan Power", CLIMATE_TAB, IP_RW, 60, IPS_IDLE);
82
83 // Stepping Modes "SpeedStep" and "MicroStep"
84 SteppingModeSP[SPEED].fill("STEPPING_SPEED", "SpeedStep", ISS_ON);
85 SteppingModeSP[MICRO].fill("STEPPING_MICRO", "MicroStep", ISS_OFF);
86 SteppingModeSP.fill(getDeviceName(), "STEPPING_MODE", "Mode", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
87
88
89 // Set limits as per documentation
90 FocusAbsPosNP[0].setMin(0);
91 FocusAbsPosNP[0].setMax((steppingMode == MICRO) ? 22400 : 1400); // 22400 in microstep mode, 1400 in speedstep mode
92 FocusAbsPosNP[0].setStep(1);
93
94 FocusRelPosNP[0].setMin(0);
95 FocusRelPosNP[0].setMax(1000);
96 FocusRelPosNP[0].setStep(1);
97
98 // Maximum Position
99 FocusMaxPosNP[0].setValue(FocusAbsPosNP[0].getMax());
101
103 //addAuxControls();
107
108
109 return true;
110}
111
113{
114 return "Alluna TCS2";
115}
116
118{
120
121 if (isConnected())
122 {
123 // turn on green Connected-LED
124 if (sendCommand("Connect 1\n"))
125 {
126 LOG_DEBUG("Turned on Connected-LED¨");
127 }
128 else
129 {
130 LOG_ERROR("Cannot turn on Connected-LED");
131 }
132
133 // Read these values before defining focuser interface properties
134 // Only ask for values in sync, because TimerHit is not running, yet.
135 getPosition();
136 getStepping();
137 getDustCover();
138 getTemperature();
139 getClimateControl();
140 getFanPower();
141
142 // Focuser
143 defineProperty(SteppingModeSP);
146
147 // Climate
148 defineProperty(TemperatureNP);
149 defineProperty(ClimateControlSP);
150 defineProperty(PrimaryDewHeaterSP);
151 defineProperty(SecondaryDewHeaterSP);
152 defineProperty(FanPowerNP);
153
154 // Cover
156
157 LOG_INFO("AllunaTCS2 is ready.");
158 }
159 else
160 {
161 deleteProperty(SteppingModeSP);
164
165 deleteProperty(TemperatureNP);
166 deleteProperty(ClimateControlSP);
167 deleteProperty(PrimaryDewHeaterSP);
168 deleteProperty(SecondaryDewHeaterSP);
169 deleteProperty(FanPowerNP);
170
172 }
173
174 return true;
175}
176
178{
179 char cmd[DRIVER_LEN] = "HandShake\n", res[DRIVER_LEN] = {0};
180
181 tcs.unlock();
182 bool rc = sendCommand(cmd, res, 0, 2);
183
184 if (rc == false)
185 return false;
186
187 return res[0] == '\r' && res[1] == '\n';
188}
189
190bool AllunaTCS2::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
191{
192
193 if (tcs.try_lock() )
194 {
195 bool result;
196 result = sendCommandNoLock(cmd, res, cmd_len, res_len);
197 tcs.unlock();
198 return result;
199 }
200 else
201 {
202 LOG_INFO("sendCommand: lock failed, abort");
203 return false;
204 }
205
206}
207
208bool AllunaTCS2::sendCommandNoLock(const char * cmd, char * res, int cmd_len, int res_len)
209{
210 int nbytes_written = 0, nbytes_read = 0, rc = -1;
211
212 LOG_DEBUG("sendCommand: Send Command");
213 tcflush(PortFD, TCIOFLUSH);
214
215 if (cmd_len > 0)
216 {
217 char hex_cmd[DRIVER_LEN * 3] = {0};
218 hexDump(hex_cmd, cmd, cmd_len);
219 LOGF_DEBUG("Byte string '%s'", hex_cmd);
220 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
221 }
222 else
223 {
224 LOGF_DEBUG("Char string '%s'", cmd);
225 rc = tty_write_string(PortFD, cmd, &nbytes_written);
226 }
227
228 if (rc != TTY_OK)
229 {
230 char errstr[MAXRBUF] = {0};
231 tty_error_msg(rc, errstr, MAXRBUF);
232 LOGF_ERROR("Serial write error: %s.", errstr);
233 return false;
234 }
235
236 if (res == nullptr)
237 return true;
238
239 if (res_len > 0)
240 {
241 LOG_DEBUG("sendCommand: Read Answer Bytes");
242 rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
243 }
244 else
245 {
246 LOG_DEBUG("sendCommand: Read Answer String");
247 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
248 }
249
250 if (rc != TTY_OK)
251 {
252 char errstr[MAXRBUF] = {0};
253 tty_error_msg(rc, errstr, MAXRBUF);
254 LOGF_ERROR("203 Serial read error: %s.", errstr);
255 return false;
256 }
257
258 if (res_len > 0)
259 {
260 char hex_res[DRIVER_LEN * 3] = {0};
261 hexDump(hex_res, res, res_len);
262 LOGF_DEBUG("Bytes '%s'", hex_res);
263 }
264 else
265 {
266 LOGF_DEBUG("String '%s'", res);
267 }
268
269 tcflush(PortFD, TCIOFLUSH);
270 LOG_DEBUG("sendCommand: Ende");
271 return true;
272}
273
274bool AllunaTCS2::sendCommandOnly(const char * cmd, int cmd_len)
275{
276 int nbytes_written = 0, rc = -1;
277
278 if (! tcs.try_lock() )
279 {
280 LOGF_INFO("sendCommandOnly: %s: lock failed, abort", cmd);
281 return false;
282 }
283 LOG_DEBUG("sendCommandOnly: Anfang");
284 tcflush(PortFD, TCIOFLUSH);
285
286 if (cmd_len > 0)
287 {
288 char hex_cmd[DRIVER_LEN * 3] = {0};
289 hexDump(hex_cmd, cmd, cmd_len);
290 LOGF_DEBUG("Bytes '%s'", hex_cmd);
291 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
292 }
293 else
294 {
295 LOGF_DEBUG("String '%s'", cmd);
296 rc = tty_write_string(PortFD, cmd, &nbytes_written);
297 }
298
299 if (rc != TTY_OK)
300 {
301 char errstr[MAXRBUF] = {0};
302 tty_error_msg(rc, errstr, MAXRBUF);
303 LOGF_ERROR("Serial write error: %s.", errstr);
304 tcs.unlock();
305 return false;
306 }
307
308 LOG_DEBUG("sendCommandOnly: Ende");
309 return true;
310}
311
312bool AllunaTCS2::receiveNext(char * res, int res_len)
313{
314 int nbytes_read = 0, rc = -1;
315 LOG_DEBUG("receiveNext: Anfang");
316
317 if (res_len > 0)
318 rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
319 else
320 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
321
322 if (rc != TTY_OK)
323 {
324 char errstr[MAXRBUF] = {0};
325 tty_error_msg(rc, errstr, MAXRBUF);
326 LOGF_ERROR("285 Serial read error: %s.", errstr);
327 tcs.unlock();
328 return false;
329 }
330
331 if (res_len > 0)
332 {
333 char hex_res[DRIVER_LEN * 3] = {0};
334 hexDump(hex_res, res, res_len);
335 LOGF_DEBUG("Bytes '%s'", hex_res);
336 }
337 else
338 {
339 LOGF_DEBUG("String '%s'", res);
340 }
341 LOG_DEBUG("receiveNext: Ende");
342
343 return true;
344}
345
346void AllunaTCS2::receiveDone()
347{
348 LOG_DEBUG("receiveDone");
349 tcflush(PortFD, TCIOFLUSH);
350 tcs.unlock();
351}
352
353void AllunaTCS2::hexDump(char * buf, const char * data, int size)
354{
355 for (int i = 0; i < size; i++)
356 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
357
358 if (size > 0)
359 buf[3 * size - 1] = '\0';
360}
361
362// client wants to change switch value (i.e. click on switch in GUI)
363bool AllunaTCS2::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
364{
365 if (DI::processSwitch(dev, name, states, names, n))
366 return true;
367
368 if (dev != nullptr && !strcmp(dev, getDeviceName()) )
369 {
370 if (!strcmp(name, "CONNECTION") && !strcmp(names[0], "DISCONNECT") && states[0] == ISS_ON)
371 {
372 // turn off green Connected-LED
373 if (sendCommand("Connect 0\n"))
374 LOG_DEBUG("Turned off Connected-LED");
375 else
376 LOG_ERROR("Cannot turn off Connected-LED");
377 }
378 // Stepping Mode?
379 if (SteppingModeSP.isNameMatch(name))
380 {
381 SteppingModeSP.update(states, names, n);
382 SteppingModeSP.setState(IPS_OK);
383 SteppingModeSP.apply();
384
385 // write new stepping mode to tcs2
386 setStepping((SteppingModeSP[SPEED].s == ISS_ON) ? SPEED : MICRO);
387 // update maximum stepping position
388 FocusAbsPosNP[0].setMax((steppingMode == MICRO) ? 22400 : 1400); // 22400 in microstep mode, 1400 in speedstep mode
389 // update max position value
390 FocusMaxPosNP[0].setValue(FocusAbsPosNP[0].getMax());
391 // update maximum stepping postion for presets
392 SetFocuserMaxPosition( FocusAbsPosNP[0].getMax() ); // 22400 in microstep mode, 1400 in speedstep mode
393 // Update clients
394 FocusAbsPosNP.apply(); // not sure if this is necessary, because not shown in driver panel
396 LOGF_INFO("Setting new max position to %d", (steppingMode == MICRO) ? 22400 : 1400 );
397
400 // read focuser position (depends on stepping mode)
401 getPosition();
402 LOGF_INFO("Processed %s", name);
403 return true;
404 }
405
406 // Climate Control Switch?
407 if (ClimateControlSP.isNameMatch(name))
408 {
409 // Find out which state is requested by the client
410 const char *actionName = IUFindOnSwitchName(states, names, n);
411 // Do nothing, if state is already what it should be
412 int currentClimateControlIndex = ClimateControlSP.findOnSwitchIndex();
413 if (ClimateControlSP[currentClimateControlIndex].isNameMatch(actionName))
414 {
415 DEBUGF(INDI::Logger::DBG_SESSION, "Climate Control is already %s", ClimateControlSP[currentClimateControlIndex].label);
416 ClimateControlSP.setState(IPS_IDLE);
417 ClimateControlSP.apply();
418 return true;
419 }
420
421 // Otherwise, let us update the switch state
422 ClimateControlSP.update(states, names, n);
423 currentClimateControlIndex = ClimateControlSP.findOnSwitchIndex();
424 if ( setClimateControl((currentClimateControlIndex == AUTO) ? MANUAL : AUTO) )
425 {
426 DEBUGF(INDI::Logger::DBG_SESSION, "ClimateControl is now %s", ClimateControlSP[currentClimateControlIndex].label);
427 ClimateControlSP.setState(IPS_OK);
428 ClimateControlSP.apply();
429 return true;
430 }
431 else
432 {
433 DEBUG(INDI::Logger::DBG_SESSION, "Cannot get lock, try again");
434 ClimateControlSP.setState(IPS_ALERT);
435 ClimateControlSP.apply();
436 }
437 }
438
439 // PrimaryDewHeater Switch?
440 if (PrimaryDewHeaterSP.isNameMatch(name))
441 {
442 // Find out which state is requested by the client
443 const char *actionName = IUFindOnSwitchName(states, names, n);
444 // Do nothing, if state is already what it should be
445 int currentPrimaryDewHeaterIndex = PrimaryDewHeaterSP.findOnSwitchIndex();
446 if (PrimaryDewHeaterSP[currentPrimaryDewHeaterIndex].isNameMatch(actionName))
447 {
448 DEBUGF(INDI::Logger::DBG_SESSION, "PrimaryDewHeater is already %s", PrimaryDewHeaterSP[currentPrimaryDewHeaterIndex].label);
449 PrimaryDewHeaterSP.setState(IPS_IDLE);
450 PrimaryDewHeaterSP.apply();
451 return true;
452 }
453
454 // Otherwise, let us update the switch state
455 PrimaryDewHeaterSP.update(states, names, n);
456 currentPrimaryDewHeaterIndex = PrimaryDewHeaterSP.findOnSwitchIndex();
457 if ( setPrimaryDewHeater((currentPrimaryDewHeaterIndex == OFF) ? ON : OFF) )
458 {
459 DEBUGF(INDI::Logger::DBG_SESSION, "PrimaryDewHeater is now %s", PrimaryDewHeaterSP[currentPrimaryDewHeaterIndex].label);
460 PrimaryDewHeaterSP.setState(IPS_OK);
461 PrimaryDewHeaterSP.apply();
462 return true;
463 }
464 else
465 {
466 DEBUG(INDI::Logger::DBG_SESSION, "Cannot get lock, try again");
467 PrimaryDewHeaterSP.setState(IPS_ALERT);
468 PrimaryDewHeaterSP.apply();
469 }
470 }
471
472 // SecondaryDewHeater Switch?
473 if (SecondaryDewHeaterSP.isNameMatch(name))
474 {
475 // Find out which state is requested by the client
476 const char *actionName = IUFindOnSwitchName(states, names, n);
477 // Do nothing, if state is already what it should be
478 int currentSecondaryDewHeaterIndex = SecondaryDewHeaterSP.findOnSwitchIndex();
479 if (SecondaryDewHeaterSP[currentSecondaryDewHeaterIndex].isNameMatch(actionName))
480 {
481 DEBUGF(INDI::Logger::DBG_SESSION, "SecondaryDewHeater is already %s",
482 SecondaryDewHeaterSP[currentSecondaryDewHeaterIndex].label);
483 SecondaryDewHeaterSP.setState(IPS_IDLE);
484 SecondaryDewHeaterSP.apply();
485 return true;
486 }
487
488 // Otherwise, let us update the switch state
489 SecondaryDewHeaterSP.update(states, names, n);
490 currentSecondaryDewHeaterIndex = SecondaryDewHeaterSP.findOnSwitchIndex();
491 if ( setSecondaryDewHeater((currentSecondaryDewHeaterIndex == OFF) ? ON : OFF) )
492 {
493 DEBUGF(INDI::Logger::DBG_SESSION, "SecondaryDewHeater is now %s",
494 SecondaryDewHeaterSP[currentSecondaryDewHeaterIndex].label);
495 SecondaryDewHeaterSP.setState(IPS_OK);
496 SecondaryDewHeaterSP.apply();
497 return true;
498 }
499 else
500 {
501 DEBUG(INDI::Logger::DBG_SESSION, "Cannot get lock, try again");
502 SecondaryDewHeaterSP.setState(IPS_ALERT);
503 SecondaryDewHeaterSP.apply();
504 }
505 }
506
507
508
509 }
510 return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
511}
512
513// client wants to change number value
514bool AllunaTCS2::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
515{
516 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
517 {
518 // Fan Power
519 if (FanPowerNP.isNameMatch(name))
520 {
521 // Try to update settings
522 int power = values[0];
523 if (power > 255) power = 255;
524 if (power < 0) power = 0;
525 if (setFanPower(power))
526 {
527 FanPowerNP.update(values, names, n);
528 FanPowerNP.setState(IPS_OK);
529 }
530 else
531 {
532 FanPowerNP.setState(IPS_ALERT);
533 }
534 FanPowerNP.apply();
535 return true;
536 }
537
538 }
539
540 return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
541}
542
544{
545 char cmd[DRIVER_LEN];
546 snprintf(cmd, DRIVER_LEN, "FocuserGoTo %d\r\n", targetTicks);
547 bool rc = sendCommandOnly(cmd);
548 if (rc == false)
549 {
550 LOGF_ERROR("MoveAbsFocuser %d failed", targetTicks);
551 return IPS_ALERT;
552 }
553 isFocuserMoving = true;
554
555 return IPS_BUSY;
556}
557
559{
560 m_TargetDiff = ticks * ((dir == FOCUS_INWARD) ? -1 : 1);
561 return MoveAbsFocuser(FocusAbsPosNP[0].getValue() + m_TargetDiff);
562}
563
565{
566 return sendCommand("FocuserStop\n");
567}
568
570{
571 if (!isConnected())
572 return;
573
574 // try to read temperature, if it works no lock was present
575 if (getTemperature() && getFanPower())
576 {
578 return;
579 }
580 // if we could not read temperature, a lock is set and we need to check if there is input to be processed.
581
582 bool actionInProgress = isFocuserMoving || isCoverMoving;
583 // expect and process device output while present
584 char res[DRIVER_LEN] = {0};
585
586 // read a line, if available
587 while (actionInProgress && receiveNext(res))
588 {
589 int32_t pos;
590
591 if ( res[1] == '#')
592 {
593 switch (res[0])
594 {
595 case 'A': // aux1 on (primary mirror heating)
596 LOG_INFO("Primary heater switched ON");
597 break;
598 case 'B': // aux1 off (primary mirror heating)
599 LOG_INFO("Primary heater switched OFF");
600 break;
601 case 'C': // aux2 on (secondary mirror heating)
602 LOG_INFO("Secondary heater switched ON");
603 break;
604 case 'D': // aux2 off (secondary mirror heating)
605 LOG_INFO("Secondary heater switched OFF");
606 break;
607 case 'E': // climate control ON
608 LOG_INFO("Climate Control switched ON");
609 break;
610 case 'F': // climate control OFF
611 LOG_INFO("Climate Control switched OFF");
612 break;
613 case 'G': // fan slider return value
614 break;
615 case 'Q': // focuser home run start
616 break;
617 case 'U': // back focus minimum for optic "None"
618 break;
619 case 'V': // back focus maximum for optic "None"
620 break;
621 case 'W': // back focus minimum for optic "Corrector"
622 break;
623 case 'X': // back focus maximum for optic "Corrector"
624 break;
625 case 'Y': // back focus minimum for optic "Reducer"
626 break;
627 case 'Z': // back focus maximum for optic "Reducer"
628 break;
629 case 'a': // ambient temperature correction value
630 break;
631 case 'b': // primary temperature correction value
632 break;
633 case 'c': // secondary temperature correction value
634 break;
635 case 'K': // new focuser position
636 pos = 1e6;
637 sscanf(res, "K#%d", &pos);
638 //LOGF_INFO("TimerHit: new pos (%d)",pos);
639 if (pos != 1e6)
640 {
641 FocusAbsPosNP[0].setValue(pos);
642 }
646 break;
647 case 'I': // starting to focus
648 LOG_DEBUG("TimerHit: starting to focus");
649 break;
650 case 'J': // end of focusing
651 LOG_DEBUG("TimetHit: end of focusing");
652 isFocuserMoving = false;
655 receiveDone();
656 break;
657 case 'O': // cover started moving
660 break;
661 case 'H': // cover stopped moving
662 isCoverMoving = false;
663 receiveDone();
666 getDustCover();
667 break;
668 default: // unexpected output
669 LOGF_DEBUG("TimerHit: unexpected response (%s)", res);
670 }
671 }
672 else
673 {
674 LOGF_DEBUG("TimerHit: unexpected response (%s)", res);
675 }
676 actionInProgress = isFocuserMoving || isCoverMoving;
677 }
678
679 // What is the last read position?
680 double currentPosition = FocusAbsPosNP[0].getValue();
681
682 // Check if we have a pending motion
683 // if isMoving() is false, then we stopped, so we need to set the Focus Absolute
684 // and relative properties to OK
686 {
691 }
692 // If there was a different between last and current positions, let's update all clients
693 else if (currentPosition != FocusAbsPosNP[0].getValue())
694 {
696 }
697
699}
700
701bool AllunaTCS2::isMoving()
702{
703 return isFocuserMoving;
704}
705
706bool AllunaTCS2::getTemperature()
707{
708 // timestamp, when we updated temperatur
709 static std::chrono::system_clock::time_point last_temp_update = std::chrono::system_clock::now();
710 static bool first_run = true;
711
712 // the command GetTemperatures will respond with 4 lines:
713 // R#{ambient_temperature}<CR><LF>
714 // S#{primary_mirror_tempertature}<CR><LF>
715 // T#{secondary_mirror_temperature}<CR><LF>
716 // d#{ambient-humidity}<CR><LF>
717
718 std::chrono::duration<double> seconds = std::chrono::system_clock::now() - last_temp_update;
719 if ( !first_run && seconds.count() < 10 ) // update every 10 seconds
720 {
721 if (tcs.try_lock())
722 {
723 tcs.unlock(); // we need to get lock, to make TimerHit behave the same when we block reading temperature
724 return true; // return true, if we could get the lock
725 }
726 else
727 {
728 return false; // return false, if we could not get the lock
729 }
730 }
731 else if ( sendCommandOnly("GetTemperatures\n") )
732 {
733 TemperatureNP.setState(IPS_BUSY);
734 isGetTemperature = true;
735
736 // expect and process device output while present
737 char res[DRIVER_LEN] = {0};
738 float value;
739
740 // read a line, if available
741 while (isGetTemperature && receiveNext(res))
742 {
743 switch (res[0])
744 {
745 case 'R': // ambient temperature value
746 sscanf(res, "R#%f", &value);
747 TemperatureNP[0].value = value;
748 break;
749 case 'S': // primary mirror temperature value
750 sscanf(res, "S#%f", &value);
751 TemperatureNP[1].value = value;
752 break;
753 case 'T': // secondary mirror temperature value
754 sscanf(res, "T#%f", &value);
755 TemperatureNP[2].value = value;
756 break;
757 case 'd': // ambient humidity value
758 sscanf(res, "d#%f", &value);
759 TemperatureNP[3].value = value;
760 receiveDone();
761 isGetTemperature = false;
762 TemperatureNP.setState(IPS_OK);
763 TemperatureNP.apply(); // update clients
764 break;
765 default: // unexpected output
766 LOGF_ERROR("GetTemperatures: unexpected response (%s)", res);
767 }
768 }
769 first_run = false;
770 last_temp_update = std::chrono::system_clock::now();
771 return true;
772 }
773 return false;
774}
775
776bool AllunaTCS2::getPosition()
777{
778 char res[DRIVER_LEN] = {0};
779
780 if (sendCommand("GetFocuserPosition\n", res, 0) == false)
781 return false;
782
783 int32_t pos = 1e6;
784 sscanf(res, "%d", &pos);
785
786 if (pos == 1e6)
787 return false;
788
789 FocusAbsPosNP[0].setValue(pos);
790
792 FocusAbsPosNP.apply(); // display in user interface
793
794 return true;
795}
796
797bool AllunaTCS2::getDustCover()
798{
799 char res[DRIVER_LEN] = {0};
800
801 if (sendCommand("GetDustCover\n", res, 0) == false)
802 return false;
803
804 int32_t value = -1;
805 sscanf(res, "%d", &value);
806
807 if (value == -1)
808 return false;
809
810 LOGF_DEBUG("Cover status read to be %s (%d)", (value == 1) ? "open" : "closed", value);
811 ParkCapSP[CAP_UNPARK].setState((value == 1) ? ISS_ON : ISS_OFF);
812 ParkCapSP[CAP_PARK ].setState((value != 1) ? ISS_ON : ISS_OFF);
815
816 return true;
817}
818
820{
821 if (ParkCapSP[CAP_PARK].getState() == ISS_ON)
822 {
823 LOG_DEBUG("Toggle");
824 if (setDustCover()) // toggle state of dust cover
825 {
826 isCoverMoving = true;
827 return IPS_BUSY;
828 }
829 else
830 return IPS_ALERT;
831 }
832 getDustCover();
833
834 // Cover already parked, nothing to do
835 return IPS_OK;
836}
837
839{
840 LOGF_DEBUG("UnParkCap called, state is %d %d", ParkCapSP[CAP_PARK].getState(), ParkCapSP[CAP_UNPARK].getState());
841 if (ParkCapSP[CAP_UNPARK].getState() == ISS_ON)
842 {
843 LOG_DEBUG("Toggle");
844 if (setDustCover()) // toggle state of dust cover
845 {
846 isCoverMoving = true;
847 return IPS_BUSY;
848 }
849 else
850 return IPS_ALERT;
851 }
852 getDustCover();
853
854 // Cover already unparked, nothing to do
855 return IPS_OK;
856}
857
858
859bool AllunaTCS2::getStepping()
860{
861 char res[DRIVER_LEN] = {0};
862
863 if (sendCommand("GetFocuserMode\n", res) == false)
864 return false;
865
866 int32_t mode = 1e6;
867 sscanf(res, "%d", &mode);
868
869 if (mode == 1e6)
870 return false;
871
872 // mode=1: Microstep, mode=0: Speedstep
873 steppingMode = (mode == 0) ? SPEED : MICRO;
874 SteppingModeSP[SPEED].setState((mode == 0) ? ISS_ON : ISS_OFF);
875 SteppingModeSP[MICRO].setState((mode == 0) ? ISS_OFF : ISS_ON);
876 SteppingModeSP.setState(IPS_OK);
877
878 // Set limits as per documentation
879 FocusAbsPosNP[0].setMax((steppingMode == MICRO) ? 22400 : 1400); // 22400 in microstep mode, 1400 in speedstep mode
880 LOGF_DEBUG("readStepping: set max position to %d", (int)FocusAbsPosNP[0].getMax());
881 return true;
882}
883
884bool AllunaTCS2::setStepping(SteppingMode mode)
885{
886 int value;
887 char cmd[DRIVER_LEN] = {0};
888 steppingMode = mode;
889 value = (mode == SPEED) ? 0 : 1;
890 LOGF_DEBUG("Setting stepping mode to: %s", (mode == SPEED) ? "SPEED" : "micro");
891 LOGF_DEBUG("Setting stepping mode to: %d", value);
892 snprintf(cmd, DRIVER_LEN, "SetFocuserMode %d\n", value);
893 return sendCommand(cmd);
894}
895
896bool AllunaTCS2::setDustCover()
897{
898 char cmd[DRIVER_LEN] = {0};
899 snprintf(cmd, DRIVER_LEN, "SetDustCover\n"); // opens/closes dust cover (state toggle)
900 return sendCommandOnly(cmd); // response is processed in TimerHit
901}
902
903bool AllunaTCS2::getClimateControl()
904{
905 char res[DRIVER_LEN] = {0};
906
907 if (sendCommand("GetClimateControl\n", res, 0) == false)
908 return false;
909
910 int32_t value = -1;
911 sscanf(res, "%d", &value);
912
913 if (value == -1)
914 return false;
915
916 DEBUGF(INDI::Logger::DBG_SESSION, "Climate Control status read to be %s (%d)", (value == 1) ? "automatic" : "manual",
917 value);
918 ClimateControlSP[AUTO ].setState((value == 1) ? ISS_ON : ISS_OFF);
919 ClimateControlSP[MANUAL].setState((value != 1) ? ISS_ON : ISS_OFF);
920 ClimateControlSP.setState(IPS_OK);
921
922 return true;
923}
924
925bool AllunaTCS2::setClimateControl(ClimateControlMode mode)
926{
927 char cmd[DRIVER_LEN] = {0};
928 int value;
929 value = (mode == AUTO) ? 1 : 0;
930 snprintf(cmd, DRIVER_LEN, "SetClimateControl %d\n", value); // enable/disable climate control
931 return sendCommand(cmd);
932}
933
934bool AllunaTCS2::getPrimaryDewHeater()
935{
936 char res[DRIVER_LEN] = {0};
937
938 if (sendCommand("GetAux1\n", res, 0) == false)
939 return false;
940
941 int32_t value = -1;
942 sscanf(res, "%d", &value);
943
944 if (value == -1)
945 return false;
946
947 DEBUGF(INDI::Logger::DBG_SESSION, "PrimaryDewHeater status read to be %s (%d)", (value == 1) ? "ON" : "OFF", value);
948 PrimaryDewHeaterSP[ON ].setState((value == 1) ? ISS_ON : ISS_OFF);
949 PrimaryDewHeaterSP[OFF].setState((value != 1) ? ISS_ON : ISS_OFF);
950 PrimaryDewHeaterSP.setState(IPS_OK);
951
952 return true;
953}
954
955bool AllunaTCS2::setPrimaryDewHeater(DewHeaterMode mode)
956{
957 char cmd[DRIVER_LEN] = {0};
958 int value;
959 value = (mode == ON) ? 1 : 0;
960 snprintf(cmd, DRIVER_LEN, "SetAux1 %d\n", value); // enable/disable heating
961 return sendCommand(cmd);
962}
963
964bool AllunaTCS2::getSecondaryDewHeater()
965{
966 char res[DRIVER_LEN] = {0};
967
968 if (sendCommand("GetAux2\n", res, 0) == false)
969 return false;
970
971 int32_t value = -1;
972 sscanf(res, "%d", &value);
973
974 if (value == -1)
975 return false;
976
977 DEBUGF(INDI::Logger::DBG_SESSION, "SecondaryDewHeater status read to be %s (%d)", (value == 1) ? "ON" : "OFF", value);
978 SecondaryDewHeaterSP[ON ].setState((value == 1) ? ISS_ON : ISS_OFF);
979 SecondaryDewHeaterSP[OFF].setState((value != 1) ? ISS_ON : ISS_OFF);
980 SecondaryDewHeaterSP.setState(IPS_OK);
981
982 return true;
983}
984
985bool AllunaTCS2::setSecondaryDewHeater(DewHeaterMode mode)
986{
987 char cmd[DRIVER_LEN] = {0};
988 int value;
989 value = (mode == ON) ? 1 : 0;
990 snprintf(cmd, DRIVER_LEN, "SetAux2 %d\n", value); // enable/disable heating
991 return sendCommand(cmd);
992}
993
994bool AllunaTCS2::getFanPower()
995{
996 // timestamp, when we updated temperatur
997 static std::chrono::system_clock::time_point last_temp_update = std::chrono::system_clock::now();
998 static bool first_run = true;
999
1000 char res[DRIVER_LEN] = {0};
1001
1002 std::chrono::duration<double> seconds = std::chrono::system_clock::now() - last_temp_update;
1003 if ( !first_run && seconds.count() < 30 ) // update every 30 seconds
1004 {
1005 if (tcs.try_lock())
1006 {
1007 tcs.unlock(); // we need to get lock, to make TimerHit behave the same when we block reading temperature
1008 return true; // return true, if we could get the lock
1009 }
1010 else
1011 {
1012 return false; // return false, if we could not get the lock
1013 }
1014 }
1015 if (sendCommand("GetFanPower\n", res, 0) == false)
1016 return false;
1017
1018 int32_t value = -1;
1019 sscanf(res, "%d", &value);
1020
1021 if (value == -1)
1022 return false;
1023
1024 if (value != (int)FanPowerNP[0].value)
1025 {
1026 LOGF_DEBUG("FanPower read to be %d", value);
1027 FanPowerNP[0].value = (double)value;
1028 FanPowerNP.setState(IPS_OK);
1029 FanPowerNP.apply();
1030 }
1031 return true;
1032}
1033
1034bool AllunaTCS2::setFanPower(int value)
1035{
1036 char cmd[DRIVER_LEN] = {0};
1037 snprintf(cmd, DRIVER_LEN, "SetFanPower %d\n", value); // enable/disable heating
1038 return sendCommand(cmd);
1039}
1040
1041
1043{
1045
1046 // We need to reserve and save stepping mode
1047 // so that the next time the driver is loaded, it is remembered and applied.
1048 //IUSaveConfigSwitch(fp, &SteppingModeSP); -- not needed, because tcs2 stores state internally
1049
1050 return true;
1051}
1052
virtual IPState UnParkCap() override
unPark dust cap (open cover). Must be implemented by child.
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
const char * getDefaultName() override
bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool Handshake() override
Performs a device-specific handshake after a connection is established. This method should be overrid...
virtual IPState ParkCap() override
Park dust cap (close cover). Must be implemented by child.
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
bool AbortFocuser() override
AbortFocuser all focus motion.
bool saveConfigItems(FILE *fp) override
bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
Provides interface to implement remotely controlled dust cover.
bool isConnected() const
void addPollPeriodControl()
Add Polling period control to the driver.
void addConfigurationControl()
Add Configuration control to the driver.
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
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.
void addDebugControl()
Add Debug control to the driver.
INDI::PropertySwitch ParkCapSP
bool updateProperties()
updateProperties Defines or Delete properties based on default device connection status
void initProperties(const char *group, uint32_t capabilities=0)
Initialize dust cap properties. It is recommended to call this function within initProperties() of yo...
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process dust cap switch properties.
INDI::PropertyNumber FocusRelPosNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusMaxPosNP
INDI::PropertyNumber FocusAbsPosNP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
virtual bool saveConfigItems(FILE *fp) override
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
void setSupportedConnections(const uint8_t &value)
virtual bool SetFocuserMaxPosition(uint32_t ticks) override
SetFocuserMaxPosition Update focuser maximum position. It only updates the PresetNP property limits.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
void setPermission(IPerm permission)
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
const char * IUFindOnSwitchName(ISState *states, char *names[], int n)
Returns the name of the first ON switch it finds in the supplied arguments.
Definition indidevapi.c:355
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
Definition indilogger.h:56
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
#define DEBUGF(priority, msg,...)
Definition indilogger.h:57
#define getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2