Instrument Neutral Distributed Interface INDI 2.2.5
pegasus_upb.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2018-2026 Jasem Mutlaq. All rights reserved.
3
4 Pegasus Ultimate Power Box 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_upb.h"
26#include "indicom.h"
28
29#include <memory>
30#include <regex>
31#include <termios.h>
32#include <cstring>
33#include <sys/ioctl.h>
34#include <chrono>
35#include <math.h>
36#include <iomanip>
37
38// We declare an auto pointer to PegasusUPB.
39static std::unique_ptr<PegasusUPB> upb(new PegasusUPB());
40
41PegasusUPB::PegasusUPB() : INDI::DefaultDevice(), INDI::FocuserInterface(this), INDI::WeatherInterface(this),
42 INDI::PowerInterface(this)
43{
44 setVersion(1, 6);
45
46 lastSensorData.reserve(21);
47 lastPowerData.reserve(4);
48 lastStepperData.reserve(4);
49 lastDewAggData.reserve(1);
50}
51
53{
55
57
64
66 WI::initProperties(ENVIRONMENT_TAB, ENVIRONMENT_TAB);
67
68
70
71 // Reboot
72 RebootSP[0].fill("REBOOT", "Reboot Device", ISS_OFF);
73 RebootSP.fill(getDeviceName(), "REBOOT_DEVICE", "Device", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1,
74 60, IPS_IDLE);
75
76 // Overall Power Consumption (remains as is, not part of INDI::Power)
77 PowerConsumptionNP[CONSUMPTION_AVG_AMPS].fill("CONSUMPTION_AVG_AMPS", "Avg. Amps", "%4.2f", 0, 999, 100, 0);
78 PowerConsumptionNP[CONSUMPTION_AMP_HOURS].fill("CONSUMPTION_AMP_HOURS", "Amp Hours", "%4.2f", 0, 999, 100, 0);
79 PowerConsumptionNP[CONSUMPTION_WATT_HOURS].fill("CONSUMPTION_WATT_HOURS", "Watt Hours", "%4.2f", 0, 999, 100, 0);
80 PowerConsumptionNP.fill(getDeviceName(), "POWER_CONSUMPTION", "Consumption",
82
86
87 // Automatic Dew Aggressiveness v2
88 AutoDewAggNP[AUTO_DEW_AGG].fill("AUTO_DEW_AGG_VALUE", "Auto Dew Agg (50-250)", "%.2f", 50, 250, 20, 0);
89 AutoDewAggNP.fill(getDeviceName(), "AUTO_DEW_AGG", "Auto Dew Agg", DEW_TAB, IP_RW, 60,
90 IPS_IDLE);
91
92 // Populate PI::USBPortLabelsTP with custom labels
93 if (PI::USBPortLabelsTP.size() >= 6)
94 {
95 PI::USBPortLabelsTP[0].setLabel("USB3 Port 1");
96 PI::USBPortLabelsTP[1].setLabel("USB3 Port 2");
97 PI::USBPortLabelsTP[2].setLabel("USB3 Port 3");
98 PI::USBPortLabelsTP[3].setLabel("USB3 Port 4");
99 PI::USBPortLabelsTP[4].setLabel("USB2 Port 5");
100 PI::USBPortLabelsTP[5].setLabel("USB2 Port 6");
101 }
102
106
107 // Settings
108 FocuserSettingsNP[SETTING_MAX_SPEED].fill("SETTING_MAX_SPEED", "Max Speed (%)", "%.f", 0, 900, 100, 400);
109 FocuserSettingsNP.fill(getDeviceName(), "FOCUSER_SETTINGS", "Settings", FOCUS_TAB,
110 IP_RW, 60, IPS_IDLE);
114 FirmwareTP[FIRMWARE_VERSION].fill("VERSION", "Version", "NA");
115 FirmwareTP[FIRMWARE_UPTIME].fill("UPTIME", "Uptime (h)", "NA");
116 FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", FIRMWARE_TAB, IP_RO, 60,
117 IPS_IDLE);
121 addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -15, 35, 15);
122 addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15);
123 addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15);
124 setCriticalParameter("WEATHER_TEMPERATURE");
125
129 serialConnection = new Connection::Serial(this);
130 serialConnection->registerHandshake([&]()
131 {
132 return Handshake();
133 });
134 registerConnection(serialConnection);
135
136 return true;
137}
138
140{
142
143 if (isConnected())
144 {
145 // Setup Parameters
146 setupParams();
147
148 // Main Control
149 defineProperty(PowerConsumptionNP);
150 defineProperty(RebootSP);
151
152 // Power
153 defineProperty(PowerOnBootSP);
154 defineProperty(OverCurrentLP);
155
156 // Dew
157 if (version == UPB_V2)
158 defineProperty(AutoDewAggNP);
159
160 // Focuser
162 defineProperty(FocuserSettingsNP);
163
164 // Weather
166
167 // Power
169
170 // Firmware
171 defineProperty(FirmwareTP);
172
173 setupComplete = true;
174 }
175 else
176 {
177 // Main Control
178 deleteProperty(PowerConsumptionNP);
179 deleteProperty(RebootSP);
180
181 // Power
182 deleteProperty(PowerOnBootSP);
183 deleteProperty(OverCurrentLP);
184
185 // Dew
186 if (version == UPB_V2)
187 deleteProperty(AutoDewAggNP);
188
189 // Focuser
191 deleteProperty(FocuserSettingsNP);
192
193 // Weather
195
196 // Power
198
199 deleteProperty(FirmwareTP);
200
201 setupComplete = false;
202 }
203
204 return true;
205}
206
208{
209 return "Pegasus UPB";
210}
211
215bool PegasusUPB::Handshake()
216{
217 char response[PEGASUS_LEN] = {0};
218 int nbytes_read = 0;
219 PortFD = serialConnection->getPortFD();
220
221 LOG_DEBUG("CMD <P#>");
222
223 if (isSimulation())
224 {
225 snprintf(response, PEGASUS_LEN, "UPB2_OK");
226 nbytes_read = 8;
227 }
228 else
229 {
230 int tty_rc = 0, nbytes_written = 0;
231 char command[PEGASUS_LEN] = {0};
232 tcflush(PortFD, TCIOFLUSH);
233 strncpy(command, "P#\n", PEGASUS_LEN);
234 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
235 {
236 char errorMessage[MAXRBUF];
237 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
238 LOGF_ERROR("Serial write error: %s", errorMessage);
239 return false;
240 }
241
242 // Try first with stopChar as the stop character
243 if ( (tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read)) != TTY_OK)
244 {
245 // Try 0xA as the stop character
246 if (tty_rc == TTY_OVERFLOW || tty_rc == TTY_TIME_OUT)
247 {
248 tcflush(PortFD, TCIOFLUSH);
249 tty_write_string(PortFD, command, &nbytes_written);
250 stopChar = 0xA;
251 tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read);
252 }
253
254 if (tty_rc != TTY_OK)
255 {
256 char errorMessage[MAXRBUF];
257 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
258 LOGF_ERROR("Serial read error: %s", errorMessage);
259 return false;
260 }
261 }
262
263 cleanupResponse(response);
264 tcflush(PortFD, TCIOFLUSH);
265 }
266
267
268 LOGF_DEBUG("RES <%s>", response);
269
270 setupComplete = false;
271
272 version = strstr(response, "UPB2_OK") ? UPB_V2 : UPB_V1;
273
274 if (version == UPB_V1)
275 {
276 // V1: No LED Toggle, No Variable Output
280 // 4 DC ports, 2 DEW ports, 0 Variable port, 1 Auto Dew port (global), 6 USB ports (but can only be all toggled on/off at once)
281 PI::initProperties(POWER_TAB, 4, 2, 0, 1, 1);
282 }
283 else // UPB_V2
284 {
285 // V2: All capabilities
289 // 4 DC ports, 3 DEW ports, 1 Variable port, 3 Auto Dew ports (per-port), 6 USB ports
290 PI::initProperties(POWER_TAB, 4, 3, 1, 3, 6);
291 }
292
293 return true;
294}
295
299bool PegasusUPB::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
300{
301 if (dev && !strcmp(dev, getDeviceName()))
302 {
303 // Reboot
304 if (RebootSP.isNameMatch(name))
305 {
306 RebootSP.setState(reboot() ? IPS_OK : IPS_ALERT);
307 RebootSP.apply();
308 LOG_INFO("Rebooting device...");
309 return true;
310 }
311
312 // Power on boot
313 if (PowerOnBootSP.isNameMatch(name))
314 {
315 PowerOnBootSP.update(states, names, n);
316 PowerOnBootSP.setState(setPowerOnBoot() ? IPS_OK : IPS_ALERT);
317 PowerOnBootSP.apply();
318 saveConfig(PowerOnBootSP);
319 return true;
320 }
321
322
323 if (strstr(name, "FOCUS"))
324 return FI::processSwitch(dev, name, states, names, n);
325
326 // Process power-related switches via PowerInterface
327 if (PI::processSwitch(dev, name, states, names, n))
328 return true;
329 }
330
331 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
332}
333
337bool PegasusUPB::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
338{
339 if (dev && !strcmp(dev, getDeviceName()))
340 {
341 // Auto Dew Aggressiveness (device-specific property, not in INDI::Power)
342 if (AutoDewAggNP.isNameMatch(name))
343 {
344 if (setAutoDewAgg(values[0]))
345 {
346 AutoDewAggNP[0].setValue(values[0]);
347 AutoDewAggNP.setState(IPS_OK);
348 }
349 else
350 {
351 AutoDewAggNP.setState(IPS_ALERT);
352 }
353
354 AutoDewAggNP.apply();
355 return true;
356 }
357
358 // Focuser Settings
359 if (FocuserSettingsNP.isNameMatch(name))
360 {
361 if (setFocuserMaxSpeed(values[0]))
362 {
363 FocuserSettingsNP[0].setValue(values[0]);
364 FocuserSettingsNP.setState(IPS_OK);
365 }
366 else
367 {
368 FocuserSettingsNP.setState(IPS_ALERT);
369 }
370
371 FocuserSettingsNP.apply();
372 return true;
373 }
374
375 if (strstr(name, "FOCUS_"))
376 return FI::processNumber(dev, name, values, names, n);
377
378 if (strstr(name, "WEATHER_"))
379 return WI::processNumber(dev, name, values, names, n);
380
381 // Process power-related numbers via PowerInterface
382 if (PI::processNumber(dev, name, values, names, n))
383 return true;
384 }
385 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
386}
387
391bool PegasusUPB::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
392{
393 if (dev && !strcmp(dev, getDeviceName()))
394 {
395 // USB Labels are now handled by INDI::PowerInterface
396
397 // Process power-related text via PowerInterface
398 if (PI::processText(dev, name, texts, names, n))
399 return true;
400 }
401
402 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
403}
404
408bool PegasusUPB::sendCommand(const char * cmd, char * res)
409{
410 int nbytes_read = 0, nbytes_written = 0, tty_rc = 0;
411 LOGF_DEBUG("CMD <%s>", cmd);
412
413 if (isSimulation())
414 {
415 if (!strcmp(cmd, "PS"))
416 {
417 strncpy(res, "PS:1111:12", PEGASUS_LEN);
418 }
419 else if (!strcmp(cmd, "PA"))
420 {
421 strncpy(res, "UPB2:12.0:0.9:10:24.8:37:9.1:1111:111111:153:153:0:0:0:0:0:70:0:0:0000000:0", PEGASUS_LEN);
422 }
423 else if (!strcmp(cmd, "PC"))
424 {
425 strncpy(res, "0.40:0.00:0.03:26969", PEGASUS_LEN);
426 }
427 else if (!strcmp(cmd, "SA"))
428 {
429 strncpy(res, "3000:0:0:10", PEGASUS_LEN);
430 }
431 else if (!strcmp(cmd, "SS"))
432 {
433 strncpy(res, "999", PEGASUS_LEN);
434 }
435 else if (!strcmp(cmd, "PD"))
436 {
437 strncpy(res, "210", PEGASUS_LEN);
438 }
439 else if (!strcmp(cmd, "PV"))
440 {
441 strncpy(res, "Sim v1.0", PEGASUS_LEN);
442 }
443 else if (res)
444 {
445 snprintf(res, PEGASUS_LEN, "%s", cmd);
446 }
447
448 return true;
449 }
450
451 for (int i = 0; i < 2; i++)
452 {
453 char command[PEGASUS_LEN] = {0};
454 tcflush(PortFD, TCIOFLUSH);
455 snprintf(command, PEGASUS_LEN, "%s\n", cmd);
456 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
457 continue;
458
459 if (!res)
460 {
461 tcflush(PortFD, TCIOFLUSH);
462 return true;
463 }
464
465 if ( (tty_rc = tty_nread_section(PortFD, res, PEGASUS_LEN, stopChar, PEGASUS_TIMEOUT, &nbytes_read)) != TTY_OK
466 || nbytes_read == 1)
467 continue;
468
469 tcflush(PortFD, TCIOFLUSH);
470
471 cleanupResponse(res);
472 LOGF_DEBUG("RES <%s>", res);
473 return true;
474 }
475
476 if (tty_rc != TTY_OK)
477 {
478 char errorMessage[MAXRBUF];
479 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
480 LOGF_ERROR("Serial error: %s", errorMessage);
481 }
482
483 return false;
484}
485
486bool PegasusUPB::SetUSBPort(size_t port, bool enabled)
487{
488 // Port numbers in interface are 0-based, but device expects 1-based
489 return setUSBPortEnabled(port, enabled);
490}
491
496{
497 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
498 snprintf(cmd, PEGASUS_LEN, "SM:%u", targetTicks);
499 if (sendCommand(cmd, res))
500 {
501 return (!strcmp(res, cmd) ? IPS_BUSY : IPS_ALERT);
502 }
503
504 return IPS_ALERT;
505}
506
511{
512 return MoveAbsFocuser(dir == FOCUS_INWARD ? FocusAbsPosNP[0].getValue() - ticks : FocusAbsPosNP[0].getValue() + ticks);
513}
514
519{
520 char res[PEGASUS_LEN] = {0};
521 if (sendCommand("SH", res))
522 {
523 return (!strcmp(res, "SH"));
524 }
525
526 return false;
527}
528
533{
534 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
535 snprintf(cmd, PEGASUS_LEN, "SR:%d", enabled ? 1 : 0);
536 if (sendCommand(cmd, res))
537 {
538 return (!strcmp(res, cmd));
539 }
540
541 return false;
542}
543
547bool PegasusUPB::SyncFocuser(uint32_t ticks)
548{
549 char cmd[PEGASUS_LEN] = {0};
550 snprintf(cmd, PEGASUS_LEN, "SC:%u", ticks);
551 return sendCommand(cmd, nullptr);
552}
553
558{
559 char cmd[PEGASUS_LEN] = {0};
560 snprintf(cmd, PEGASUS_LEN, "SB:%d", steps);
561 return sendCommand(cmd, nullptr);
562}
563
567bool PegasusUPB::setFocuserMaxSpeed(uint16_t maxSpeed)
568{
569 char cmd[PEGASUS_LEN] = {0};
570 snprintf(cmd, PEGASUS_LEN, "SS:%d", maxSpeed);
571 return sendCommand(cmd, nullptr);
572}
573
578{
579 char cmd[PEGASUS_LEN] = {0};
580 snprintf(cmd, PEGASUS_LEN, "SB:%d", enabled ? 1 : 0);
581 return sendCommand(cmd, nullptr);
582}
583
587bool PegasusUPB::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
602bool PegasusUPB::setPowerLEDEnabled(bool enabled)
603{
604 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
605 snprintf(cmd, PEGASUS_LEN, "PL:%d", enabled ? 1 : 0);
606 if (sendCommand(cmd, res))
607 {
608 return (!strcmp(res, cmd));
609 }
610
611 return false;
612}
613
617bool PegasusUPB::setAutoDewEnabled(bool enabled)
618{
619 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
620 snprintf(cmd, PEGASUS_LEN, "PD:%d", enabled ? 1 : 0);
621 if (sendCommand(cmd, res))
622 {
623 return (!strcmp(res, cmd));
624 }
625
626 return false;
627}
628
632bool PegasusUPB::setAutoDewAgg(uint8_t value)
633{
634 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expected[PEGASUS_LEN] = {0};
635 snprintf(cmd, PEGASUS_LEN, "PD:%03d", value);
636 snprintf(expected, PEGASUS_LEN, "PD:%d", value);
637 if (sendCommand(cmd, res))
638 {
639 return (!strcmp(res, expected));
640 }
641
642 return false;
643}
647bool PegasusUPB::setAdjustableOutput(uint8_t voltage)
648{
649 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
650 snprintf(cmd, PEGASUS_LEN, "P8:%d", voltage);
651 if (sendCommand(cmd, res))
652 {
653 return (!strcmp(res, cmd));
654 }
655
656 return false;
657}
658
662bool PegasusUPB::setPowerOnBoot()
663{
664 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
665 snprintf(cmd, PEGASUS_LEN, "PE:%d%d%d%d", PowerOnBootSP[POWER_PORT_1].getState() == ISS_ON ? 1 : 0,
666 PowerOnBootSP[POWER_PORT_2].getState() == ISS_ON ? 1 : 0,
667 PowerOnBootSP[POWER_PORT_3].getState() == ISS_ON ? 1 : 0,
668 PowerOnBootSP[POWER_PORT_4].getState() == ISS_ON ? 1 : 0);
669 if (sendCommand(cmd, res))
670 {
671 return (!strcmp(res, "PE:1"));
672 }
673
674 return false;
675}
676
680bool PegasusUPB::getPowerOnBoot()
681{
682 char res[PEGASUS_LEN] = {0};
683 if (sendCommand("PS", res))
684 {
685 std::vector<std::string> result = split(res, ":");
686 if (result.size() != 3)
687 {
688 LOGF_WARN("Received wrong number (%i) of power on boot data (%s). Retrying...", result.size(), res);
689 return false;
690 }
691
692 const char *status = result[1].c_str();
693 PowerOnBootSP[POWER_PORT_1].setState((status[0] == '1') ? ISS_ON : ISS_OFF);
694 PowerOnBootSP[POWER_PORT_2].setState((status[1] == '1') ? ISS_ON : ISS_OFF);
695 PowerOnBootSP[POWER_PORT_3].setState((status[2] == '1') ? ISS_ON : ISS_OFF);
696 PowerOnBootSP[POWER_PORT_4].setState((status[3] == '1') ? ISS_ON : ISS_OFF);
697
698 if (PI::VariableChannelVoltsNP.size() > 0)
699 {
700 PI::VariableChannelVoltsNP[0].setValue(std::stod(result[2]));
702 }
703
704 return true;
705 }
706
707 return false;
708}
709
713bool PegasusUPB::setDewPWM(uint8_t id, uint8_t value)
714{
715 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expected[PEGASUS_LEN] = {0};
716 snprintf(cmd, PEGASUS_LEN, "P%d:%03d", id, value);
717 snprintf(expected, PEGASUS_LEN, "P%d:%d", id, value);
718 if (sendCommand(cmd, res))
719 {
720 return (!strcmp(res, expected));
721 }
722
723 return false;
724}
725
729bool PegasusUPB::setUSBHubEnabled(bool enabled)
730{
731 char cmd[PEGASUS_LEN] = {0}, expected[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
732 snprintf(cmd, PEGASUS_LEN, "PU:%d", enabled ? 1 : 0);
733 snprintf(expected, PEGASUS_LEN, "PU:%d", enabled ? 0 : 1);
734 if (sendCommand(cmd, res))
735 {
736 return (!strcmp(res, expected));
737 }
738
739 return false;
740}
741
745bool PegasusUPB::setUSBPortEnabled(uint8_t port, bool enabled)
746{
747 char cmd[PEGASUS_LEN] = {0}, expected[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
748 snprintf(cmd, PEGASUS_LEN, "U%d:%d", port + 1, enabled ? 1 : 0);
749 snprintf(expected, PEGASUS_LEN, "U%d:%d", port + 1, enabled ? 1 : 0);
750 if (sendCommand(cmd, res))
751 {
752 return (!strcmp(res, expected));
753 }
754
755 return false;
756}
757
761bool PegasusUPB::toggleAutoDewV2(size_t port, bool enabled)
762{
763 char cmd[PEGASUS_LEN] = {0}, expected[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
764
765 uint8_t value = 0;
766
767 // Get current states from PI::AutoDewSP, but override the 'port' being changed
768 bool dewA_on = (PI::AutoDewSP.size() > 0 && PI::AutoDewSP[0].getState() == ISS_ON);
769 bool dewB_on = (PI::AutoDewSP.size() > 1 && PI::AutoDewSP[1].getState() == ISS_ON);
770 bool dewC_on = (PI::AutoDewSP.size() > 2 && PI::AutoDewSP[2].getState() == ISS_ON);
771
772 if (port == 0) dewA_on = enabled;
773 else if (port == 1) dewB_on = enabled;
774 else if (port == 2) dewC_on = enabled;
775
776 if (!dewA_on && !dewB_on && !dewC_on)
777 value = 0;
778 else if (dewA_on && dewB_on && dewC_on)
779 value = 1;
780 else if (dewA_on && dewB_on)
781 value = 5;
782 else if (dewA_on && dewC_on)
783 value = 6;
784 else if (dewB_on && dewC_on)
785 value = 7;
786 else if (dewA_on)
787 value = 2;
788 else if (dewB_on)
789 value = 3;
790 else if (dewC_on)
791 value = 4;
792
793 snprintf(cmd, PEGASUS_LEN, "PD:%d", value);
794 snprintf(expected, PEGASUS_LEN, "PD:%d", value);
795 if (sendCommand(cmd, res))
796 {
797 return (!strcmp(res, expected));
798 }
799
800 return false;
801}
802
807{
808 // Save CCD Config
813
814 if (version == UPB_V2)
815 {
816 AutoDewAggNP.save(fp);
817 }
818 FocuserSettingsNP.save(fp);
819 PowerOnBootSP.save(fp);
820 return true;
821}
822
827{
828 if (!isConnected() || setupComplete == false)
829 {
831 return;
832 }
833
834 if (getSensorData())
835 {
836 getPowerData();
837 getStepperData();
838
839 if (version == UPB_V2)
840 getDewAggData();
841 }
842
844}
845
849bool PegasusUPB::sendFirmware()
850{
851 char res[PEGASUS_LEN] = {0};
852 if (sendCommand("PV", res))
853 {
854 LOGF_INFO("Detected firmware %s", res);
855 FirmwareTP[FIRMWARE_VERSION].setText(res);
856 FirmwareTP.apply();
857 return true;
858 }
859
860 return false;
861}
862
866bool PegasusUPB::sensorUpdated(const std::vector<std::string> &result, uint8_t start, uint8_t end)
867{
868 if (lastSensorData.empty())
869 return true;
870
871 for (uint8_t index = start; index <= end; index++)
872 {
873 if (index >= lastSensorData.size() or result[index] != lastSensorData[index])
874 return true;
875 }
876
877 return false;
878}
879
883bool PegasusUPB::stepperUpdated(const std::vector<std::string> &result, uint8_t index)
884{
885 if (lastStepperData.empty())
886 return true;
887
888 return index >= lastStepperData.size() or result[index] != lastSensorData[index];
889}
890
894bool PegasusUPB::getSensorData()
895{
896 char res[PEGASUS_LEN] = {0};
897 if (sendCommand("PA", res))
898 {
899 std::vector<std::string> result = split(res, ":");
900 if ( (version == UPB_V1 && result.size() != 19) ||
901 (version == UPB_V2 && result.size() != 21))
902 {
903 LOGF_WARN("Received wrong number (%i) of detailed sensor data (%s). Retrying...", result.size(), res);
904 return false;
905 }
906
907 if (result == lastSensorData)
908 return true;
909
910 // Power Sensors
911 PowerSensorsNP[SENSOR_VOLTAGE].setValue(std::stod(result[1]));
912 PowerSensorsNP[SENSOR_CURRENT].setValue(std::stod(result[2]));
913 PowerSensorsNP[SENSOR_POWER].setValue(std::stod(result[3]));
915 //if (lastSensorData[0] != result[0] || lastSensorData[1] != result[1] || lastSensorData[2] != result[2])
916 if (sensorUpdated(result, 0, 2))
918
919 // Environment Sensors
920 setParameterValue("WEATHER_TEMPERATURE", std::stod(result[4]));
921 setParameterValue("WEATHER_HUMIDITY", std::stod(result[5]));
922 setParameterValue("WEATHER_DEWPOINT", std::stod(result[6]));
923 //if (lastSensorData[4] != result[4] || lastSensorData[5] != result[5] || lastSensorData[6] != result[6])
924 if (sensorUpdated(result, 4, 6))
925 {
930 }
931
932 // Port Status
933 const char * portStatus = result[7].c_str();
934 if (PI::PowerChannelsSP.size() >= 1)
935 PI::PowerChannelsSP[0].setState((portStatus[0] == '1') ? ISS_ON : ISS_OFF);
936 if (PI::PowerChannelsSP.size() >= 2)
937 PI::PowerChannelsSP[1].setState((portStatus[1] == '1') ? ISS_ON : ISS_OFF);
938 if (PI::PowerChannelsSP.size() >= 3)
939 PI::PowerChannelsSP[2].setState((portStatus[2] == '1') ? ISS_ON : ISS_OFF);
940 if (PI::PowerChannelsSP.size() >= 4)
941 PI::PowerChannelsSP[3].setState((portStatus[3] == '1') ? ISS_ON : ISS_OFF);
942 if (sensorUpdated(result, 7, 7))
944
945 // Hub Status
946 const char * usb_status = result[8].c_str();
947 if (version == UPB_V1)
948 {
949 // For V1, map global USB hub status to PI::USBPortSP[0]
950 if (PI::USBPortSP.size() > 0)
951 {
952 PI::USBPortSP[0].setState((usb_status[0] == '0') ? ISS_ON : ISS_OFF);
953 if (sensorUpdated(result, 8, 8))
954 {
956 }
957 }
958 }
959 else
960 {
961 // V2 has per-port USB control, update PI::USBPortSP directly
962 if (sensorUpdated(result, 8, 8))
963 {
964 for (size_t i = 0; i < PI::USBPortSP.size() && i < 6; ++i) // Assuming 6 USB ports
965 {
966 PI::USBPortSP[i].setState((usb_status[i] == '1') ? ISS_ON : ISS_OFF);
967 }
969 }
970 }
971
972 // From here, we get differences between v1 and v2 readings
973 int index = 9;
974 // Dew DEW
975 if (PI::DewChannelDutyCycleNP.size() >= 1)
976 PI::DewChannelDutyCycleNP[0].setValue(std::stod(result[index]) / 255.0 * 100.0);
977 if (PI::DewChannelDutyCycleNP.size() >= 2)
978 PI::DewChannelDutyCycleNP[1].setValue(std::stod(result[index + 1]) / 255.0 * 100.0);
979 if (version == UPB_V2 && PI::DewChannelDutyCycleNP.size() >= 3)
980 PI::DewChannelDutyCycleNP[2].setValue(std::stod(result[index + 2]) / 255.0 * 100.0);
981 if (sensorUpdated(result, index, version == UPB_V1 ? index + 1 : index + 2))
982 PI::DewChannelDutyCycleNP.apply();
983
984 // Update Dew Channel switches based on actual power status
985 // If Auto Dew is enabled, it may turn channels on/off, so we need to reflect that
986 bool dewChannelChanged = false;
987 if (PI::DewChannelsSP.size() >= 1)
988 {
989 auto newState = (std::stoi(result[index]) > 0) ? ISS_ON : ISS_OFF;
990 if (PI::DewChannelsSP[0].getState() != newState)
991 {
992 PI::DewChannelsSP[0].setState(newState);
993 dewChannelChanged = true;
994 }
995 }
996 if (PI::DewChannelsSP.size() >= 2)
997 {
998 auto newState = (std::stoi(result[index + 1]) > 0) ? ISS_ON : ISS_OFF;
999 if (PI::DewChannelsSP[1].getState() != newState)
1000 {
1001 PI::DewChannelsSP[1].setState(newState);
1002 dewChannelChanged = true;
1003 }
1004 }
1005 if (version == UPB_V2 && PI::DewChannelsSP.size() >= 3)
1006 {
1007 auto newState = (std::stoi(result[index + 2]) > 0) ? ISS_ON : ISS_OFF;
1008 if (PI::DewChannelsSP[2].getState() != newState)
1009 {
1010 PI::DewChannelsSP[2].setState(newState);
1011 dewChannelChanged = true;
1012 }
1013 }
1014 if (dewChannelChanged)
1016
1017 index = (version == UPB_V1) ? 11 : 12;
1018
1019 const double ampDivision = (version == UPB_V1) ? 400.0 : 480.0;
1020
1021 // Current draw
1022 if (PI::PowerChannelCurrentNP.size() >= 1)
1023 PI::PowerChannelCurrentNP[0].setValue(std::stod(result[index]) / ampDivision);
1024 if (PI::PowerChannelCurrentNP.size() >= 2)
1025 PI::PowerChannelCurrentNP[1].setValue(std::stod(result[index + 1]) / ampDivision);
1026 if (PI::PowerChannelCurrentNP.size() >= 3)
1027 PI::PowerChannelCurrentNP[2].setValue(std::stod(result[index + 2]) / ampDivision);
1028 if (PI::PowerChannelCurrentNP.size() >= 4)
1029 PI::PowerChannelCurrentNP[3].setValue(std::stod(result[index + 3]) / ampDivision);
1030 if (sensorUpdated(result, index, index + 3))
1032
1033 index = (version == UPB_V1) ? 15 : 16;
1034
1035 if (PI::DewChannelCurrentNP.size() >= 1)
1036 PI::DewChannelCurrentNP[0].setValue(std::stod(result[index]) / ampDivision);
1037 if (PI::DewChannelCurrentNP.size() >= 2)
1038 PI::DewChannelCurrentNP[1].setValue(std::stod(result[index + 1]) / ampDivision);
1039 if (version == UPB_V2 && PI::DewChannelCurrentNP.size() >= 3)
1040 PI::DewChannelCurrentNP[2].setValue(std::stod(result[index + 2]) / 700);
1041 if (sensorUpdated(result, index, version == UPB_V1 ? index + 1 : index + 2))
1042 PI::DewChannelCurrentNP.apply();
1043
1044 index = (version == UPB_V1) ? 17 : 19;
1045
1046 // Over Current
1047 //if (lastSensorData[index] != result[index])
1048 if (sensorUpdated(result, index, index))
1049 {
1050 const char * over_curent = result[index].c_str();
1051 OverCurrentLP[POWER_PORT_1].setState((over_curent[0] == '0') ? IPS_OK : IPS_ALERT);
1052 OverCurrentLP[POWER_PORT_2].setState((over_curent[1] == '0') ? IPS_OK : IPS_ALERT);
1053 OverCurrentLP[POWER_PORT_3].setState((over_curent[2] == '0') ? IPS_OK : IPS_ALERT);
1054 OverCurrentLP[POWER_PORT_4].setState((over_curent[3] == '0') ? IPS_OK : IPS_ALERT);
1055 if (version == UPB_V2)
1056 {
1057 OverCurrentLP[DEW_A].setState((over_curent[4] == '0') ? IPS_OK : IPS_ALERT);
1058 OverCurrentLP[DEW_B].setState((over_curent[5] == '0') ? IPS_OK : IPS_ALERT);
1059 OverCurrentLP[DEW_C].setState((over_curent[6] == '0') ? IPS_OK : IPS_ALERT);
1060 }
1061
1062 OverCurrentLP.apply();
1063 }
1064
1065 index = (version == UPB_V1) ? 18 : 20;
1066
1067 // Auto Dew
1068 if (version == UPB_V1)
1069 {
1070 //if (lastSensorData[index] != result[index])
1071 if (sensorUpdated(result, index, index))
1072 {
1073 PI::AutoDewSP[0].setState((std::stoi(result[index]) == 1) ? ISS_ON : ISS_OFF);
1075 }
1076 }
1077 else
1078 {
1079 // V2 has per-port auto dew control, update PI::AutoDewSP directly
1080 if (sensorUpdated(result, index, index))
1081 {
1082 int value = std::stoi(result[index]);
1083 // Reset all auto dew switches first
1084 for (size_t i = 0; i < PI::AutoDewSP.size(); ++i)
1085 {
1087 }
1088
1089 // Set states based on the value
1090 switch (value)
1091 {
1092 case 1: // All three on
1093 if (PI::AutoDewSP.size() >= 3)
1095 if (PI::AutoDewSP.size() >= 3)
1097 if (PI::AutoDewSP.size() >= 3)
1099 break;
1100 case 2: // A on
1101 if (PI::AutoDewSP.size() >= 1)
1103 break;
1104 case 3: // B on
1105 if (PI::AutoDewSP.size() >= 2)
1107 break;
1108 case 4: // C on
1109 if (PI::AutoDewSP.size() >= 3)
1111 break;
1112 case 5: // A & B on
1113 if (PI::AutoDewSP.size() >= 1)
1115 if (PI::AutoDewSP.size() >= 2)
1117 break;
1118 case 6: // A & C on
1119 if (PI::AutoDewSP.size() >= 1)
1121 if (PI::AutoDewSP.size() >= 3)
1123 break;
1124 case 7: // B & C on
1125 if (PI::AutoDewSP.size() >= 2)
1127 if (PI::AutoDewSP.size() >= 3)
1129 break;
1130 default:
1131 break;
1132 }
1134 }
1135 }
1136
1137 lastSensorData = result;
1138 return true;
1139 }
1140
1141 return false;
1142}
1143
1147bool PegasusUPB::getPowerData()
1148{
1149 char res[PEGASUS_LEN] = {0};
1150 if (sendCommand("PC", res))
1151 {
1152 std::vector<std::string> result = split(res, ":");
1153 // Minimum is 3 values. Uptime is optional.
1154 if (result.size() < 3)
1155 {
1156 LOGF_WARN("Received wrong number (%i) of power sensor data (%s). Retrying...", result.size(), res);
1157 return false;
1158 }
1159
1160 if (result == lastPowerData)
1161 return true;
1162
1163 PowerConsumptionNP[CONSUMPTION_AVG_AMPS].setValue(std::stod(result[0]));
1164 PowerConsumptionNP[CONSUMPTION_AMP_HOURS].setValue(std::stod(result[1]));
1165 PowerConsumptionNP[CONSUMPTION_WATT_HOURS].setValue(std::stod(result[2]));
1166 PowerConsumptionNP.setState(IPS_OK);
1167 PowerConsumptionNP.apply();
1168
1169 if (result.size() == 4)
1170 {
1171 try
1172 {
1173 std::chrono::milliseconds uptime(std::stol(result[3]));
1174 using dhours = std::chrono::duration<double, std::ratio<3600>>;
1175 std::stringstream ss;
1176 ss << std::fixed << std::setprecision(3) << dhours(uptime).count();
1177 FirmwareTP[FIRMWARE_UPTIME].setText(ss.str().c_str());
1178 }
1179 catch(...)
1180 {
1181 // Uptime not critical, so just put debug statement on failure.
1182 FirmwareTP[FIRMWARE_UPTIME].setText("NA");
1183 LOGF_DEBUG("Failed to process uptime: %s", result[3].c_str());
1184 }
1185 FirmwareTP.apply();
1186 }
1187
1188
1189 lastPowerData = result;
1190 return true;
1191 }
1192
1193 return false;
1194}
1195
1199bool PegasusUPB::getStepperData()
1200{
1201 char res[PEGASUS_LEN] = {0};
1202 if (sendCommand("SA", res))
1203 {
1204 std::vector<std::string> result = split(res, ":");
1205 if (result.size() != 4)
1206 {
1207 LOGF_WARN("Received wrong number (%i) of stepper sensor data (%s). Retrying...", result.size(), res);
1208 return false;
1209 }
1210
1211 if (result == lastStepperData)
1212 return true;
1213
1214 FocusAbsPosNP[0].setValue(std::stoi(result[0]));
1215 focusMotorRunning = (std::stoi(result[1]) == 1);
1216
1217 if (FocusAbsPosNP.getState() == IPS_BUSY && focusMotorRunning == false)
1218 {
1223 }
1224 else if (stepperUpdated(result, 0))
1226
1227 FocusReverseSP[INDI_ENABLED].setState((std::stoi(result[2]) == 1) ? ISS_ON : ISS_OFF);
1228 FocusReverseSP[INDI_DISABLED].setState((std::stoi(result[2]) == 1) ? ISS_OFF : ISS_ON);
1229
1230 if (stepperUpdated(result, 1))
1232
1233 uint16_t backlash = std::stoi(result[3]);
1234 if (backlash == 0)
1235 {
1236 FocusBacklashNP[0].setValue(backlash);
1239 if (stepperUpdated(result, 3))
1240 {
1242 FocuserSettingsNP.apply();
1243 }
1244 }
1245 else
1246 {
1249 FocusBacklashNP[0].setValue(backlash);
1250 if (stepperUpdated(result, 3))
1251 {
1253 FocuserSettingsNP.apply();
1254 }
1255 }
1256
1257 lastStepperData = result;
1258 return true;
1259 }
1260
1261 return false;
1262}
1263bool PegasusUPB::getDewAggData()
1264{
1265 char res[PEGASUS_LEN] = {0};
1266 if (sendCommand("DA", res))
1267 {
1268 std::vector<std::string> result = split(res, ":");
1269 if (result.size() != 2)
1270 {
1271 LOGF_WARN("Received wrong number (%i) of dew aggresiveness data (%s). Retrying...", result.size(), res);
1272 return false;
1273 }
1274
1275 if (result == lastDewAggData)
1276 return true;
1277
1278 AutoDewAggNP[0].setValue(std::stod(result[1]));
1279 AutoDewAggNP.setState(IPS_OK);
1280 AutoDewAggNP.apply();
1281
1282 lastDewAggData = result;
1283 return true;
1284 }
1285 return false;
1286}
1290bool PegasusUPB::reboot()
1291{
1292 return sendCommand("PF", nullptr);
1293}
1294
1298std::vector<std::string> PegasusUPB::split(const std::string &input, const std::string &regex)
1299{
1300 // passing -1 as the submatch index parameter performs splitting
1301 std::regex re(regex);
1302 std::sregex_token_iterator
1303 first{input.begin(), input.end(), re, -1},
1304 last;
1305 return {first, last};
1306}
1307
1311bool PegasusUPB::setupParams()
1312{
1313 if (version == UPB_V2)
1314 getPowerOnBoot();
1315
1316 sendFirmware();
1317
1318 // Get Max Focuser Speed
1319 char res[PEGASUS_LEN] = {0};
1320 if (sendCommand("SS", res))
1321 {
1322 try
1323 {
1324 uint32_t value = std::stol(res);
1325 if (value == UINT16_MAX)
1326 {
1327 LOGF_WARN("Invalid maximum speed detected: %u. Please set maximum speed appropriate for your motor focus type (0-900)",
1328 value);
1329 FocuserSettingsNP.setState(IPS_ALERT);
1330 }
1331 else
1332 {
1333 FocuserSettingsNP[SETTING_MAX_SPEED].setValue(value);
1334 FocuserSettingsNP.setState(IPS_OK);
1335 }
1336 }
1337 catch(...)
1338 {
1339 LOGF_WARN("Failed to process focuser max speed: %s", res);
1340 FocuserSettingsNP.setState(IPS_ALERT);
1341 }
1342 }
1343
1344 return false;
1345}
1346
1350void PegasusUPB::cleanupResponse(char *response)
1351{
1352 std::string s(response);
1353 s.erase(std::remove_if(s.begin(), s.end(),
1354 [](unsigned char x)
1355 {
1356 return std::isspace(x);
1357 }), s.end());
1358 snprintf(response, PEGASUS_LEN, "%s", s.c_str());
1359}
1360
1364bool PegasusUPB::SetPowerPort(size_t port, bool enabled)
1365{
1366 // Port numbers in interface are 0-based, but device expects 1-based
1367 return setPowerEnabled(port + 1, enabled);
1368}
1369
1370bool PegasusUPB::SetDewPort(size_t port, bool enabled, double dutyCycle)
1371{
1372 // DEW ports are 5, 6, 7 for dew heaters A, B, C (device uses 1-based indexing)
1373 // Convert duty cycle percentage (0-100) to 0-255 range
1374 auto pwmValue = static_cast<uint8_t>(dutyCycle / 100.0 * 255.0);
1375 return setDewPWM(port + 5, enabled ? pwmValue : 0);
1376}
1377
1378bool PegasusUPB::SetVariablePort(size_t port, bool enabled, double voltage)
1379{
1380 INDI_UNUSED(port); // UPB only has one adjustable output
1381 if (!enabled)
1382 {
1383 return setAdjustableOutput(0);
1384 }
1385 return setAdjustableOutput(static_cast<uint8_t>(voltage));
1386}
1387
1389{
1390 return setPowerLEDEnabled(enabled);
1391}
1392
1393bool PegasusUPB::SetAutoDewEnabled(size_t port, bool enabled)
1394{
1395 if (version == UPB_V1)
1396 {
1397 // V1 only has global auto dew control
1398 INDI_UNUSED(port);
1399 return setAutoDewEnabled(enabled);
1400 }
1401 else
1402 {
1403 // V2 has per-port auto dew control.
1404 // Call the re-written toggleAutoDewV2 with the specific port and enabled state.
1405 return toggleAutoDewV2(port, enabled);
1406 }
1407}
1408
1410{
1411 // PZ:1 cycles power off then on to all ports
1412 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
1413 snprintf(cmd, PEGASUS_LEN, "PZ:1");
1414 if (sendCommand(cmd, res))
1415 {
1416 return (!strcmp(cmd, res));
1417 }
1418 return false;
1419}
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
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.
bool isSimulation() const
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
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.
INDI::PropertyNumber FocusRelPosNP
INDI::PropertySwitch FocusReverseSP
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::PropertySwitch FocusBacklashSP
INDI::PropertyNumber FocusBacklashNP
INDI::PropertyNumber FocusAbsPosNP
INDI::PropertySwitch DewChannelsSP
bool saveConfigItems(FILE *fp)
saveConfigItems Save power properties in config file
INDI::PropertySwitch AutoDewSP
INDI::PropertyNumber VariableChannelVoltsNP
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 ...
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::PropertySwitch USBPortSP
INDI::PropertyNumber DewChannelDutyCycleNP
INDI::PropertyText USBPortLabelsTP
bool updateProperties()
updateProperties Define or Delete Power properties based on the connection status of the base device
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
void setLabel(const char *label)
bool isNameMatch(const char *otherName) const
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)
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 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 MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool SetVariablePort(size_t port, bool enabled, double voltage) override
SetVariablePort Set variable voltage port.
virtual bool SetPowerPort(size_t port, bool enabled) override
Power Interface Implementations.
virtual bool SetDewPort(size_t port, bool enabled, double dutyCycle) override
SetDewPort Set DEW/Dew port duty cycle.
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
virtual bool SetLEDEnabled(bool enabled) override
SetLEDEnabled Enable/disable power LEDs.
virtual bool SetUSBPort(size_t port, bool enabled) override
SetUSBPort Set USB port on/off.
virtual bool CyclePower() override
CyclePower Cycle power to all ports.
virtual bool SyncFocuser(uint32_t ticks) override
SyncFocuser Set current position to ticks without moving the focuser.
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
virtual bool SetFocuserBacklashEnabled(bool enabled) override
SetFocuserBacklashEnabled Enables or disables the focuser backlash compensation.
virtual bool ReverseFocuser(bool enabled) override
ReverseFocuser Reverse focuser motion direction.
const char * getDefaultName() override
virtual bool SetAutoDewEnabled(size_t port, bool enabled) override
SetAutoDewEnabled Enable/disable automatic dew control.
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
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
@ 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 LOGF_WARN(fmt,...)
Definition indilogger.h:81
#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