Instrument Neutral Distributed Interface INDI 2.2.5
pegasus_ppba.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2019 Jasem Mutlaq. All rights reserved.
3
4 Pegasus Pocket Power Box Advance Driver.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 The full GNU General Public License is included in this distribution in the
22 file called LICENSE.
23*******************************************************************************/
24
25#include "pegasus_ppba.h"
26#include "indicom.h"
28
29#include <regex>
30#include <termios.h>
31#include <chrono>
32#include <iomanip>
33
34// We declare an auto pointer to PegasusPPBA.
35static std::unique_ptr<PegasusPPBA> ppba(new PegasusPPBA());
36
37PegasusPPBA::PegasusPPBA() : INDI::DefaultDevice(), FI(this), WI(this), PI(this)
38{
39 setVersion(1, 4);
40 lastSensorData.reserve(PA_N);
41 lastConsumptionData.reserve(PS_N);
42 lastMetricsData.reserve(PC_N);
43}
44
46{
48
50
57
59 WI::initProperties(ENVIRONMENT_TAB, ENVIRONMENT_TAB);
60
62
63 // Reboot
64 RebootSP[0].fill("REBOOT", "Reboot Device", ISS_OFF);
65 RebootSP.fill(getDeviceName(), "REBOOT_DEVICE", "Device", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1,
66 60, IPS_IDLE);
67
71 // Power Interface properties
72 // 1 DC output, 2 DEW outputs, 0 Variable output (individually implemented), 1 Auto Dew ports (Global), 0 USB ports
73 PI::initProperties(POWER_TAB, 1, 2, 0, 1, 0);
74
75 // overwrite labels to device labelling defaults
76 DewChannelsSP.setLabel("Dew Heater");
77 for (size_t i = 0; i < DewChannelsSP.size(); i++)
78 {
79 std::string name (1, static_cast<char>('A' + i));
80 DewChannelLabelsTP[i].setLabel("Dew " + name);
81 DewChannelLabelsTP[i].setText("Dew " + name);
82 }
84 {
85 PowerChannelLabelsTP[0].setLabel("Quad Output");
86 PowerChannelLabelsTP[0].setText("Enable 12V ports");
87 PowerChannelCurrentNP[0].setLabel("12 V current (A)");
88 }
89
90 // set the labels to the configured values
92 for (size_t i = 0; i < DewChannelsSP.size(); i++)
93 {
94 char dewChannelLabel[MAXINDILABEL];
95 char dewDutyCycleLabel[MAXINDILABEL];
96 char dewChannelCurrentLabel[MAXINDILABEL];
97 snprintf(dewChannelLabel, MAXINDILABEL, "%s", DewChannelLabelsTP[i].getText());
98 snprintf(dewDutyCycleLabel, MAXINDILABEL, "%s (%%)", DewChannelLabelsTP[i].getText());
99 snprintf(dewChannelCurrentLabel, MAXINDILABEL, "%s (A)", DewChannelLabelsTP[i].getText());
100 DewChannelsSP[i].setLabel(dewChannelLabel);
101 DewChannelDutyCycleNP[i].setLabel(dewDutyCycleLabel);
102 DewChannelCurrentNP[i].setLabel(dewChannelCurrentLabel);
103 }
106 {
107 char powerChannelLabel[MAXINDILABEL];
108 snprintf(powerChannelLabel, MAXINDILABEL, "%s", PowerChannelLabelsTP[0].getText());
109 PowerChannelsSP[0].setLabel(powerChannelLabel);
110 }
111
112 AutoDewSP.setLabel("Dew Control");
113 AutoDewSP[0].setLabel("Auto Dew Control");
114 PowerChannelsSP.setLabel("Quad Output");
115
116 // Power Sensors
117 PowerStatisticsNP[STATS_AVG_AMPS].fill("STATS_AVG_AMPS", "Average Current (A)", "%4.2f", 0, 999, 100, 0);
118 PowerStatisticsNP[STATS_AMP_HOURS].fill("STATS_AMP_HOURS", "Amp hours (Ah)", "%4.2f", 0, 999, 100, 0);
119 PowerStatisticsNP[STATS_WATT_HOURS].fill("STATS_WATT_HOURS", "Watt hours (Wh)", "%4.2f", 0, 999, 100, 0);
120 PowerStatisticsNP[STATS_TOTAL_CURRENT].fill("STATS_TOTAL_CURRENT", "Total current (A)", "%4.2f", 0, 999, 100, 0);
121 PowerStatisticsNP.fill(getDeviceName(), "POWER_STATISTICS", "Power Statistics", POWER_TAB, IP_RO, 60, IPS_IDLE);
122
123 // Adjustable Voltage
124 AdjOutVoltSP[ADJOUT_OFF].fill("ADJOUT_OFF", "Off", ISS_ON);
125 AdjOutVoltSP[ADJOUT_3V].fill("ADJOUT_3V", "3V", ISS_OFF);
126 AdjOutVoltSP[ADJOUT_5V].fill("ADJOUT_5V", "5V", ISS_OFF);
127 AdjOutVoltSP[ADJOUT_8V].fill("ADJOUT_8V", "8V", ISS_OFF);
128 AdjOutVoltSP[ADJOUT_9V].fill("ADJOUT_9V", "9V", ISS_OFF);
129 AdjOutVoltSP[ADJOUT_12V].fill("ADJOUT_12V", "12V", ISS_OFF);
130 AdjOutVoltSP.fill(getDeviceName(), "ADJOUT_VOLTAGE", "Adj voltage", POWER_TAB, IP_RW,
131 ISR_1OFMANY, 60, IPS_IDLE);
132
133 // Power on Boot
134 PowerOnBootSP[POWER_PORT_1].fill("POWER_PORT_1", "Quad Out", ISS_ON);
135 PowerOnBootSP[POWER_PORT_2].fill("POWER_PORT_2", "Adj Out", ISS_ON);
136 PowerOnBootSP[POWER_PORT_3].fill("POWER_PORT_3", "Dew A", ISS_ON);
137 PowerOnBootSP[POWER_PORT_4].fill("POWER_PORT_4", "Dew B", ISS_ON);
138 PowerOnBootSP.fill(getDeviceName(), "POWER_ON_BOOT", "Power On Boot", MAIN_CONTROL_TAB,
140
141 // Automatic Dew Settings
142 AutoDewSettingsNP[AUTO_DEW_AGGRESSION].fill("AGGRESSION", "Aggresiveness (%)", "%.2f", 0, 100, 10, 0);
143 AutoDewSettingsNP.fill(getDeviceName(), "AUTO_DEW_SETTINGS", "Auto Dew Settings", DEW_TAB, IP_RW, 60, IPS_IDLE);
144
148 FirmwareTP[FIRMWARE_VERSION].fill("VERSION", "Version", "NA");
149 FirmwareTP[FIRMWARE_UPTIME].fill("UPTIME", "Uptime (h)", "NA");
150 FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", FIRMWARE_TAB, IP_RO, 60,
151 IPS_IDLE);
152
156 addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -15, 35, 15);
157 addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15);
158 addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15);
159 setCriticalParameter("WEATHER_TEMPERATURE");
160
164
165 // Max Speed
166 FocuserSettingsNP[SETTING_MAX_SPEED].fill("SETTING_MAX_SPEED", "Max Speed (%)", "%.f", 0, 900, 100, 400);
167 FocuserSettingsNP.fill(getDeviceName(), "FOCUSER_SETTINGS", "Settings", FOCUS_TAB,
168 IP_RW, 60, IPS_IDLE);
169
170 // Stepping
171 FocuserDriveSP[STEP_FULL].fill("STEP_FULL", "Full", ISS_OFF);
172 FocuserDriveSP[STEP_HALF].fill("STEP_HALF", "Half", ISS_ON);
173 FocuserDriveSP[STEP_FORTH].fill("STEP_FORTH", "1/4", ISS_OFF);
174 FocuserDriveSP[STEP_EIGHTH].fill("STEP_EIGHTH", "1/8", ISS_OFF);
175 FocuserDriveSP.fill(getDeviceName(), "FOCUSER_DRIVE", "Microstepping", FOCUS_TAB,
177
181 serialConnection = new Connection::Serial(this);
182 serialConnection->registerHandshake([&]()
183 {
184 return Handshake();
185 });
186 registerConnection(serialConnection);
187
188 return true;
189}
190
192{
194
195 if (isConnected())
196 {
197 m_HasExternalMotor = findExternalMotorController();
198
199 if (m_HasExternalMotor)
200 {
201 getXMCStartupData();
204 }
205
206 // Main Control
207 defineProperty(RebootSP);
208 defineProperty(PowerWarnLP); // This is a custom property, not part of INDI::PowerInterface
209 defineProperty(PowerOnBootSP); // Re-add PowerOnBootSP
210
211 defineProperty(AutoDewSettingsNP);
212 getAutoDewAggression();
213
214 // Power Interface properties
216 defineProperty(AdjOutVoltSP);
217 defineProperty(PowerStatisticsNP);
218
219 // Focuser
220 if (m_HasExternalMotor)
221 {
223 defineProperty(FocuserSettingsNP);
224 defineProperty(FocuserDriveSP);
225 }
226
228
229 // Firmware
230 defineProperty(FirmwareTP);
231 sendFirmware();
232
233 setupComplete = true;
234 }
235 else
236 {
237 // Main Control
238 deleteProperty(RebootSP);
239 deleteProperty(PowerWarnLP);
240 deleteProperty(PowerOnBootSP);
241
242 deleteProperty(AutoDewSettingsNP);
243
244 // Power Interface properties
246 deleteProperty(AdjOutVoltSP);
247 deleteProperty(PowerStatisticsNP);
248
249 if (m_HasExternalMotor)
250 {
252 deleteProperty(FocuserSettingsNP);
253 deleteProperty(FocuserDriveSP);
254 }
255
257
258 deleteProperty(FirmwareTP);
259
260 setupComplete = false;
261 }
262
263 return true;
264}
265
267{
268 return "Pegasus PPBA";
269}
270
271bool PegasusPPBA::Handshake()
272{
273 int tty_rc = 0, nbytes_written = 0, nbytes_read = 0;
274 char command[PEGASUS_LEN] = {0}, response[PEGASUS_LEN] = {0};
275
276 PortFD = serialConnection->getPortFD();
277
278 LOG_DEBUG("CMD <P#>");
279
280 tcflush(PortFD, TCIOFLUSH);
281 strncpy(command, "P#\n", PEGASUS_LEN);
282 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
283 {
284 char errorMessage[MAXRBUF];
285 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
286 LOGF_ERROR("Serial write error: %s", errorMessage);
287 return false;
288 }
289
290 // Try first with stopChar as the stop character
291 if ( (tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read)) != TTY_OK)
292 {
293 // Try 0xA as the stop character
294 if (tty_rc == TTY_OVERFLOW || tty_rc == TTY_TIME_OUT)
295 {
296 tcflush(PortFD, TCIOFLUSH);
297 tty_write_string(PortFD, command, &nbytes_written);
298 tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, 0xA, 1, &nbytes_read);
299 }
300
301 if (tty_rc != TTY_OK)
302 {
303 char errorMessage[MAXRBUF];
304 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
305 LOGF_ERROR("Serial read error: %s", errorMessage);
306 return false;
307 }
308 }
309
310 tcflush(PortFD, TCIOFLUSH);
311 response[nbytes_read - 1] = '\0';
312 LOGF_DEBUG("RES <%s>", response);
313
314 setupComplete = false;
315
316 return (!strcmp(response, "PPBA_OK") || !strcmp(response, "PPBM_OK"));
317}
318
319bool PegasusPPBA::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
320{
321 if (dev && !strcmp(dev, getDeviceName()))
322 {
323 // Process power-related switches via PowerInterface
324 if (PI::processSwitch(dev, name, states, names, n))
325 return true;
326
327 // Adjustable Voltage
328 if (AdjOutVoltSP.isNameMatch(name))
329 {
330 int previous_index = AdjOutVoltSP.findOnSwitchIndex();
331 AdjOutVoltSP.update(states, names, n);
332 int target_index = AdjOutVoltSP.findOnSwitchIndex();
333 int adjv = 0;
334 switch(target_index)
335 {
336 case ADJOUT_OFF:
337 adjv = 0;
338 break;
339 case ADJOUT_3V:
340 adjv = 3;
341 break;
342 case ADJOUT_5V:
343 adjv = 5;
344 break;
345 case ADJOUT_8V:
346 adjv = 8;
347 break;
348 case ADJOUT_9V:
349 adjv = 9;
350 break;
351 case ADJOUT_12V:
352 adjv = 12;
353 break;
354 default:
355 AdjOutVoltSP.setState(IPS_ALERT);
356 AdjOutVoltSP.apply();
357 LOGF_ERROR("Unexpected voltage level: %d", target_index);
358 return true;
359 }
360
361 AdjOutVoltSP.setState(IPS_ALERT);
362 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
363 // On the PPBA, port 2's adjustable output tracks a voltage setpoint and an enable
364 // flag *independently*:
365 // - "P2:<v>" (v = 3/5/8/9/12) sets the voltage but does NOT enable the output
366 // - "P2:1" enables the output, "P2:0" disables it; neither changes the setpoint
367 // So to switch a voltage ON we set the voltage first, then enable (setting the
368 // voltage first avoids briefly emitting the previously stored voltage). To switch
369 // OFF we just disable with "P2:0". Enabling unconditionally (not only when coming
370 // from OFF) keeps the driver in sync even if the output was disabled behind our
371 // back. Verified against PPBADV_Gen2C firmware. See indilib/indi#2471.
372 bool ok = true;
373 if (target_index == ADJOUT_OFF)
374 {
375 ok = sendCommand("P2:0", res);
376 }
377 else
378 {
379 snprintf(cmd, PEGASUS_LEN, "P2:%d", adjv);
380 ok = sendCommand(cmd, res);
381 if (ok)
382 ok = sendCommand("P2:1", res);
383 }
384
385 if (ok)
386 {
387 AdjOutVoltSP.reset();
388 AdjOutVoltSP[target_index].setState(ISS_ON);
389 AdjOutVoltSP.setState(IPS_OK);
390 saveConfig(AdjOutVoltSP);
391 }
392 else
393 {
394 AdjOutVoltSP.reset();
395 AdjOutVoltSP[previous_index].setState(ISS_ON);
396 AdjOutVoltSP.setState(IPS_ALERT);
397 }
398
399 AdjOutVoltSP.apply();
400 LOGF_INFO("New adj. voltage: %dV", adjv);
401 return true;
402 }
403
404 // Reboot
405 if (RebootSP.isNameMatch(name))
406 {
407 RebootSP.setState(reboot() ? IPS_OK : IPS_ALERT);
408 RebootSP.apply();
409 LOG_INFO("Rebooting device...");
410 return true;
411 }
412
413 // Power on boot
414 if (PowerOnBootSP.isNameMatch(name))
415 {
416 PowerOnBootSP.update(states, names, n);
417 PowerOnBootSP.setState(setPowerOnBoot() ? IPS_OK : IPS_ALERT);
418 PowerOnBootSP.apply();
419 saveConfig(PowerOnBootSP);
420 return true;
421 }
422
423 // Microstepping
424 if (FocuserDriveSP.isNameMatch(name))
425 {
426 int prevIndex = FocuserDriveSP.findOnSwitchIndex();
427 FocuserDriveSP.update(states, names, n);
428 if (setFocuserMicrosteps(FocuserDriveSP.findOnSwitchIndex() + 1))
429 {
430 FocuserDriveSP.setState(IPS_OK);
431 }
432 else
433 {
434 FocuserDriveSP.reset();
435 FocuserDriveSP[prevIndex].setState(ISS_ON);
436 FocuserDriveSP.setState(IPS_ALERT);
437 }
438
439 FocuserDriveSP.apply();
440 return true;
441 }
442
443 if (strstr(name, "FOCUS"))
444 return FI::processSwitch(dev, name, states, names, n);
445 }
446
447 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
448}
449
450bool PegasusPPBA::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
451{
452 if (dev && !strcmp(dev, getDeviceName()))
453 {
454 // Process power-related numbers via PowerInterface
455 if (PI::processNumber(dev, name, values, names, n))
456 return true;
457
458 // Auto Dew Aggressiveness
459 if (AutoDewSettingsNP.isNameMatch(name))
460 {
461 // Convert percentage (0-100) to device range (0-255)
462 uint8_t aggression = static_cast<uint8_t>(values[0] / 100.0 * 255.0);
463 if (setAutoDewAggression(aggression))
464 {
465 AutoDewSettingsNP.update(values, names, n);
466 AutoDewSettingsNP.setState(IPS_OK);
467 }
468 else
469 {
470 AutoDewSettingsNP.setState(IPS_ALERT);
471 }
472 AutoDewSettingsNP.apply();
473 return true;
474 }
475
476 // Focuser Settings
477 if (FocuserSettingsNP.isNameMatch(name))
478 {
479 if (setFocuserMaxSpeed(values[0]))
480 {
481 FocuserSettingsNP[SETTING_MAX_SPEED].setValue(values[0]);
482 FocuserSettingsNP.setState(IPS_OK);
483 }
484 else
485 {
486 FocuserSettingsNP.setState(IPS_ALERT);
487 }
488
489 FocuserSettingsNP.apply();
490 return true;
491 }
492
493 if (strstr(name, "FOCUS_"))
494 return FI::processNumber(dev, name, values, names, n);
495
496 if (strstr(name, "WEATHER_"))
497 return WI::processNumber(dev, name, values, names, n);
498 }
499 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
500}
501
502bool PegasusPPBA::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
503{
504 if (dev && !strcmp(dev, getDeviceName()))
505 {
506 // Process power-related text via PowerInterface
507 if (PI::processText(dev, name, texts, names, n))
508 return true;
509 }
510
511 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
512}
513
514bool PegasusPPBA::sendCommand(const char * cmd, char * res)
515{
516 int nbytes_read = 0, nbytes_written = 0, tty_rc = 0;
517 char command[PEGASUS_LEN] = {0};
518 LOGF_DEBUG("CMD <%s>", cmd);
519
520 for (int i = 0; i < 2; i++)
521 {
522 tcflush(PortFD, TCIOFLUSH);
523 snprintf(command, PEGASUS_LEN, "%s\n", cmd);
524 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
525 continue;
526
527 if (!res)
528 {
529 tcflush(PortFD, TCIOFLUSH);
530 return true;
531 }
532
533 if ( (tty_rc = tty_nread_section(PortFD, res, PEGASUS_LEN, stopChar, PEGASUS_TIMEOUT, &nbytes_read)) != TTY_OK
534 || nbytes_read == 1)
535 continue;
536
537 tcflush(PortFD, TCIOFLUSH);
538 res[nbytes_read - 1] = '\0';
539 LOGF_DEBUG("RES <%s>", res);
540 return true;
541 }
542
543 if (tty_rc != TTY_OK)
544 {
545 char errorMessage[MAXRBUF];
546 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
547 LOGF_ERROR("Serial error: %s", errorMessage);
548 }
549
550 return false;
551}
552
553bool PegasusPPBA::findExternalMotorController()
554{
555 char res[PEGASUS_LEN] = {0};
556 if (!sendCommand("XS", res))
557 return false;
558
559 // 200 XMC present
560 return strstr(res, "200");
561}
562
563bool PegasusPPBA::setAutoDewEnabled(bool enabled)
564{
565 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
566 snprintf(cmd, PEGASUS_LEN, "PD:%d", enabled ? 1 : 0);
567 if (sendCommand(cmd, res))
568 {
569 return (!strcmp(res, cmd));
570 }
571
572 return false;
573}
574
575bool PegasusPPBA::setAutoDewAggression(uint8_t value)
576{
577 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
578 snprintf(cmd, PEGASUS_LEN, "PD:%d", value);
579 if (sendCommand(cmd, res))
580 {
581 return (!strcmp(res, cmd));
582 }
583
584 return false;
585}
586
587bool PegasusPPBA::setPowerEnabled(uint8_t port, bool enabled)
588{
589 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
590 snprintf(cmd, PEGASUS_LEN, "P%d:%d", port, enabled ? 1 : 0);
591 if (sendCommand(cmd, res))
592 {
593 return (!strcmp(res, cmd));
594 }
595
596 return false;
597}
598
599bool PegasusPPBA::setLedIndicator(bool enabled)
600{
601 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
602 snprintf(cmd, PEGASUS_LEN, "PL:%d", enabled ? 1 : 0);
603 if (sendCommand(cmd, res))
604 {
605 return (!strcmp(res, cmd));
606 }
607
608 return false;
609}
610
611bool PegasusPPBA::setPowerOnBoot()
612{
613 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
614 snprintf(cmd, PEGASUS_LEN, "PE:%d%d%d%d", PowerOnBootSP[POWER_PORT_1].getState() == ISS_ON ? 1 : 0,
615 PowerOnBootSP[POWER_PORT_2].getState() == ISS_ON ? 1 : 0,
616 PowerOnBootSP[POWER_PORT_3].getState() == ISS_ON ? 1 : 0,
617 PowerOnBootSP[POWER_PORT_4].getState() == ISS_ON ? 1 : 0);
618 if (sendCommand(cmd, res))
619 {
620 return (!strcmp(res, "PE:1"));
621 }
622
623 return false;
624}
625
626bool PegasusPPBA::setDewPWM(uint8_t id, uint8_t value)
627{
628 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expected[PEGASUS_LEN] = {0};
629 snprintf(cmd, PEGASUS_LEN, "P%d:%03d", id, value);
630 snprintf(expected, PEGASUS_LEN, "P%d:%d", id, value);
631 if (sendCommand(cmd, res))
632 {
633 return (!strcmp(res, expected));
634 }
635
636 return false;
637}
638
640{
642
643 AdjOutVoltSP.save(fp);
644 PowerOnBootSP.save(fp);
645 AutoDewSettingsNP.save(fp);
646
647 if (m_HasExternalMotor)
648 {
650 FocuserSettingsNP.save(fp);
651 FocuserDriveSP.save(fp);
652 }
653
656
657 return true;
658}
659
661{
662 if (!isConnected() || setupComplete == false)
663 {
665 return;
666 }
667
668 getSensorData();
669 getConsumptionData();
670 getMetricsData();
671
672 if (m_HasExternalMotor)
673 queryXMC();
674
676}
677
678bool PegasusPPBA::sendFirmware()
679{
680 char res[PEGASUS_LEN] = {0};
681 if (sendCommand("PV", res))
682 {
683 LOGF_INFO("Detected firmware %s", res);
684 FirmwareTP[FIRMWARE_VERSION].setText(res);
685 FirmwareTP.apply();
686 return true;
687 }
688
689 return false;
690}
691
692bool PegasusPPBA::getSensorData()
693{
694 char res[PEGASUS_LEN] = {0};
695 if (sendCommand("PA", res))
696 {
697 std::vector<std::string> result = split(res, ":");
698 if (result.size() < PA_N)
699 {
700 LOG_WARN("Received wrong number of detailed sensor data. Retrying...");
701 return false;
702 }
703
704 if (result == lastSensorData)
705 return true;
706
707 // Power Sensors
708 PI::PowerSensorsNP[PI::SENSOR_VOLTAGE].setValue(std::stod(result[PA_VOLTAGE]));
709 PI::PowerSensorsNP[PI::SENSOR_CURRENT].setValue(std::stod(result[PA_CURRENT]) / 65.0);
710 // PPBA does not report total power directly, so we calculate it
714 if (lastSensorData.size() < PA_N ||
715 lastSensorData[PA_VOLTAGE] != result[PA_VOLTAGE] || lastSensorData[PA_CURRENT] != result[PA_CURRENT])
717
718 // Environment Sensors
719 setParameterValue("WEATHER_TEMPERATURE", std::stod(result[PA_TEMPERATURE]));
720 setParameterValue("WEATHER_HUMIDITY", std::stod(result[PA_HUMIDITY]));
721 setParameterValue("WEATHER_DEWPOINT", std::stod(result[PA_DEW_POINT]));
722 if (lastSensorData.size() < PA_N ||
723 lastSensorData[PA_TEMPERATURE] != result[PA_TEMPERATURE] ||
724 lastSensorData[PA_HUMIDITY] != result[PA_HUMIDITY] ||
725 lastSensorData[PA_DEW_POINT] != result[PA_DEW_POINT])
726 {
731 }
732
733 // Power Status (DC Output)
734 if (PI::PowerChannelsSP.size() > 0)
735 {
736 PI::PowerChannelsSP[0].setState((std::stoi(result[PA_PORT_STATUS]) == 1) ? ISS_ON : ISS_OFF);
737 if (lastSensorData.size() < PA_N || lastSensorData[PA_PORT_STATUS] != result[PA_PORT_STATUS])
739 }
740 // Adjustable Power Status
741 // Use the enable flag (PA_ADJ_STATUS) to decide on/off, not the voltage setpoint
742 // (PA_PWRADJ): the two can diverge (output disabled while a non-zero setpoint is
743 // still remembered), which otherwise makes the selector display a voltage while the
744 // output is physically off (see indilib/indi#2471).
745 AdjOutVoltSP.reset();
746 if (std::stoi(result[PA_ADJ_STATUS]) == 0)
747 AdjOutVoltSP[ADJOUT_OFF].setState(ISS_ON);
748 else
749 {
750 AdjOutVoltSP[ADJOUT_3V].setState((std::stoi(result[PA_PWRADJ]) == 3) ? ISS_ON : ISS_OFF);
751 AdjOutVoltSP[ADJOUT_5V].setState((std::stoi(result[PA_PWRADJ]) == 5) ? ISS_ON : ISS_OFF);
752 AdjOutVoltSP[ADJOUT_8V].setState((std::stoi(result[PA_PWRADJ]) == 8) ? ISS_ON : ISS_OFF);
753 AdjOutVoltSP[ADJOUT_9V].setState((std::stoi(result[PA_PWRADJ]) == 9) ? ISS_ON : ISS_OFF);
754 AdjOutVoltSP[ADJOUT_12V].setState((std::stoi(result[PA_PWRADJ]) == 12) ? ISS_ON : ISS_OFF);
755 }
756 if (lastSensorData.size() < PA_N ||
757 lastSensorData[PA_PWRADJ] != result[PA_PWRADJ] || lastSensorData[PA_ADJ_STATUS] != result[PA_ADJ_STATUS])
758 AdjOutVoltSP.apply();
759
760 // Power Warn (This is a custom property, not part of INDI::PowerInterface)
761 PowerWarnLP[0].setState((std::stoi(result[PA_PWR_WARN]) == 1) ? IPS_ALERT : IPS_OK);
762 PowerWarnLP.setState((std::stoi(result[PA_PWR_WARN]) == 1) ? IPS_ALERT : IPS_OK);
763 if (lastSensorData.size() < PA_N ||
764 lastSensorData[PA_PWR_WARN] != result[PA_PWR_WARN])
765 PowerWarnLP.apply();
766
767 // Dew PWM (Dew Outputs)
768 if (PI::DewChannelDutyCycleNP.size() > 0)
769 PI::DewChannelDutyCycleNP[0].setValue(std::stod(result[PA_DEW_1]) / 255.0 * 100.0);
770 if (PI::DewChannelDutyCycleNP.size() > 1)
771 PI::DewChannelDutyCycleNP[1].setValue(std::stod(result[PA_DEW_2]) / 255.0 * 100.0);
772 if (lastSensorData.size() < PA_N ||
773 lastSensorData[PA_DEW_1] != result[PA_DEW_1] || lastSensorData[PA_DEW_2] != result[PA_DEW_2])
775
776 // Auto Dew
777 if (PI::AutoDewSP.size() > 0)
778 {
779 bool autodew = std::stoi(result[PA_AUTO_DEW]);
780 PI::AutoDewSP[0].setState(autodew ? ISS_ON : ISS_OFF);
781 if (lastSensorData.size() < PA_N || lastSensorData[PA_AUTO_DEW] != result[PA_AUTO_DEW])
783
784 // ensure that dew heater channels are on, since the PPBA auto dew control handles both ports automatically
785 if (autodew)
786 {
787 bool changed = false;
788 for (size_t i = 0; i < DewChannelsSP.size(); i++)
789 {
790 if (DewChannelsSP[i].getState() == ISS_OFF)
791 {
793 LOGF_INFO("Auto dew on, enabling channel %s", (i == 0 ? "A" : "B"));
794 changed = true;
795 }
796 }
797 if (changed)
799 }
800 }
801
802 lastSensorData = result;
803
804 return true;
805 }
806
807 return false;
808}
809
810bool PegasusPPBA::getConsumptionData()
811{
812 char res[PEGASUS_LEN] = {0};
813 if (sendCommand("PS", res))
814 {
815 std::vector<std::string> result = split(res, ":");
816 if (result.size() < PS_N)
817 {
818 LOG_WARN("Received wrong number of detailed consumption data. Retrying...");
819 return false;
820 }
821
822 if (result == lastConsumptionData)
823 return true;
824
825 // Power Statistics
826 PowerStatisticsNP[STATS_AVG_AMPS].setValue(std::stod(result[PS_AVG_AMPS]));
827 PowerStatisticsNP[STATS_AMP_HOURS].setValue(std::stod(result[PS_AMP_HOURS]));
828 PowerStatisticsNP[STATS_WATT_HOURS].setValue(std::stod(result[PS_WATT_HOURS]));
829 PowerStatisticsNP.setState(IPS_OK);
830 if (lastConsumptionData.size() < PS_N ||
831 lastConsumptionData[PS_AVG_AMPS] != result[PS_AVG_AMPS] ||
832 lastConsumptionData[PS_AMP_HOURS] != result[PS_AMP_HOURS] ||
833 lastConsumptionData[PS_WATT_HOURS] != result[PS_WATT_HOURS])
834 PowerStatisticsNP.apply();
835
836 lastConsumptionData = result;
837
838 return true;
839 }
840
841 return false;
842}
843
844bool PegasusPPBA::getAutoDewAggression()
845{
846 char res[PEGASUS_LEN] = {0};
847 if (sendCommand("DA", res))
848 {
849
850 uint32_t value = 0;
851 sscanf(res, "%*[^:]:%d", &value);
852 AutoDewSettingsNP[AUTO_DEW_AGGRESSION].setValue(100 * value / 255);
853 }
854 else
855 AutoDewSettingsNP.setState(IPS_ALERT);
856
857 AutoDewSettingsNP.apply();
858 return AutoDewSettingsNP.getState() != IPS_ALERT;
859}
860
861bool PegasusPPBA::getMetricsData()
862{
863 char res[PEGASUS_LEN] = {0};
864 if (sendCommand("PC", res))
865 {
866 std::vector<std::string> result = split(res, ":");
867 if (result.size() < PC_N)
868 {
869 LOG_WARN("Received wrong number of detailed metrics data. Retrying...");
870 return false;
871 }
872
873 if (result == lastMetricsData)
874 return true;
875
876 // Power Sensors
877 PowerStatisticsNP[STATS_TOTAL_CURRENT].setValue(std::stod(result[PC_TOTAL_CURRENT]));
878 // Power Sensors (Per-port current monitoring)
879 if (PI::PowerChannelCurrentNP.size() > 0)
880 {
881 PI::PowerChannelCurrentNP[0].setValue(std::stod(result[PC_12V_CURRENT]));
882 if (lastMetricsData.size() < PC_N || lastMetricsData[PC_12V_CURRENT] != result[PC_12V_CURRENT])
883 {
886 }
887 }
888 // Update PI::DewChannelCurrentNP for Dew ports
889 if (PI::DewChannelCurrentNP.size() > 0)
890 PI::DewChannelCurrentNP[0].setValue(std::stod(result[PC_DEWA_CURRENT]));
891 if (PI::DewChannelCurrentNP.size() > 1)
892 PI::DewChannelCurrentNP[1].setValue(std::stod(result[PC_DEWB_CURRENT]));
893 if (lastMetricsData.size() < PC_N ||
894 lastMetricsData[PC_TOTAL_CURRENT] != result[PC_TOTAL_CURRENT] ||
895 lastMetricsData[PC_DEWA_CURRENT] != result[PC_DEWA_CURRENT] ||
896 lastMetricsData[PC_DEWB_CURRENT] != result[PC_DEWB_CURRENT])
897 {
900 PowerStatisticsNP.apply();
901 }
902
903 std::chrono::milliseconds uptime(std::stol(result[PC_UPTIME]));
904 using dhours = std::chrono::duration<double, std::ratio<3600 >>;
905 std::stringstream ss;
906 ss << std::fixed << std::setprecision(3) << dhours(uptime).count();
907 FirmwareTP[FIRMWARE_UPTIME].setText(ss.str().c_str());
908 FirmwareTP.apply();
909
910 lastMetricsData = result;
911
912 return true;
913 }
914
915 return false;
916}
917// Device Control
918bool PegasusPPBA::reboot()
919{
920 return sendCommand("PF", nullptr);
921}
922
927{
928 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
929 snprintf(cmd, PEGASUS_LEN, "XS:3#%u", targetTicks);
930 return (sendCommand(cmd, res) ? IPS_BUSY : IPS_ALERT);
931}
932
937{
938 return MoveAbsFocuser(dir == FOCUS_INWARD ? FocusAbsPosNP[0].getValue() - ticks : FocusAbsPosNP[0].getValue() + ticks);
939}
940
945{
946 return sendCommand("XS:6", nullptr);
947}
948
953{
954 char cmd[PEGASUS_LEN] = {0};
955 snprintf(cmd, PEGASUS_LEN, "XS:8#%d", enabled ? 1 : 0);
956 return sendCommand(cmd, nullptr);
957}
958
962bool PegasusPPBA::SyncFocuser(uint32_t ticks)
963{
964 char cmd[PEGASUS_LEN] = {0};
965 snprintf(cmd, PEGASUS_LEN, "XS:5#%u", ticks);
966 return sendCommand(cmd, nullptr);
967}
968
973{
974 char cmd[PEGASUS_LEN] = {0};
975 snprintf(cmd, PEGASUS_LEN, "XS:10#%d", steps);
976 return sendCommand(cmd, nullptr);
977}
978
982bool PegasusPPBA::setFocuserMaxSpeed(uint16_t maxSpeed)
983{
984 char cmd[PEGASUS_LEN] = {0};
985 snprintf(cmd, PEGASUS_LEN, "XS:7#%d", maxSpeed);
986 return sendCommand(cmd, nullptr);
987}
988
989bool PegasusPPBA::setFocuserMicrosteps(int value)
990{
991 char cmd[PEGASUS_LEN] = {0};
992 snprintf(cmd, PEGASUS_LEN, "XS:9#%d", value);
993 return sendCommand(cmd, nullptr);
994}
995
1000{
1001 char cmd[PEGASUS_LEN] = {0};
1002 snprintf(cmd, PEGASUS_LEN, "XS:8#%d", enabled ? 1 : 0);
1003 return sendCommand(cmd, nullptr);
1004}
1005
1009bool PegasusPPBA::getXMCStartupData()
1010{
1011 char res[PEGASUS_LEN] = {0};
1012
1013 // Position
1014 if (sendCommand("XS:2", res))
1015 {
1016 uint32_t position = 0;
1017 sscanf(res, "%*[^#]#%d", &position);
1018 FocusAbsPosNP[0].setValue(position);
1019 }
1020
1021 // Max speed
1022 if (sendCommand("XS:7", res))
1023 {
1024 uint32_t speed = 0;
1025 sscanf(res, "%*[^#]#%d", &speed);
1026 FocuserSettingsNP[SETTING_MAX_SPEED].setValue(speed);
1027 }
1028
1029 return true;
1030}
1031
1035void PegasusPPBA::queryXMC()
1036{
1037 char res[PEGASUS_LEN] = {0};
1038 uint32_t position = 0;
1039 uint32_t motorRunning = 0;
1040
1041 // Get Motor Status
1042 if (sendCommand("XS:1", res))
1043 sscanf(res, "%*[^#]#%d", &motorRunning);
1044 // Get Position
1045 if (sendCommand("XS:2", res))
1046 sscanf(res, "%*[^#]#%d", &position);
1047
1048 uint32_t lastPosition = FocusAbsPosNP[0].getValue();
1049 FocusAbsPosNP[0].setValue(position);
1050
1051 if (FocusAbsPosNP.getState() == IPS_BUSY && motorRunning == 0)
1052 {
1057 }
1058 else if (lastPosition != position)
1060
1061}
1065std::vector<std::string> PegasusPPBA::split(const std::string &input, const std::string &regex)
1066{
1067 // passing -1 as the submatch index parameter performs splitting
1068 std::regex re(regex);
1069 std::sregex_token_iterator
1070 first{input.begin(), input.end(), re, -1},
1071 last;
1072 return {first, last};
1073}
1074
1078bool PegasusPPBA::SetPowerPort(size_t port, bool enabled)
1079{
1080 // Port numbers in interface are 0-based, but device expects 1-based
1081 return setPowerEnabled(port + 1, enabled);
1082}
1083
1084bool PegasusPPBA::SetDewPort(size_t port, bool enabled, double dutyCycle)
1085{
1086 // DEW ports are 3, 4 for Dew A, Dew B (device uses 1-based indexing)
1087 // Convert duty cycle percentage (0-100) to 0-255 range
1088 auto pwmValue = static_cast<uint8_t>(dutyCycle / 100.0 * 255.0);
1089 return setDewPWM(port + 3, enabled ? pwmValue : 0);
1090}
1091
1093{
1094 return setLedIndicator(enabled);
1095}
1096
1097bool PegasusPPBA::SetAutoDewEnabled(size_t port, bool enabled)
1098{
1099 INDI_UNUSED(port); // PPBA only has global auto dew control
1100 return setAutoDewEnabled(enabled);
1101}
1102
1104{
1105 // PPBA does not have a specific power cycle command, reboot is the closest.
1106 // Or we can implement a software cycle by turning off and on all ports.
1107 // For now, let's just reboot.
1108 return reboot();
1109}
1110
1111bool PegasusPPBA::SetUSBPort(size_t port, bool enabled)
1112{
1113 INDI_UNUSED(port);
1114 INDI_UNUSED(enabled);
1115 // PPBA does not have USB power control
1116 return false;
1117}
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
bool isConnected() const
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
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)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
void syncDriverInfo()
syncDriverInfo sends the current driver information to the client.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
uint32_t getDriverInterface() const
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
Provides interface to implement focuser functionality.
INDI::PropertyNumber FocusRelPosNP
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
void initProperties(const char *groupName)
Initialize focuser properties. It is recommended to call this function within initProperties() of you...
bool saveConfigItems(FILE *fp)
saveConfigItems save focuser properties defined in the interface in config file
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process focus number properties.
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process focus switch properties.
INDI::PropertyNumber FocusAbsPosNP
INDI::PropertySwitch DewChannelsSP
bool saveConfigItems(FILE *fp)
saveConfigItems Save power properties in config file
INDI::PropertySwitch AutoDewSP
void SetCapability(uint32_t cap)
SetPowerCapability sets the Power capabilities. All capabilities must be initialized.
INDI::PropertyNumber PowerSensorsNP
INDI::PropertyNumber PowerChannelCurrentNP
INDI::PropertyNumber DewChannelCurrentNP
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process Power number properties.
void initProperties(const char *groupName, size_t nPowerPorts=0, size_t nDewPorts=0, size_t nVariablePorts=0, size_t nAutoDewPorts=0, size_t nUSBPorts=0)
Initialize Power properties. It is recommended to call this function within initProperties() of your ...
INDI::PropertyText PowerChannelLabelsTP
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process Power text properties.
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process Power switch properties.
INDI::PropertySwitch PowerChannelsSP
INDI::PropertyNumber DewChannelDutyCycleNP
bool updateProperties()
updateProperties Define or Delete Power properties based on the connection status of the base device
INDI::PropertyText DewChannelLabelsTP
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
void setLabel(const char *label)
bool isNameMatch(const char *otherName) const
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)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
Provides interface to implement weather reporting and safety monitoring functionality.
void addParameter(std::string name, std::string label, double numMinOk, double numMaxOk, double percWarning, bool flipWarning=false)
Add a physical weather measurable parameter to the weather driver.
bool setCriticalParameter(std::string name)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
bool syncCriticalParameters()
Synchronize and update the state of all critical parameters.
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process weather number properties.
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save parameters ranges in the config file.
INDI::PropertyNumber ParametersNP
void initProperties(const char *statusGroup, const char *paramsGroup)
Initialize focuser properties. It is recommended to call this function within initProperties() of you...
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
INDI::PropertyLight critialParametersLP
virtual bool SetPowerPort(size_t port, bool enabled) override
Power Interface Implementations.
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
const char * getDefaultName() override
virtual bool SyncFocuser(uint32_t ticks) override
SyncFocuser Set current position to ticks without moving the focuser.
virtual bool SetFocuserBacklashEnabled(bool enabled) override
SetFocuserBacklashEnabled Enables or disables the focuser backlash compensation.
virtual bool SetDewPort(size_t port, bool enabled, double dutyCycle) override
SetDewPort Set DEW/Dew port duty cycle.
virtual bool ReverseFocuser(bool enabled) override
ReverseFocuser Reverse focuser motion direction.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
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 SetUSBPort(size_t port, bool enabled) override
SetUSBPort Set USB port on/off.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
virtual bool SetLEDEnabled(bool enabled) override
SetLEDEnabled Enable/disable power LEDs.
virtual bool CyclePower() override
CyclePower Cycle power to all ports.
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
virtual bool SetAutoDewEnabled(size_t port, bool enabled) override
SetAutoDewEnabled Enable/disable automatic dew control.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * FOCUS_TAB
FOCUS_TAB Where all the properties for focuser 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
#define MAXINDILABEL
Definition indiapi.h:190
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_NOFMANY
Definition indiapi.h:173
@ ISR_ATMOST1
Definition indiapi.h:172
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
@ TTY_TIME_OUT
Definition indicom.h:58
@ TTY_OVERFLOW
Definition indicom.h:62
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
Namespace to encapsulate INDI client, drivers, and mediator classes.
uint8_t cmd[4]
Definition pwc-ioctl.h:2