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 ®ex)
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