1 /*******************************************************************************
2 Copyright(c) 2019 Jasem Mutlaq. All rights reserved.
3
4 Pegasus Pocket 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_ppb.h"
26 #include "indicom.h"
27 #include "connectionplugins/connectionserial.h"
28
29 #include <memory>
30 #include <regex>
31 #include <termios.h>
32 #include <cstring>
33 #include <sys/ioctl.h>
34
35 // We declare an auto pointer to PegasusPPB.
36 static std::unique_ptr<PegasusPPB> pocket_power_box(new PegasusPPB());
37
PegasusPPB()38 PegasusPPB::PegasusPPB() : WI(this)
39 {
40 setVersion(1, 1);
41 lastSensorData.reserve(PA_N);
42 }
43
initProperties()44 bool PegasusPPB::initProperties()
45 {
46 INDI::DefaultDevice::initProperties();
47
48 setDriverInterface(AUX_INTERFACE | WEATHER_INTERFACE);
49
50 WI::initProperties(ENVIRONMENT_TAB, ENVIRONMENT_TAB);
51
52 addAuxControls();
53
54 ////////////////////////////////////////////////////////////////////////////
55 /// Main Control Panel
56 ////////////////////////////////////////////////////////////////////////////
57 // Cycle all power on/off
58 IUFillSwitch(&PowerCycleAllS[POWER_CYCLE_OFF], "POWER_CYCLE_OFF", "All Off", ISS_OFF);
59 IUFillSwitch(&PowerCycleAllS[POWER_CYCLE_ON], "POWER_CYCLE_ON", "All On", ISS_OFF);
60 IUFillSwitchVector(&PowerCycleAllSP, PowerCycleAllS, 2, getDeviceName(), "POWER_CYCLE", "Cycle Power", MAIN_CONTROL_TAB,
61 IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
62
63 // DSLR on/off
64 IUFillSwitch(&DSLRPowerS[DSLR_OFF], "DSLR_OFF", "Off", ISS_OFF);
65 IUFillSwitch(&DSLRPowerS[DSLR_ON], "DSLR_ON", "On", ISS_OFF);
66 IUFillSwitchVector(&DSLRPowerSP, DSLRPowerS, 2, getDeviceName(), "DSLR_POWER", "DSLR Power", MAIN_CONTROL_TAB, IP_RW,
67 ISR_ATMOST1, 60, IPS_IDLE);
68
69 // Reboot
70 IUFillSwitch(&RebootS[0], "REBOOT", "Reboot Device", ISS_OFF);
71 IUFillSwitchVector(&RebootSP, RebootS, 1, getDeviceName(), "REBOOT_DEVICE", "Device", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1,
72 60, IPS_IDLE);
73
74 // Power Sensors
75 IUFillNumber(&PowerSensorsN[SENSOR_VOLTAGE], "SENSOR_VOLTAGE", "Voltage (V)", "%4.2f", 0, 999, 100, 0);
76 IUFillNumber(&PowerSensorsN[SENSOR_CURRENT], "SENSOR_CURRENT", "Current (A)", "%4.2f", 0, 999, 100, 0);
77 IUFillNumberVector(&PowerSensorsNP, PowerSensorsN, 2, getDeviceName(), "POWER_SENSORS", "Sensors", MAIN_CONTROL_TAB, IP_RO,
78 60, IPS_IDLE);
79
80 ////////////////////////////////////////////////////////////////////////////
81 /// Power Group
82 ////////////////////////////////////////////////////////////////////////////
83
84 // Power on Boot
85 IUFillSwitch(&PowerOnBootS[0], "POWER_PORT_1", "Port 1", ISS_ON);
86 IUFillSwitch(&PowerOnBootS[1], "POWER_PORT_2", "Port 2", ISS_ON);
87 IUFillSwitch(&PowerOnBootS[2], "POWER_PORT_3", "Port 3", ISS_ON);
88 IUFillSwitch(&PowerOnBootS[3], "POWER_PORT_4", "Port 4", ISS_ON);
89 IUFillSwitchVector(&PowerOnBootSP, PowerOnBootS, 4, getDeviceName(), "POWER_ON_BOOT", "Power On Boot", MAIN_CONTROL_TAB,
90 IP_RW, ISR_NOFMANY, 60, IPS_IDLE);
91
92 ////////////////////////////////////////////////////////////////////////////
93 /// Dew Group
94 ////////////////////////////////////////////////////////////////////////////
95
96 // Automatic Dew
97 IUFillSwitch(&AutoDewS[INDI_ENABLED], "INDI_ENABLED", "Enabled", ISS_OFF);
98 IUFillSwitch(&AutoDewS[INDI_DISABLED], "INDI_DISABLED", "Disabled", ISS_ON);
99 IUFillSwitchVector(&AutoDewSP, AutoDewS, 2, getDeviceName(), "AUTO_DEW", "Auto Dew", DEW_TAB, IP_RW, ISR_1OFMANY, 60,
100 IPS_IDLE);
101
102 // Dew PWM
103 IUFillNumber(&DewPWMN[DEW_PWM_A], "DEW_A", "Dew A (%)", "%.2f", 0, 100, 10, 0);
104 IUFillNumber(&DewPWMN[DEW_PWM_B], "DEW_B", "Dew B (%)", "%.2f", 0, 100, 10, 0);
105 IUFillNumberVector(&DewPWMNP, DewPWMN, 2, getDeviceName(), "DEW_PWM", "Dew PWM", DEW_TAB, IP_RW, 60, IPS_IDLE);
106
107 ////////////////////////////////////////////////////////////////////////////
108 /// Environment Group
109 ////////////////////////////////////////////////////////////////////////////
110 addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -15, 35, 15);
111 addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15);
112 addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15);
113 setCriticalParameter("WEATHER_TEMPERATURE");
114
115 ////////////////////////////////////////////////////////////////////////////
116 /// Serial Connection
117 ////////////////////////////////////////////////////////////////////////////
118 serialConnection = new Connection::Serial(this);
119 serialConnection->registerHandshake([&]()
120 {
121 return Handshake();
122 });
123 registerConnection(serialConnection);
124
125 return true;
126 }
127
updateProperties()128 bool PegasusPPB::updateProperties()
129 {
130 INDI::DefaultDevice::updateProperties();
131
132 if (isConnected())
133 {
134 // Main Control
135 defineProperty(&PowerCycleAllSP);
136 defineProperty(&DSLRPowerSP);
137 defineProperty(&PowerSensorsNP);
138 defineProperty(&PowerOnBootSP);
139 defineProperty(&RebootSP);
140
141 // Dew
142 defineProperty(&AutoDewSP);
143 defineProperty(&DewPWMNP);
144
145 WI::updateProperties();
146
147 setupComplete = true;
148 }
149 else
150 {
151 // Main Control
152 deleteProperty(PowerCycleAllSP.name);
153 deleteProperty(DSLRPowerSP.name);
154 deleteProperty(PowerSensorsNP.name);
155 deleteProperty(PowerOnBootSP.name);
156 deleteProperty(RebootSP.name);
157
158 // Dew
159 deleteProperty(AutoDewSP.name);
160 deleteProperty(DewPWMNP.name);
161
162 WI::updateProperties();
163
164 setupComplete = false;
165 }
166
167 return true;
168 }
169
getDefaultName()170 const char * PegasusPPB::getDefaultName()
171 {
172 return "Pegasus PPB";
173 }
174
Handshake()175 bool PegasusPPB::Handshake()
176 {
177 int tty_rc = 0, nbytes_written = 0, nbytes_read = 0;
178 char command[PEGASUS_LEN] = {0}, response[PEGASUS_LEN] = {0};
179
180 PortFD = serialConnection->getPortFD();
181
182 LOG_DEBUG("CMD <P#>");
183
184 tcflush(PortFD, TCIOFLUSH);
185 strncpy(command, "P#\n", PEGASUS_LEN);
186 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
187 {
188 char errorMessage[MAXRBUF];
189 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
190 LOGF_ERROR("Serial write error: %s", errorMessage);
191 return false;
192 }
193
194 // Try first with stopChar as the stop character
195 if ( (tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read)) != TTY_OK)
196 {
197 // Try 0xA as the stop character
198 if (tty_rc == TTY_OVERFLOW || tty_rc == TTY_TIME_OUT)
199 {
200 tcflush(PortFD, TCIOFLUSH);
201 tty_write_string(PortFD, command, &nbytes_written);
202 stopChar = 0xA;
203 tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read);
204 }
205
206 if (tty_rc != TTY_OK)
207 {
208 char errorMessage[MAXRBUF];
209 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
210 LOGF_ERROR("Serial read error: %s", errorMessage);
211 return false;
212 }
213 }
214
215 tcflush(PortFD, TCIOFLUSH);
216 response[nbytes_read - 1] = '\0';
217 LOGF_DEBUG("RES <%s>", response);
218
219 setupComplete = false;
220
221 return !strcmp(response, "PPB_OK");
222 }
223
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)224 bool PegasusPPB::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
225 {
226 if (dev && !strcmp(dev, getDeviceName()))
227 {
228 // Cycle all power on or off
229 if (!strcmp(name, PowerCycleAllSP.name))
230 {
231 IUUpdateSwitch(&PowerCycleAllSP, states, names, n);
232
233 PowerCycleAllSP.s = IPS_ALERT;
234 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
235 snprintf(cmd, PEGASUS_LEN, "P1:%d", IUFindOnSwitchIndex(&PowerCycleAllSP));
236 if (sendCommand(cmd, res))
237 {
238 PowerCycleAllSP.s = !strcmp(cmd, res) ? IPS_OK : IPS_ALERT;
239 }
240
241 IUResetSwitch(&PowerCycleAllSP);
242 IDSetSwitch(&PowerCycleAllSP, nullptr);
243 return true;
244 }
245
246 // DSLR
247 if (!strcmp(name, DSLRPowerSP.name))
248 {
249 IUUpdateSwitch(&DSLRPowerSP, states, names, n);
250
251 DSLRPowerSP.s = IPS_ALERT;
252 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
253 snprintf(cmd, PEGASUS_LEN, "P2:%d", IUFindOnSwitchIndex(&DSLRPowerSP));
254 if (sendCommand(cmd, res))
255 {
256 DSLRPowerSP.s = !strcmp(cmd, res) ? IPS_OK : IPS_ALERT;
257 }
258
259 IUResetSwitch(&DSLRPowerSP);
260 IDSetSwitch(&DSLRPowerSP, nullptr);
261 return true;
262 }
263
264 // Reboot
265 if (!strcmp(name, RebootSP.name))
266 {
267 RebootSP.s = reboot() ? IPS_OK : IPS_ALERT;
268 IDSetSwitch(&RebootSP, nullptr);
269 LOG_INFO("Rebooting device...");
270 return true;
271 }
272
273 // Power on boot
274 if (!strcmp(name, PowerOnBootSP.name))
275 {
276 IUUpdateSwitch(&PowerOnBootSP, states, names, n);
277 PowerOnBootSP.s = setPowerOnBoot() ? IPS_OK : IPS_ALERT;
278 IDSetSwitch(&PowerOnBootSP, nullptr);
279 saveConfig(true, PowerOnBootSP.name);
280 return true;
281 }
282
283 // Auto Dew
284 if (!strcmp(name, AutoDewSP.name))
285 {
286 int prevIndex = IUFindOnSwitchIndex(&AutoDewSP);
287 IUUpdateSwitch(&AutoDewSP, states, names, n);
288 if (setAutoDewEnabled(AutoDewS[INDI_ENABLED].s == ISS_ON))
289 {
290 AutoDewSP.s = IPS_OK;
291 }
292 else
293 {
294 IUResetSwitch(&AutoDewSP);
295 AutoDewS[prevIndex].s = ISS_ON;
296 AutoDewSP.s = IPS_ALERT;
297 }
298
299 IDSetSwitch(&AutoDewSP, nullptr);
300 return true;
301 }
302 }
303
304 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
305 }
306
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)307 bool PegasusPPB::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
308 {
309 if (dev && !strcmp(dev, getDeviceName()))
310 {
311 // Dew PWM
312 if (!strcmp(name, DewPWMNP.name))
313 {
314 bool rc1 = false, rc2 = false;
315 for (int i = 0; i < n; i++)
316 {
317 if (!strcmp(names[i], DewPWMN[DEW_PWM_A].name))
318 rc1 = setDewPWM(3, static_cast<uint8_t>(values[i] / 100.0 * 255.0));
319 else if (!strcmp(names[i], DewPWMN[DEW_PWM_B].name))
320 rc2 = setDewPWM(4, static_cast<uint8_t>(values[i] / 100.0 * 255.0));
321 }
322
323 DewPWMNP.s = (rc1 && rc2) ? IPS_OK : IPS_ALERT;
324 if (DewPWMNP.s == IPS_OK)
325 IUUpdateNumber(&DewPWMNP, values, names, n);
326 IDSetNumber(&DewPWMNP, nullptr);
327 return true;
328 }
329
330 if (strstr(name, "WEATHER_"))
331 return WI::processNumber(dev, name, values, names, n);
332 }
333 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
334 }
335
sendCommand(const char * cmd,char * res)336 bool PegasusPPB::sendCommand(const char * cmd, char * res)
337 {
338 int nbytes_read = 0, nbytes_written = 0, tty_rc = 0;
339 char command[PEGASUS_LEN] = {0};
340 LOGF_DEBUG("CMD <%s>", cmd);
341
342 for (int i = 0; i < 2; i++)
343 {
344 tcflush(PortFD, TCIOFLUSH);
345 snprintf(command, PEGASUS_LEN, "%s\n", cmd);
346 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
347 continue;
348
349 if (!res)
350 {
351 tcflush(PortFD, TCIOFLUSH);
352 return true;
353 }
354
355 if ( (tty_rc = tty_nread_section(PortFD, res, PEGASUS_LEN, stopChar, PEGASUS_TIMEOUT, &nbytes_read)) != TTY_OK
356 || nbytes_read == 1)
357 continue;
358
359 tcflush(PortFD, TCIOFLUSH);
360 res[nbytes_read - 1] = '\0';
361 LOGF_DEBUG("RES <%s>", res);
362 return true;
363 }
364
365 if (tty_rc != TTY_OK)
366 {
367 char errorMessage[MAXRBUF];
368 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
369 LOGF_ERROR("Serial error: %s", errorMessage);
370 }
371
372 return false;
373 }
374
setAutoDewEnabled(bool enabled)375 bool PegasusPPB::setAutoDewEnabled(bool enabled)
376 {
377 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
378 snprintf(cmd, PEGASUS_LEN, "PD:%d", enabled ? 1 : 0);
379 if (sendCommand(cmd, res))
380 {
381 return (!strcmp(res, cmd));
382 }
383
384 return false;
385 }
386
setPowerOnBoot()387 bool PegasusPPB::setPowerOnBoot()
388 {
389 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
390 snprintf(cmd, PEGASUS_LEN, "PE:%d%d%d%d", PowerOnBootS[0].s == ISS_ON ? 1 : 0,
391 PowerOnBootS[1].s == ISS_ON ? 1 : 0,
392 PowerOnBootS[2].s == ISS_ON ? 1 : 0,
393 PowerOnBootS[3].s == ISS_ON ? 1 : 0);
394 if (sendCommand(cmd, res))
395 {
396 return (!strcmp(res, "PE:1"));
397 }
398
399 return false;
400 }
401
setDewPWM(uint8_t id,uint8_t value)402 bool PegasusPPB::setDewPWM(uint8_t id, uint8_t value)
403 {
404 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expected[PEGASUS_LEN] = {0};
405 snprintf(cmd, PEGASUS_LEN, "P%d:%03d", id, value);
406 snprintf(expected, PEGASUS_LEN, "P%d:%d", id, value);
407 if (sendCommand(cmd, res))
408 {
409 return (!strcmp(res, expected));
410 }
411
412 return false;
413 }
414
saveConfigItems(FILE * fp)415 bool PegasusPPB::saveConfigItems(FILE * fp)
416 {
417 INDI::DefaultDevice::saveConfigItems(fp);
418 WI::saveConfigItems(fp);
419 IUSaveConfigSwitch(fp, &AutoDewSP);
420
421 return true;
422 }
423
TimerHit()424 void PegasusPPB::TimerHit()
425 {
426 if (!isConnected() || setupComplete == false)
427 {
428 SetTimer(getCurrentPollingPeriod());
429 return;
430 }
431
432 getSensorData();
433 SetTimer(getCurrentPollingPeriod());
434 }
435
sendFirmware()436 bool PegasusPPB::sendFirmware()
437 {
438 char res[PEGASUS_LEN] = {0};
439 if (sendCommand("PV", res))
440 {
441 LOGF_INFO("Detected firmware %s", res);
442 return true;
443 }
444
445 return false;
446 }
447
getSensorData()448 bool PegasusPPB::getSensorData()
449 {
450 char res[PEGASUS_LEN] = {0};
451 if (sendCommand("PA", res))
452 {
453 std::vector<std::string> result = split(res, ":");
454 if (result.size() < PA_N)
455 {
456 LOG_WARN("Received wrong number of detailed sensor data. Retrying...");
457 return false;
458 }
459
460 if (result == lastSensorData)
461 return true;
462
463 // Power Sensors
464 PowerSensorsN[SENSOR_VOLTAGE].value = std::stod(result[PA_VOLTAGE]);
465 PowerSensorsN[SENSOR_CURRENT].value = std::stod(result[PA_CURRENT]) / 65.0;
466 PowerSensorsNP.s = IPS_OK;
467 if (lastSensorData[PA_VOLTAGE] != result[PA_VOLTAGE] || lastSensorData[PA_CURRENT] != result[PA_CURRENT])
468 IDSetNumber(&PowerSensorsNP, nullptr);
469
470 // Environment Sensors
471 setParameterValue("WEATHER_TEMPERATURE", std::stod(result[PA_TEMPERATURE]));
472 setParameterValue("WEATHER_HUMIDITY", std::stod(result[PA_HUMIDITY]));
473 setParameterValue("WEATHER_DEWPOINT", std::stod(result[PA_DEW_POINT]));
474 if (lastSensorData[PA_TEMPERATURE] != result[PA_TEMPERATURE] ||
475 lastSensorData[PA_HUMIDITY] != result[PA_HUMIDITY] ||
476 lastSensorData[PA_DEW_POINT] != result[PA_DEW_POINT])
477 {
478 if (WI::syncCriticalParameters())
479 IDSetLight(&critialParametersLP, nullptr);
480 ParametersNP.s = IPS_OK;
481 IDSetNumber(&ParametersNP, nullptr);
482 }
483
484 // Power Status
485 PowerCycleAllS[POWER_CYCLE_ON].s = (std::stoi(result[PA_PORT_STATUS]) == 1) ? ISS_ON : ISS_OFF;
486 PowerCycleAllS[POWER_CYCLE_ON].s = (std::stoi(result[PA_PORT_STATUS]) == 0) ? ISS_ON : ISS_OFF;
487 PowerCycleAllSP.s = (std::stoi(result[6]) == 1) ? IPS_OK : IPS_IDLE;
488 if (lastSensorData[PA_PORT_STATUS] != result[PA_PORT_STATUS])
489 IDSetSwitch(&PowerCycleAllSP, nullptr);
490
491 // DSLR Power Status
492 DSLRPowerS[POWER_CYCLE_ON].s = (std::stoi(result[PA_DSLR_STATUS]) == 1) ? ISS_ON : ISS_OFF;
493 DSLRPowerS[POWER_CYCLE_ON].s = (std::stoi(result[PA_DSLR_STATUS]) == 0) ? ISS_ON : ISS_OFF;
494 DSLRPowerSP.s = (std::stoi(result[PA_DSLR_STATUS]) == 1) ? IPS_OK : IPS_IDLE;
495 if (lastSensorData[PA_DSLR_STATUS] != result[PA_DSLR_STATUS])
496 IDSetSwitch(&DSLRPowerSP, nullptr);
497
498 // Dew PWM
499 DewPWMN[0].value = std::stod(result[PA_DEW_1]) / 255.0 * 100.0;
500 DewPWMN[1].value = std::stod(result[PA_DEW_2]) / 255.0 * 100.0;
501 if (lastSensorData[PA_DEW_1] != result[PA_DEW_1] || lastSensorData[PA_DEW_2] != result[PA_DEW_2])
502 IDSetNumber(&DewPWMNP, nullptr);
503
504 // Auto Dew
505 AutoDewS[INDI_ENABLED].s = (std::stoi(result[PA_AUTO_DEW]) == 1) ? ISS_ON : ISS_OFF;
506 AutoDewS[INDI_DISABLED].s = (std::stoi(result[PA_AUTO_DEW]) == 1) ? ISS_OFF : ISS_ON;
507 if (lastSensorData[PA_AUTO_DEW] != result[PA_AUTO_DEW])
508 IDSetSwitch(&AutoDewSP, nullptr);
509
510 lastSensorData = result;
511
512 return true;
513 }
514
515 return false;
516 }
517
518 // Device Control
reboot()519 bool PegasusPPB::reboot()
520 {
521 return sendCommand("PF", nullptr);
522 }
523
split(const std::string & input,const std::string & regex)524 std::vector<std::string> PegasusPPB::split(const std::string &input, const std::string ®ex)
525 {
526 // passing -1 as the submatch index parameter performs splitting
527 std::regex re(regex);
528 std::sregex_token_iterator
529 first{input.begin(), input.end(), re, -1},
530 last;
531 return {first, last};
532 }
533
534