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"
27 #include "connectionplugins/connectionserial.h"
28 
29 #include <regex>
30 #include <termios.h>
31 #include <chrono>
32 #include <iomanip>
33 
34 // We declare an auto pointer to PegasusPPBA.
35 static std::unique_ptr<PegasusPPBA> ppba(new PegasusPPBA());
36 
PegasusPPBA()37 PegasusPPBA::PegasusPPBA() : FI(this), WI(this)
38 {
39     setVersion(1, 2);
40     lastSensorData.reserve(PA_N);
41     lastConsumptionData.reserve(PS_N);
42     lastMetricsData.reserve(PC_N);
43 }
44 
initProperties()45 bool PegasusPPBA::initProperties()
46 {
47     INDI::DefaultDevice::initProperties();
48 
49     setDriverInterface(AUX_INTERFACE | WEATHER_INTERFACE);
50 
51     FI::SetCapability(FOCUSER_CAN_ABS_MOVE |
52                       FOCUSER_CAN_REL_MOVE |
53                       FOCUSER_CAN_REVERSE  |
54                       FOCUSER_CAN_SYNC     |
55                       FOCUSER_CAN_ABORT    |
56                       FOCUSER_HAS_BACKLASH);
57 
58     FI::initProperties(FOCUS_TAB);
59     WI::initProperties(ENVIRONMENT_TAB, ENVIRONMENT_TAB);
60 
61     addAuxControls();
62 
63     ////////////////////////////////////////////////////////////////////////////
64     /// Main Control Panel
65     ////////////////////////////////////////////////////////////////////////////
66     // Quad 12v Power
67     IUFillSwitch(&QuadOutS[INDI_ENABLED], "QUADOUT_ON", "Enable", ISS_OFF);
68     IUFillSwitch(&QuadOutS[INDI_DISABLED], "QUADOUT_OFF", "Disable", ISS_OFF);
69     IUFillSwitchVector(&QuadOutSP, QuadOutS, 2, getDeviceName(), "QUADOUT_POWER", "Quad Output", MAIN_CONTROL_TAB,
70                        IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
71 
72     // Adjustable Power
73     //    IUFillSwitch(&AdjOutS[INDI_ENABLED], "ADJOUT_ON", "Enable", ISS_OFF);
74     //    IUFillSwitch(&AdjOutS[INDI_DISABLED], "ADJOUT_OFF", "Disable", ISS_OFF);
75     //    IUFillSwitchVector(&AdjOutSP, AdjOutS, 2, getDeviceName(), "ADJOUT_POWER", "Adj Output", MAIN_CONTROL_TAB, IP_RW,
76     //                       ISR_1OFMANY, 60, IPS_IDLE);
77 
78     // Adjustable Voltage
79     IUFillSwitch(&AdjOutVoltS[ADJOUT_OFF], "ADJOUT_OFF", "Off", ISS_ON);
80     IUFillSwitch(&AdjOutVoltS[ADJOUT_3V], "ADJOUT_3V", "3V", ISS_OFF);
81     IUFillSwitch(&AdjOutVoltS[ADJOUT_5V], "ADJOUT_5V", "5V", ISS_OFF);
82     IUFillSwitch(&AdjOutVoltS[ADJOUT_8V], "ADJOUT_8V", "8V", ISS_OFF);
83     IUFillSwitch(&AdjOutVoltS[ADJOUT_9V], "ADJOUT_9V", "9V", ISS_OFF);
84     IUFillSwitch(&AdjOutVoltS[ADJOUT_12V], "ADJOUT_12V", "12V", ISS_OFF);
85     IUFillSwitchVector(&AdjOutVoltSP, AdjOutVoltS, 6, getDeviceName(), "ADJOUT_VOLTAGE", "Adj voltage", MAIN_CONTROL_TAB, IP_RW,
86                        ISR_1OFMANY, 60, IPS_IDLE);
87 
88     // Reboot
89     IUFillSwitch(&RebootS[0], "REBOOT", "Reboot Device", ISS_OFF);
90     IUFillSwitchVector(&RebootSP, RebootS, 1, getDeviceName(), "REBOOT_DEVICE", "Device", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1,
91                        60, IPS_IDLE);
92 
93     // Power Sensors
94     IUFillNumber(&PowerSensorsN[SENSOR_VOLTAGE], "SENSOR_VOLTAGE", "Voltage (V)", "%4.2f", 0, 999, 100, 0);
95     IUFillNumber(&PowerSensorsN[SENSOR_CURRENT], "SENSOR_CURRENT", "Current (A)", "%4.2f", 0, 999, 100, 0);
96     IUFillNumber(&PowerSensorsN[SENSOR_AVG_AMPS], "SENSOR_AVG_AMPS", "Average Current (A)", "%4.2f", 0, 999, 100, 0);
97     IUFillNumber(&PowerSensorsN[SENSOR_AMP_HOURS], "SENSOR_AMP_HOURS", "Amp hours (Ah)", "%4.2f", 0, 999, 100, 0);
98     IUFillNumber(&PowerSensorsN[SENSOR_WATT_HOURS], "SENSOR_WATT_HOURS", "Watt hours (Wh)", "%4.2f", 0, 999, 100, 0);
99     IUFillNumber(&PowerSensorsN[SENSOR_TOTAL_CURRENT], "SENSOR_TOTAL_CURRENT", "Total current (A)", "%4.2f", 0, 999, 100, 0);
100     IUFillNumber(&PowerSensorsN[SENSOR_12V_CURRENT], "SENSOR_12V_CURRENT", "12V current (A)", "%4.2f", 0, 999, 100, 0);
101     IUFillNumber(&PowerSensorsN[SENSOR_DEWA_CURRENT], "SENSOR_DEWA_CURRENT", "DewA current (A)", "%4.2f", 0, 999, 100, 0);
102     IUFillNumber(&PowerSensorsN[SENSOR_DEWB_CURRENT], "SENSOR_DEWB_CURRENT", "DewB current (A)", "%4.2f", 0, 999, 100, 0);
103     IUFillNumberVector(&PowerSensorsNP, PowerSensorsN, 9, getDeviceName(), "POWER_SENSORS", "Sensors", MAIN_CONTROL_TAB, IP_RO,
104                        60, IPS_IDLE);
105 
106     IUFillLight(&PowerWarnL[0], "POWER_WARN_ON", "Current Overload", IPS_IDLE);
107     IUFillLightVector(&PowerWarnLP, PowerWarnL, 1, getDeviceName(), "POWER_WARM", "Power Warn", MAIN_CONTROL_TAB, IPS_IDLE);
108 
109     // LED Indicator
110     IUFillSwitch(&LedIndicatorS[INDI_ENABLED], "LED_ON", "Enable", ISS_ON);
111     IUFillSwitch(&LedIndicatorS[INDI_DISABLED], "LED_OFF", "Disable", ISS_OFF);
112     IUFillSwitchVector(&LedIndicatorSP, LedIndicatorS, 2, getDeviceName(), "LED_INDICATOR", "LED Indicator", MAIN_CONTROL_TAB,
113                        IP_RW,
114                        ISR_1OFMANY, 60, IPS_IDLE);
115 
116     ////////////////////////////////////////////////////////////////////////////
117     /// Power Group
118     ////////////////////////////////////////////////////////////////////////////
119 
120     // Power on Boot
121     IUFillSwitch(&PowerOnBootS[0], "POWER_PORT_1", "Quad Out", ISS_ON);
122     IUFillSwitch(&PowerOnBootS[1], "POWER_PORT_2", "Adj Out", ISS_ON);
123     IUFillSwitch(&PowerOnBootS[2], "POWER_PORT_3", "Dew A", ISS_ON);
124     IUFillSwitch(&PowerOnBootS[3], "POWER_PORT_4", "Dew B", ISS_ON);
125     IUFillSwitchVector(&PowerOnBootSP, PowerOnBootS, 4, getDeviceName(), "POWER_ON_BOOT", "Power On Boot", MAIN_CONTROL_TAB,
126                        IP_RW, ISR_NOFMANY, 60, IPS_IDLE);
127 
128     ////////////////////////////////////////////////////////////////////////////
129     /// Dew Group
130     ////////////////////////////////////////////////////////////////////////////
131 
132     // Automatic Dew
133     IUFillSwitch(&AutoDewS[INDI_ENABLED], "INDI_ENABLED", "Enabled", ISS_OFF);
134     IUFillSwitch(&AutoDewS[INDI_DISABLED], "INDI_DISABLED", "Disabled", ISS_OFF);
135     IUFillSwitchVector(&AutoDewSP, AutoDewS, 2, getDeviceName(), "AUTO_DEW", "Auto Dew", DEW_TAB, IP_RW, ISR_1OFMANY, 60,
136                        IPS_IDLE);
137 
138     // Dew PWM
139     IUFillNumber(&DewPWMN[DEW_PWM_A], "DEW_A", "Dew A (%)", "%.2f", 0, 100, 10, 0);
140     IUFillNumber(&DewPWMN[DEW_PWM_B], "DEW_B", "Dew B (%)", "%.2f", 0, 100, 10, 0);
141     IUFillNumberVector(&DewPWMNP, DewPWMN, 2, getDeviceName(), "DEW_PWM", "Dew PWM", DEW_TAB, IP_RW, 60, IPS_IDLE);
142 
143     ////////////////////////////////////////////////////////////////////////////
144     /// Firmware Group
145     ////////////////////////////////////////////////////////////////////////////
146     IUFillText(&FirmwareT[FIRMWARE_VERSION], "VERSION", "Version", "NA");
147     IUFillText(&FirmwareT[FIRMWARE_UPTIME], "UPTIME", "Uptime (h)", "NA");
148     IUFillTextVector(&FirmwareTP, FirmwareT, 2, getDeviceName(), "FIRMWARE_INFO", "Firmware", FIRMWARE_TAB, IP_RO, 60,
149                      IPS_IDLE);
150 
151     ////////////////////////////////////////////////////////////////////////////
152     /// Environment Group
153     ////////////////////////////////////////////////////////////////////////////
154     addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -15, 35, 15);
155     addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15);
156     addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15);
157     setCriticalParameter("WEATHER_TEMPERATURE");
158 
159     ////////////////////////////////////////////////////////////////////////////
160     /// Focuser Group
161     ////////////////////////////////////////////////////////////////////////////
162 
163     // Max Speed
164     IUFillNumber(&FocuserSettingsN[SETTING_MAX_SPEED], "SETTING_MAX_SPEED", "Max Speed (%)", "%.f", 0, 900, 100, 400);
165     IUFillNumberVector(&FocuserSettingsNP, FocuserSettingsN, 1, getDeviceName(), "FOCUSER_SETTINGS", "Settings", FOCUS_TAB,
166                        IP_RW, 60, IPS_IDLE);
167 
168     // Stepping
169     IUFillSwitch(&FocuserDriveS[STEP_FULL], "STEP_FULL", "Full", ISS_OFF);
170     IUFillSwitch(&FocuserDriveS[STEP_HALF], "STEP_HALF", "Half", ISS_ON);
171     IUFillSwitch(&FocuserDriveS[STEP_FORTH], "STEP_FORTH", "1/4", ISS_OFF);
172     IUFillSwitch(&FocuserDriveS[STEP_EIGHTH], "STEP_EIGHTH", "1/8", ISS_OFF);
173     IUFillSwitchVector(&FocuserDriveSP, FocuserDriveS, 4, getDeviceName(), "FOCUSER_DRIVE", "Microstepping", FOCUS_TAB,
174                        IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
175 
176     ////////////////////////////////////////////////////////////////////////////
177     /// Serial Connection
178     ////////////////////////////////////////////////////////////////////////////
179     serialConnection = new Connection::Serial(this);
180     serialConnection->registerHandshake([&]()
181     {
182         return Handshake();
183     });
184     registerConnection(serialConnection);
185 
186     return true;
187 }
188 
updateProperties()189 bool PegasusPPBA::updateProperties()
190 {
191     INDI::DefaultDevice::updateProperties();
192 
193     if (isConnected())
194     {
195         m_HasExternalMotor = findExternalMotorController();
196 
197         if (m_HasExternalMotor)
198         {
199             getXMCStartupData();
200             setDriverInterface(getDriverInterface() | FOCUSER_INTERFACE);
201             syncDriverInfo();
202         }
203 
204         // Main Control
205         defineProperty(&QuadOutSP);
206         //defineProperty(&AdjOutSP);
207         defineProperty(&AdjOutVoltSP);
208         defineProperty(&PowerSensorsNP);
209         defineProperty(&PowerOnBootSP);
210         defineProperty(&RebootSP);
211         defineProperty(&PowerWarnLP);
212         defineProperty(&LedIndicatorSP);
213 
214         // Dew
215         defineProperty(&AutoDewSP);
216         defineProperty(&DewPWMNP);
217 
218         // Focuser
219         if (m_HasExternalMotor)
220         {
221             FI::updateProperties();
222             defineProperty(&FocuserSettingsNP);
223             defineProperty(&FocuserDriveSP);
224         }
225 
226         WI::updateProperties();
227 
228         // Firmware
229         defineProperty(&FirmwareTP);
230 
231         setupComplete = true;
232     }
233     else
234     {
235         // Main Control
236         deleteProperty(QuadOutSP.name);
237         //deleteProperty(AdjOutSP.name);
238         deleteProperty(AdjOutVoltSP.name);
239         deleteProperty(PowerSensorsNP.name);
240         deleteProperty(PowerOnBootSP.name);
241         deleteProperty(RebootSP.name);
242         deleteProperty(PowerWarnLP.name);
243         deleteProperty(LedIndicatorSP.name);
244 
245         // Dew
246         deleteProperty(AutoDewSP.name);
247         deleteProperty(DewPWMNP.name);
248 
249         if (m_HasExternalMotor)
250         {
251             FI::updateProperties();
252             deleteProperty(FocuserSettingsNP.name);
253             deleteProperty(FocuserDriveSP.name);
254         }
255 
256         WI::updateProperties();
257 
258         deleteProperty(FirmwareTP.name);
259 
260         setupComplete = false;
261     }
262 
263     return true;
264 }
265 
getDefaultName()266 const char * PegasusPPBA::getDefaultName()
267 {
268     return "Pegasus PPBA";
269 }
270 
Handshake()271 bool 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             stopChar = 0xA;
299             tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read);
300         }
301 
302         if (tty_rc != TTY_OK)
303         {
304             char errorMessage[MAXRBUF];
305             tty_error_msg(tty_rc, errorMessage, MAXRBUF);
306             LOGF_ERROR("Serial read error: %s", errorMessage);
307             return false;
308         }
309     }
310 
311     tcflush(PortFD, TCIOFLUSH);
312     response[nbytes_read - 1] = '\0';
313     LOGF_DEBUG("RES <%s>", response);
314 
315     setupComplete = false;
316 
317     return (!strcmp(response, "PPBA_OK") || !strcmp(response, "PPBM_OK"));
318 }
319 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)320 bool PegasusPPBA::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
321 {
322     if (dev && !strcmp(dev, getDeviceName()))
323     {
324         // Quad 12V Power
325         if (!strcmp(name, QuadOutSP.name))
326         {
327             IUUpdateSwitch(&QuadOutSP, states, names, n);
328 
329             QuadOutSP.s = IPS_ALERT;
330             char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
331             snprintf(cmd, PEGASUS_LEN, "P1:%d", QuadOutS[INDI_ENABLED].s == ISS_ON);
332             if (sendCommand(cmd, res))
333             {
334                 QuadOutSP.s = !strcmp(cmd, res) ? IPS_OK : IPS_ALERT;
335             }
336 
337             IUResetSwitch(&QuadOutSP);
338             IDSetSwitch(&QuadOutSP, nullptr);
339             return true;
340         }
341 
342         //        // Adjustable Power
343         //        if (!strcmp(name, AdjOutSP.name))
344         //        {
345         //            IUUpdateSwitch(&AdjOutSP, states, names, n);
346 
347         //            AdjOutSP.s = IPS_ALERT;
348         //            char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
349         //            snprintf(cmd, PEGASUS_LEN, "P2:%d", AdjOutS[INDI_ENABLED].s == ISS_ON);
350         //            if (sendCommand(cmd, res))
351         //            {
352         //                AdjOutSP.s = !strcmp(cmd, res) ? IPS_OK : IPS_ALERT;
353         //            }
354 
355         //            IUResetSwitch(&AdjOutSP);
356         //            IDSetSwitch(&AdjOutSP, nullptr);
357         //            return true;
358         //        }
359 
360         // Adjustable Voltage
361         if (!strcmp(name, AdjOutVoltSP.name))
362         {
363             int previous_index = IUFindOnSwitchIndex(&AdjOutVoltSP);
364             IUUpdateSwitch(&AdjOutVoltSP, states, names, n);
365             int target_index = IUFindOnSwitchIndex(&AdjOutVoltSP);
366             int adjv = 0;
367             switch(target_index)
368             {
369                 case ADJOUT_OFF:
370                     adjv = 0;
371                     break;
372                 case ADJOUT_3V:
373                     adjv = 3;
374                     break;
375                 case ADJOUT_5V:
376                     adjv = 5;
377                     break;
378                 case ADJOUT_8V:
379                     adjv = 8;
380                     break;
381                 case ADJOUT_9V:
382                     adjv = 9;
383                     break;
384                 case ADJOUT_12V:
385                     adjv = 12;
386                     break;
387             }
388 
389             AdjOutVoltSP.s = IPS_ALERT;
390             char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
391             snprintf(cmd, PEGASUS_LEN, "P2:%d", adjv);
392             if (sendCommand(cmd, res))
393                 AdjOutVoltSP.s = IPS_OK;
394             else
395             {
396                 IUResetSwitch(&AdjOutVoltSP);
397                 AdjOutVoltS[previous_index].s = ISS_ON;
398                 AdjOutVoltSP.s = IPS_ALERT;
399             }
400 
401             IDSetSwitch(&AdjOutVoltSP, nullptr);
402             return true;
403         }
404 
405         // Reboot
406         if (!strcmp(name, RebootSP.name))
407         {
408             RebootSP.s = reboot() ? IPS_OK : IPS_ALERT;
409             IDSetSwitch(&RebootSP, nullptr);
410             LOG_INFO("Rebooting device...");
411             return true;
412         }
413 
414         // LED Indicator
415         if (!strcmp(name, LedIndicatorSP.name))
416         {
417             IUUpdateSwitch(&LedIndicatorSP, states, names, n);
418             char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
419             snprintf(cmd, PEGASUS_LEN, "PL:%d", LedIndicatorS[INDI_ENABLED].s == ISS_ON);
420             if (sendCommand(cmd, res))
421             {
422                 LedIndicatorSP.s = !strcmp(cmd, res) ? IPS_OK : IPS_ALERT;
423             }
424             IDSetSwitch(&LedIndicatorSP, nullptr);
425             saveConfig(true, LedIndicatorSP.name);
426             return true;
427         }
428 
429         // Power on boot
430         if (!strcmp(name, PowerOnBootSP.name))
431         {
432             IUUpdateSwitch(&PowerOnBootSP, states, names, n);
433             PowerOnBootSP.s = setPowerOnBoot() ? IPS_OK : IPS_ALERT;
434             IDSetSwitch(&PowerOnBootSP, nullptr);
435             saveConfig(true, PowerOnBootSP.name);
436             return true;
437         }
438 
439         // Auto Dew
440         if (!strcmp(name, AutoDewSP.name))
441         {
442             int prevIndex = IUFindOnSwitchIndex(&AutoDewSP);
443             IUUpdateSwitch(&AutoDewSP, states, names, n);
444             if (setAutoDewEnabled(AutoDewS[INDI_ENABLED].s == ISS_ON))
445             {
446                 AutoDewSP.s = IPS_OK;
447             }
448             else
449             {
450                 IUResetSwitch(&AutoDewSP);
451                 AutoDewS[prevIndex].s = ISS_ON;
452                 AutoDewSP.s = IPS_ALERT;
453             }
454 
455             IDSetSwitch(&AutoDewSP, nullptr);
456             return true;
457         }
458 
459         // Microstepping
460         if (!strcmp(name, FocuserDriveSP.name))
461         {
462             int prevIndex = IUFindOnSwitchIndex(&FocuserDriveSP);
463             IUUpdateSwitch(&FocuserDriveSP, states, names, n);
464             if (setFocuserMicrosteps(IUFindOnSwitchIndex(&FocuserDriveSP) + 1))
465             {
466                 FocuserDriveSP.s = IPS_OK;
467             }
468             else
469             {
470                 IUResetSwitch(&FocuserDriveSP);
471                 FocuserDriveS[prevIndex].s = ISS_ON;
472                 FocuserDriveSP.s = IPS_ALERT;
473             }
474 
475             IDSetSwitch(&FocuserDriveSP, nullptr);
476             return true;
477         }
478 
479         if (strstr(name, "FOCUS"))
480             return FI::processSwitch(dev, name, states, names, n);
481     }
482 
483     return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
484 }
485 
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)486 bool PegasusPPBA::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
487 {
488     if (dev && !strcmp(dev, getDeviceName()))
489     {
490         // Dew PWM
491         if (!strcmp(name, DewPWMNP.name))
492         {
493             bool rc1 = false, rc2 = false;
494             for (int i = 0; i < n; i++)
495             {
496                 if (!strcmp(names[i], DewPWMN[DEW_PWM_A].name))
497                     rc1 = setDewPWM(3, static_cast<uint8_t>(values[i] / 100.0 * 255.0));
498                 else if (!strcmp(names[i], DewPWMN[DEW_PWM_B].name))
499                     rc2 = setDewPWM(4, static_cast<uint8_t>(values[i] / 100.0 * 255.0));
500             }
501 
502             DewPWMNP.s = (rc1 && rc2) ? IPS_OK : IPS_ALERT;
503             if (DewPWMNP.s == IPS_OK)
504                 IUUpdateNumber(&DewPWMNP, values, names, n);
505             IDSetNumber(&DewPWMNP, nullptr);
506             return true;
507         }
508 
509         // Focuser Settings
510         if (!strcmp(name, FocuserSettingsNP.name))
511         {
512             if (setFocuserMaxSpeed(values[0]))
513             {
514                 FocuserSettingsN[0].value = values[0];
515                 FocuserSettingsNP.s = IPS_OK;
516             }
517             else
518             {
519                 FocuserSettingsNP.s = IPS_ALERT;
520             }
521 
522             IDSetNumber(&FocuserSettingsNP, nullptr);
523             return true;
524         }
525 
526         if (strstr(name, "FOCUS_"))
527             return FI::processNumber(dev, name, values, names, n);
528 
529         if (strstr(name, "WEATHER_"))
530             return WI::processNumber(dev, name, values, names, n);
531     }
532     return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
533 }
534 
sendCommand(const char * cmd,char * res)535 bool PegasusPPBA::sendCommand(const char * cmd, char * res)
536 {
537     int nbytes_read = 0, nbytes_written = 0, tty_rc = 0;
538     char command[PEGASUS_LEN] = {0};
539     LOGF_DEBUG("CMD <%s>", cmd);
540 
541     for (int i = 0; i < 2; i++)
542     {
543         tcflush(PortFD, TCIOFLUSH);
544         snprintf(command, PEGASUS_LEN, "%s\n", cmd);
545         if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
546             continue;
547 
548         if (!res)
549         {
550             tcflush(PortFD, TCIOFLUSH);
551             return true;
552         }
553 
554         if ( (tty_rc = tty_nread_section(PortFD, res, PEGASUS_LEN, stopChar, PEGASUS_TIMEOUT, &nbytes_read)) != TTY_OK
555                 || nbytes_read == 1)
556             continue;
557 
558         tcflush(PortFD, TCIOFLUSH);
559         res[nbytes_read - 1] = '\0';
560         LOGF_DEBUG("RES <%s>", res);
561         return true;
562     }
563 
564     if (tty_rc != TTY_OK)
565     {
566         char errorMessage[MAXRBUF];
567         tty_error_msg(tty_rc, errorMessage, MAXRBUF);
568         LOGF_ERROR("Serial error: %s", errorMessage);
569     }
570 
571     return false;
572 }
573 
findExternalMotorController()574 bool PegasusPPBA::findExternalMotorController()
575 {
576     char res[PEGASUS_LEN] = {0};
577     if (!sendCommand("XS", res))
578         return false;
579 
580     // 200 XMC present
581     return strstr(res, "200");
582 }
583 
setAutoDewEnabled(bool enabled)584 bool PegasusPPBA::setAutoDewEnabled(bool enabled)
585 {
586     char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
587     snprintf(cmd, PEGASUS_LEN, "PD:%d", enabled ? 1 : 0);
588     if (sendCommand(cmd, res))
589     {
590         return (!strcmp(res, cmd));
591     }
592 
593     return false;
594 }
595 
setPowerOnBoot()596 bool PegasusPPBA::setPowerOnBoot()
597 {
598     char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
599     snprintf(cmd, PEGASUS_LEN, "PE:%d%d%d%d", PowerOnBootS[0].s == ISS_ON ? 1 : 0,
600              PowerOnBootS[1].s == ISS_ON ? 1 : 0,
601              PowerOnBootS[2].s == ISS_ON ? 1 : 0,
602              PowerOnBootS[3].s == ISS_ON ? 1 : 0);
603     if (sendCommand(cmd, res))
604     {
605         return (!strcmp(res, "PE:1"));
606     }
607 
608     return false;
609 }
610 
setDewPWM(uint8_t id,uint8_t value)611 bool PegasusPPBA::setDewPWM(uint8_t id, uint8_t value)
612 {
613     char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expected[PEGASUS_LEN] = {0};
614     snprintf(cmd, PEGASUS_LEN, "P%d:%03d", id, value);
615     snprintf(expected, PEGASUS_LEN, "P%d:%d", id, value);
616     if (sendCommand(cmd, res))
617     {
618         return (!strcmp(res, expected));
619     }
620 
621     return false;
622 }
623 
saveConfigItems(FILE * fp)624 bool PegasusPPBA::saveConfigItems(FILE * fp)
625 {
626     INDI::DefaultDevice::saveConfigItems(fp);
627     if (m_HasExternalMotor)
628     {
629         FI::saveConfigItems(fp);
630         IUSaveConfigNumber(fp, &FocuserSettingsNP);
631         IUSaveConfigSwitch(fp, &FocuserDriveSP);
632     }
633     WI::saveConfigItems(fp);
634     IUSaveConfigSwitch(fp, &AutoDewSP);
635     return true;
636 }
637 
TimerHit()638 void PegasusPPBA::TimerHit()
639 {
640     if (!isConnected() || setupComplete == false)
641     {
642         SetTimer(getCurrentPollingPeriod());
643         return;
644     }
645 
646     getSensorData();
647     getConsumptionData();
648     getMetricsData();
649 
650     if (m_HasExternalMotor)
651         queryXMC();
652 
653     SetTimer(getCurrentPollingPeriod());
654 }
655 
sendFirmware()656 bool PegasusPPBA::sendFirmware()
657 {
658     char res[PEGASUS_LEN] = {0};
659     if (sendCommand("PV", res))
660     {
661         LOGF_INFO("Detected firmware %s", res);
662         IUSaveText(&FirmwareT[FIRMWARE_VERSION], res);
663         IDSetText(&FirmwareTP, nullptr);
664         return true;
665     }
666 
667     return false;
668 }
669 
getSensorData()670 bool PegasusPPBA::getSensorData()
671 {
672     char res[PEGASUS_LEN] = {0};
673     if (sendCommand("PA", res))
674     {
675         std::vector<std::string> result = split(res, ":");
676         if (result.size() < PA_N)
677         {
678             LOG_WARN("Received wrong number of detailed sensor data. Retrying...");
679             return false;
680         }
681 
682         if (result == lastSensorData)
683             return true;
684 
685         // Power Sensors
686         PowerSensorsN[SENSOR_VOLTAGE].value = std::stod(result[PA_VOLTAGE]);
687         PowerSensorsN[SENSOR_CURRENT].value = std::stod(result[PA_CURRENT]) / 65.0;
688         PowerSensorsNP.s = IPS_OK;
689         if (lastSensorData[PA_VOLTAGE] != result[PA_VOLTAGE] || lastSensorData[PA_CURRENT] != result[PA_CURRENT])
690             IDSetNumber(&PowerSensorsNP, nullptr);
691 
692         // Environment Sensors
693         setParameterValue("WEATHER_TEMPERATURE", std::stod(result[PA_TEMPERATURE]));
694         setParameterValue("WEATHER_HUMIDITY", std::stod(result[PA_HUMIDITY]));
695         setParameterValue("WEATHER_DEWPOINT", std::stod(result[PA_DEW_POINT]));
696         if (lastSensorData[PA_TEMPERATURE] != result[PA_TEMPERATURE] ||
697                 lastSensorData[PA_HUMIDITY] != result[PA_HUMIDITY] ||
698                 lastSensorData[PA_DEW_POINT] != result[PA_DEW_POINT])
699         {
700             if (WI::syncCriticalParameters())
701                 IDSetLight(&critialParametersLP, nullptr);
702             ParametersNP.s = IPS_OK;
703             IDSetNumber(&ParametersNP, nullptr);
704         }
705 
706         // Power Status
707         QuadOutS[INDI_ENABLED].s = (std::stoi(result[PA_PORT_STATUS]) == 1) ? ISS_ON : ISS_OFF;
708         QuadOutS[INDI_DISABLED].s = (std::stoi(result[PA_PORT_STATUS]) == 1) ? ISS_OFF : ISS_ON;
709         QuadOutSP.s = (std::stoi(result[6]) == 1) ? IPS_OK : IPS_IDLE;
710         if (lastSensorData[PA_PORT_STATUS] != result[PA_PORT_STATUS])
711             IDSetSwitch(&QuadOutSP, nullptr);
712 
713         // Adjustable Power Status
714         //        AdjOutS[INDI_ENABLED].s = (std::stoi(result[PA_ADJ_STATUS]) == 1) ? ISS_ON : ISS_OFF;
715         //        AdjOutS[INDI_DISABLED].s = (std::stoi(result[PA_ADJ_STATUS]) == 1) ? ISS_OFF : ISS_ON;
716         //        AdjOutSP.s = (std::stoi(result[PA_ADJ_STATUS]) == 1) ? IPS_OK : IPS_IDLE;
717         //        if (lastSensorData[PA_ADJ_STATUS] != result[PA_ADJ_STATUS])
718         //            IDSetSwitch(&AdjOutSP, nullptr);
719 
720         // Adjustable Power Status
721         IUResetSwitch(&AdjOutVoltSP);
722         if (std::stoi(result[PA_ADJ_STATUS]) == 0)
723             AdjOutVoltS[ADJOUT_OFF].s = ISS_ON;
724         else
725         {
726             AdjOutVoltS[ADJOUT_3V].s = (std::stoi(result[PA_PWRADJ]) == 3) ? ISS_ON : ISS_OFF;
727             AdjOutVoltS[ADJOUT_5V].s = (std::stoi(result[PA_PWRADJ]) == 5) ? ISS_ON : ISS_OFF;
728             AdjOutVoltS[ADJOUT_8V].s = (std::stoi(result[PA_PWRADJ]) == 8) ? ISS_ON : ISS_OFF;
729             AdjOutVoltS[ADJOUT_9V].s = (std::stoi(result[PA_PWRADJ]) == 9) ? ISS_ON : ISS_OFF;
730             AdjOutVoltS[ADJOUT_12V].s = (std::stoi(result[PA_PWRADJ]) == 12) ? ISS_ON : ISS_OFF;
731         }
732         if (lastSensorData[PA_PWRADJ] != result[PA_PWRADJ] || lastSensorData[PA_ADJ_STATUS] != result[PA_ADJ_STATUS])
733             IDSetSwitch(&AdjOutVoltSP, nullptr);
734 
735         // Power Warn
736         PowerWarnL[0].s = (std::stoi(result[PA_PWR_WARN]) == 1) ? IPS_ALERT : IPS_OK;
737         PowerWarnLP.s = (std::stoi(result[PA_PWR_WARN]) == 1) ? IPS_ALERT : IPS_OK;
738         if (lastSensorData[PA_PWR_WARN] != result[PA_PWR_WARN])
739             IDSetLight(&PowerWarnLP, nullptr);
740 
741         // Dew PWM
742         DewPWMN[0].value = std::stod(result[PA_DEW_1]) / 255.0 * 100.0;
743         DewPWMN[1].value = std::stod(result[PA_DEW_2]) / 255.0 * 100.0;
744         if (lastSensorData[PA_DEW_1] != result[PA_DEW_1] || lastSensorData[PA_DEW_2] != result[PA_DEW_2])
745             IDSetNumber(&DewPWMNP, nullptr);
746 
747         // Auto Dew
748         AutoDewS[INDI_DISABLED].s = (std::stoi(result[PA_AUTO_DEW]) == 1) ? ISS_OFF : ISS_ON;
749         AutoDewS[INDI_ENABLED].s  = (std::stoi(result[PA_AUTO_DEW]) == 1) ? ISS_ON : ISS_OFF;
750         AutoDewSP.s = (std::stoi(result[PA_AUTO_DEW]) == 1) ? IPS_OK : IPS_IDLE;
751         if (lastSensorData[PA_AUTO_DEW] != result[PA_AUTO_DEW])
752             IDSetSwitch(&AutoDewSP, nullptr);
753 
754         lastSensorData = result;
755 
756         return true;
757     }
758 
759     return false;
760 }
761 
getConsumptionData()762 bool PegasusPPBA::getConsumptionData()
763 {
764     char res[PEGASUS_LEN] = {0};
765     if (sendCommand("PS", res))
766     {
767         std::vector<std::string> result = split(res, ":");
768         if (result.size() < PS_N)
769         {
770             LOG_WARN("Received wrong number of detailed consumption data. Retrying...");
771             return false;
772         }
773 
774         if (result == lastConsumptionData)
775             return true;
776 
777         // Power Sensors
778         PowerSensorsN[SENSOR_AVG_AMPS].value = std::stod(result[PS_AVG_AMPS]);
779         PowerSensorsN[SENSOR_AMP_HOURS].value = std::stod(result[PS_AMP_HOURS]);
780         PowerSensorsN[SENSOR_WATT_HOURS].value = std::stod(result[PS_WATT_HOURS]);
781         PowerSensorsNP.s = IPS_OK;
782         if (lastConsumptionData[PS_AVG_AMPS] != result[PS_AVG_AMPS] || lastConsumptionData[PS_AMP_HOURS] != result[PS_AMP_HOURS]
783                 || lastConsumptionData[PS_WATT_HOURS] != result[PS_WATT_HOURS])
784             IDSetNumber(&PowerSensorsNP, nullptr);
785 
786         lastConsumptionData = result;
787 
788         return true;
789     }
790 
791     return false;
792 }
793 
getMetricsData()794 bool PegasusPPBA::getMetricsData()
795 {
796     char res[PEGASUS_LEN] = {0};
797     if (sendCommand("PC", res))
798     {
799         std::vector<std::string> result = split(res, ":");
800         if (result.size() < PC_N)
801         {
802             LOG_WARN("Received wrong number of detailed metrics data. Retrying...");
803             return false;
804         }
805 
806         if (result == lastMetricsData)
807             return true;
808 
809         // Power Sensors
810         PowerSensorsN[SENSOR_TOTAL_CURRENT].value = std::stod(result[PC_TOTAL_CURRENT]);
811         PowerSensorsN[SENSOR_12V_CURRENT].value = std::stod(result[PC_12V_CURRENT]);
812         PowerSensorsN[SENSOR_DEWA_CURRENT].value = std::stod(result[PC_DEWA_CURRENT]);
813         PowerSensorsN[SENSOR_DEWB_CURRENT].value = std::stod(result[PC_DEWB_CURRENT]);
814         PowerSensorsNP.s = IPS_OK;
815         if (lastMetricsData[PC_TOTAL_CURRENT] != result[PC_TOTAL_CURRENT] ||
816                 lastMetricsData[PC_12V_CURRENT] != result[PC_12V_CURRENT] ||
817                 lastMetricsData[PC_DEWA_CURRENT] != result[PC_DEWA_CURRENT] ||
818                 lastMetricsData[PC_DEWB_CURRENT] != result[PC_DEWB_CURRENT])
819             IDSetNumber(&PowerSensorsNP, nullptr);
820 
821         std::chrono::milliseconds uptime(std::stol(result[PC_UPTIME]));
822         using dhours = std::chrono::duration<double, std::ratio<3600>>;
823         std::stringstream ss;
824         ss << std::fixed << std::setprecision(3) << dhours(uptime).count();
825         IUSaveText(&FirmwareT[FIRMWARE_UPTIME], ss.str().c_str());
826         IDSetText(&FirmwareTP, nullptr);
827 
828         lastMetricsData = result;
829 
830         return true;
831     }
832 
833     return false;
834 }
835 // Device Control
reboot()836 bool PegasusPPBA::reboot()
837 {
838     return sendCommand("PF", nullptr);
839 }
840 
841 //////////////////////////////////////////////////////////////////////
842 ///
843 //////////////////////////////////////////////////////////////////////
MoveAbsFocuser(uint32_t targetTicks)844 IPState PegasusPPBA::MoveAbsFocuser(uint32_t targetTicks)
845 {
846     char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
847     snprintf(cmd, PEGASUS_LEN, "XS:3#%u", targetTicks);
848     return (sendCommand(cmd, res) ? IPS_BUSY : IPS_ALERT);
849 }
850 
851 //////////////////////////////////////////////////////////////////////
852 ///
853 //////////////////////////////////////////////////////////////////////
MoveRelFocuser(FocusDirection dir,uint32_t ticks)854 IPState PegasusPPBA::MoveRelFocuser(FocusDirection dir, uint32_t ticks)
855 {
856     return MoveAbsFocuser(dir == FOCUS_INWARD ? FocusAbsPosN[0].value - ticks : FocusAbsPosN[0].value + ticks);
857 }
858 
859 //////////////////////////////////////////////////////////////////////
860 ///
861 //////////////////////////////////////////////////////////////////////
AbortFocuser()862 bool PegasusPPBA::AbortFocuser()
863 {
864     return sendCommand("XS:6", nullptr);
865 }
866 
867 //////////////////////////////////////////////////////////////////////
868 ///
869 //////////////////////////////////////////////////////////////////////
ReverseFocuser(bool enabled)870 bool PegasusPPBA::ReverseFocuser(bool enabled)
871 {
872     char cmd[PEGASUS_LEN] = {0};
873     snprintf(cmd, PEGASUS_LEN, "XS:8#%d", enabled ? 1 : 0);
874     return sendCommand(cmd, nullptr);
875 }
876 
877 //////////////////////////////////////////////////////////////////////
878 ///
879 //////////////////////////////////////////////////////////////////////
SyncFocuser(uint32_t ticks)880 bool PegasusPPBA::SyncFocuser(uint32_t ticks)
881 {
882     char cmd[PEGASUS_LEN] = {0};
883     snprintf(cmd, PEGASUS_LEN, "XS:5#%u", ticks);
884     return sendCommand(cmd, nullptr);
885 }
886 
887 //////////////////////////////////////////////////////////////////////
888 ///
889 //////////////////////////////////////////////////////////////////////
SetFocuserBacklash(int32_t steps)890 bool PegasusPPBA::SetFocuserBacklash(int32_t steps)
891 {
892     char cmd[PEGASUS_LEN] = {0};
893     snprintf(cmd, PEGASUS_LEN, "XS:10#%d", steps);
894     return sendCommand(cmd, nullptr);
895 }
896 
897 //////////////////////////////////////////////////////////////////////
898 ///
899 //////////////////////////////////////////////////////////////////////
setFocuserMaxSpeed(uint16_t maxSpeed)900 bool PegasusPPBA::setFocuserMaxSpeed(uint16_t maxSpeed)
901 {
902     char cmd[PEGASUS_LEN] = {0};
903     snprintf(cmd, PEGASUS_LEN, "XS:7#%d", maxSpeed);
904     return sendCommand(cmd, nullptr);
905 }
906 
setFocuserMicrosteps(int value)907 bool PegasusPPBA::setFocuserMicrosteps(int value)
908 {
909     char cmd[PEGASUS_LEN] = {0};
910     snprintf(cmd, PEGASUS_LEN, "XS:9#%d", value);
911     return sendCommand(cmd, nullptr);
912 }
913 
914 //////////////////////////////////////////////////////////////////////
915 ///
916 //////////////////////////////////////////////////////////////////////
SetFocuserBacklashEnabled(bool enabled)917 bool PegasusPPBA::SetFocuserBacklashEnabled(bool enabled)
918 {
919     char cmd[PEGASUS_LEN] = {0};
920     snprintf(cmd, PEGASUS_LEN, "XS:8#%d", enabled ? 1 : 0);
921     return sendCommand(cmd, nullptr);
922 }
923 
924 //////////////////////////////////////////////////////////////////////
925 ///
926 //////////////////////////////////////////////////////////////////////
getXMCStartupData()927 bool PegasusPPBA::getXMCStartupData()
928 {
929     char res[PEGASUS_LEN] = {0};
930 
931     // Position
932     if (sendCommand("XS:2", res))
933     {
934         uint32_t position = 0;
935         sscanf(res, "%*[^#]#%d", &position);
936         FocusAbsPosN[0].value = position;
937     }
938 
939     // Max speed
940     if (sendCommand("XS:7", res))
941     {
942         uint32_t speed = 0;
943         sscanf(res, "%*[^#]#%d", &speed);
944         FocuserSettingsN[0].value = speed;
945     }
946 
947     return true;
948 }
949 
950 //////////////////////////////////////////////////////////////////////
951 ///
952 //////////////////////////////////////////////////////////////////////
queryXMC()953 void PegasusPPBA::queryXMC()
954 {
955     char res[PEGASUS_LEN] = {0};
956     uint32_t position = 0;
957     uint32_t motorRunning = 0;
958 
959     // Get Motor Status
960     if (sendCommand("XS:1", res))
961         sscanf(res, "%*[^#]#%d", &motorRunning);
962     // Get Position
963     if (sendCommand("XS:2", res))
964         sscanf(res, "%*[^#]#%d", &position);
965 
966     uint32_t lastPosition = FocusAbsPosN[0].value;
967     FocusAbsPosN[0].value = position;
968 
969     if (FocusAbsPosNP.s == IPS_BUSY && motorRunning == 0)
970     {
971         FocusAbsPosNP.s = IPS_OK;
972         FocusRelPosNP.s = IPS_OK;
973         IDSetNumber(&FocusAbsPosNP, nullptr);
974         IDSetNumber(&FocusRelPosNP, nullptr);
975     }
976     else if (lastPosition != position)
977         IDSetNumber(&FocusAbsPosNP, nullptr);
978 
979 }
980 //////////////////////////////////////////////////////////////////////
981 ///
982 //////////////////////////////////////////////////////////////////////
split(const std::string & input,const std::string & regex)983 std::vector<std::string> PegasusPPBA::split(const std::string &input, const std::string &regex)
984 {
985     // passing -1 as the submatch index parameter performs splitting
986     std::regex re(regex);
987     std::sregex_token_iterator
988     first{input.begin(), input.end(), re, -1},
989           last;
990     return {first, last};
991 }
992 
993