Instrument Neutral Distributed Interface INDI 2.2.5
pegasus_spb.cpp
Go to the documentation of this file.
1#include "pegasus_spb.h"
2#include "indicom.h"
4
5#include <regex>
6#include <termios.h>
7#include <chrono>
8#include <iomanip>
9
10static std::unique_ptr<PegasusSPB> spb(new PegasusSPB());
11
12
13
14//double result = map(100, 9, 255, 1, 100);
15//double reverse = map(result, 1, 100, 9, 255);
16//LOGF_DEBUG("%d", reverse);
17
18PegasusSPB::PegasusSPB() : INDI::DefaultDevice(), WI(this), PI(this)
19{
20 setVersion(1, 0);
21 lastSensorData.reserve(PA_N);
22 lastConsumptionData.reserve(PS_N);
23 lastMetricsData.reserve(PC_N);
24
25}
26
27double PegasusSPB::map(double value, double from1, double to1, double from2, double to2)
28{
29 // Make sure the value is within the source range [from1, to1]
30 value = std::max(from1, std::min(to1, value));
31
32 // Calculate the mapped value in the destination range [from2, to2]
33 double mappedValue = from2 + (value - from1) * (to2 - from2) / (to1 - from1);
34
35 return mappedValue;
36}
37
39{
42
43 WI::initProperties(ENVIRONMENT_TAB, ENVIRONMENT_TAB);
44
46
47 serialConnection = new Connection::Serial(this);
48 serialConnection->registerHandshake([&]()
49 {
50 return Handshake();
51 });
52 registerConnection(serialConnection);
53
56 PI::initProperties(POWER_TAB, 1, 2, 0, 1,
57 0); // 1 DC port (Quad Hub), 2 DEW ports (switchable), 0 Variable, 1 Auto Dew (global), 0 USB
58
59 // Power-Dew SwitchA
60 PowerDewSwitchASP[0].fill("DEW", "Dew", ISS_OFF);
61 PowerDewSwitchASP[1].fill("POWER", "Power", ISS_ON);
62 PowerDewSwitchASP.fill(getDeviceName(), "DEW_POWER_SWITCH_A", "Port A Mode", DEW_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
63
64 // Power-Dew SwitchB
65 PowerDewSwitchBSP[0].fill("DEW", "Dew", ISS_OFF);
66 PowerDewSwitchBSP[1].fill("POWER", "Power", ISS_ON);
67 PowerDewSwitchBSP.fill(getDeviceName(), "DEW_POWER_SWITCH_B", "Port B Mode", DEW_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
68
69 //DewAggress
70 DewAggressNP[0].fill("DEW_AGGRESS", "Agg Level", "%.2f", 0, 100, 1, 0);
71 DewAggressNP.fill(getDeviceName(), "DEW-AGGESS", "Auto Dew", DEW_TAB, IP_RW, 60, IPS_IDLE);
72
73 // overwrite labels to device labelling defaults
74 DewChannelsSP.setLabel("Dew Heater");
75 for (size_t i = 0; i < DewChannelsSP.size(); i++)
76 {
77 std::string name (1, static_cast<char>('A' + i));
78 DewChannelLabelsTP[i].setLabel("Dew " + name);
79 DewChannelLabelsTP[i].setText("Dew " + name);
80 }
82 {
83 PowerChannelLabelsTP[0].setLabel("Quad Output");
84 PowerChannelLabelsTP[0].setText("Enable 12V ports");
85 PowerChannelCurrentNP[0].setLabel("12 V current (A)");
86 }
87
88 // set the labels to the configured values
90 for (size_t i = 0; i < DewChannelsSP.size(); i++)
91 {
92 char dewChannelLabel[MAXINDILABEL];
93 char dewDutyCycleLabel[MAXINDILABEL];
94 char dewChannelCurrentLabel[MAXINDILABEL];
95 snprintf(dewChannelLabel, MAXINDILABEL, "%s", DewChannelLabelsTP[i].getText());
96 snprintf(dewDutyCycleLabel, MAXINDILABEL, "%s (%%)", DewChannelLabelsTP[i].getText());
97 snprintf(dewChannelCurrentLabel, MAXINDILABEL, "%s (A)", DewChannelLabelsTP[i].getText());
98 DewChannelsSP[i].setLabel(dewChannelLabel);
99 DewChannelDutyCycleNP[i].setLabel(dewDutyCycleLabel);
100 DewChannelCurrentNP[i].setLabel(dewChannelCurrentLabel);
101 }
104 {
105 char powerChannelLabel[MAXINDILABEL];
106 snprintf(powerChannelLabel, MAXINDILABEL, "%s", PowerChannelLabelsTP[0].getText());
107 PowerChannelsSP[0].setLabel(powerChannelLabel);
108 }
109
110 AutoDewSP.setLabel("Dew Control");
111 AutoDewSP[0].setLabel("Auto Dew Control");
112 PowerChannelsSP.setLabel("Quad Output");
113
114 // Power Sensors
115 PowerStatisticsNP[STATS_AVG_AMPS].fill("STATS_AVG_AMPS", "Average Current (A)", "%4.2f", 0, 999, 100, 0);
116 PowerStatisticsNP[STATS_AMP_HOURS].fill("STATS_AMP_HOURS", "Amp hours (Ah)", "%4.2f", 0, 999, 100, 0);
117 PowerStatisticsNP[STATS_WATT_HOURS].fill("STATS_WATT_HOURS", "Watt hours (Wh)", "%4.2f", 0, 999, 100, 0);
118 PowerStatisticsNP[STATS_TOTAL_CURRENT].fill("STATS_TOTAL_CURRENT", "Total current (A)", "%4.2f", 0, 999, 100, 0);
119 PowerStatisticsNP.fill(getDeviceName(), "POWER_STATISTICS", "Power Statistics", POWER_TAB, IP_RO, 60, IPS_IDLE);
120
121 // Firmware Group
122 FirmwareTP[FIRMWARE_VERSION].fill("VERSION", "Version", "NA");
123 FirmwareTP[FIRMWARE_UPTIME].fill("UPTIME", "Uptime (h)", "NA");
124 FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", FIRMWARE_TAB, IP_RO, 60,
125 IPS_IDLE);
126
127 // Environment Group
128 addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -15, 35, 15);
129 addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15);
130 addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15);
131 setCriticalParameter("WEATHER_TEMPERATURE");
132
133 // Sensor Offset
134 HumidityOffsetNP[0].fill("HUM_OFFSET", "Level", "%.0f", -50, 50, 1, 0);
135 HumidityOffsetNP.fill(getDeviceName(), "HUM-OFFSET", "Humidity Offset", ENVIRONMENT_TAB, IP_RW, 60, IPS_IDLE);
136
137 TemperatureOffsetNP[0].fill("TEMP_OFFSET", "Level", "%.0f", -40, 40, 1, 0);
138 TemperatureOffsetNP.fill(getDeviceName(), "TEMP-OFFSET", "Temperature Offset", ENVIRONMENT_TAB, IP_RW, 60, IPS_IDLE);
139
140 return true;
141
142}
143
145{
147
148 if (isConnected())
149 {
150 defineProperty(DewAggressNP);
151 int aggressiveness = getDewAggressiveness();
152 DewAggressNP[0].setValue(static_cast<double>(aggressiveness));
153 DewAggressNP.setState(IPS_OK);
154 DewAggressNP.apply();
155
156 defineProperty(PowerDewSwitchASP);
157 int portNumber = 1;
158 int mode = getPowerDewPortMode(portNumber);
159 PowerDewSwitchASP.reset();
160 PowerDewSwitchASP[DEW].setState(mode == PegasusSPB::PORT_MODE::DEW ? ISS_ON : ISS_OFF);
161 PowerDewSwitchASP[POWER].setState(mode == PegasusSPB::PORT_MODE::POWER ? ISS_ON : ISS_OFF);
162 PowerDewSwitchASP.setState(IPS_OK);
163 PowerDewSwitchASP.apply();
164
165 defineProperty(PowerDewSwitchBSP);
166 portNumber = 2;
167 mode = getPowerDewPortMode(portNumber);
168 PowerDewSwitchBSP.reset();
169 PowerDewSwitchBSP[DEW].setState(mode == PegasusSPB::PORT_MODE::DEW ? ISS_ON : ISS_OFF);
170 PowerDewSwitchBSP[POWER].setState(mode == PegasusSPB::PORT_MODE::POWER ? ISS_ON : ISS_OFF);
171 PowerDewSwitchBSP.setState(IPS_OK);
172 PowerDewSwitchBSP.apply();
173
174 defineProperty(HumidityOffsetNP);
175 int humidityOffset = getHumidityOffset();
176 HumidityOffsetNP[0].setValue(static_cast<double>(humidityOffset));
177 HumidityOffsetNP.setState(IPS_OK);
178 HumidityOffsetNP.apply();
179
180 defineProperty(TemperatureOffsetNP);
181 int temperatureOffset = getTemperatureOffset();
182 TemperatureOffsetNP[0].setValue(static_cast<double>(temperatureOffset));
183 TemperatureOffsetNP.setState(IPS_OK);
184 TemperatureOffsetNP.apply();
185
188 defineProperty(PowerStatisticsNP);
189
190 // Firmware
191 defineProperty(FirmwareTP);
192 getFirmware();
193
194 setupComplete = true;
195 }
196 else
197 {
198 deleteProperty(DewAggressNP);
199 deleteProperty(PowerDewSwitchASP);
200 deleteProperty(PowerDewSwitchBSP);
201 deleteProperty(HumidityOffsetNP);
202 deleteProperty(TemperatureOffsetNP);
205 deleteProperty(FirmwareTP);
206 deleteProperty(PowerStatisticsNP);
207 setupComplete = false;
208 }
209
210 return true;
211}
212
214{
215 return "Pegasus SPB";
216}
217
218
219bool PegasusSPB::Handshake()
220{
221 int tty_rc = 0, nbytes_written = 0, nbytes_read = 0;
222 char command[PEGASUS_LEN] = {0}, response[PEGASUS_LEN] = {0};
223
224 PortFD = serialConnection->getPortFD();
225
226 LOG_DEBUG("CMD <P#>");
227
228 tcflush(PortFD, TCIOFLUSH);
229 strncpy(command, "P#\n", PEGASUS_LEN);
230 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
231 {
232 char errorMessage[MAXRBUF];
233 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
234 LOGF_ERROR("Serial write error: %s", errorMessage);
235 return false;
236 }
237
238 // Try first with stopChar as the stop character
239 if ( (tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read)) != TTY_OK)
240 {
241 // Try 0xA as the stop character
242 if (tty_rc == TTY_OVERFLOW || tty_rc == TTY_TIME_OUT)
243 {
244 tcflush(PortFD, TCIOFLUSH);
245 tty_write_string(PortFD, command, &nbytes_written);
246 stopChar = 0xA;
247 tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read);
248 }
249
250 if (tty_rc != TTY_OK)
251 {
252 char errorMessage[MAXRBUF];
253 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
254 LOGF_ERROR("Serial read error: %s", errorMessage);
255 return false;
256 }
257 }
258
259 tcflush(PortFD, TCIOFLUSH);
260 response[nbytes_read - 1] = '\0';
261 LOGF_DEBUG("RES <%s>", response);
262
263 setupComplete = false;
264
265 return (strstr(response, "SPB") != nullptr);
266}
267
269{
270 if (!isConnected() || setupComplete == false)
271 {
273 return;
274 }
275
276 getSensorData();
277 getConsumptionData();
278 getMetricsData();
279
281}
282
283bool PegasusSPB::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
284{
285 if (dev != nullptr && !strcmp(dev, getDeviceName()))
286 {
287 // Process power-related switches via PowerInterface
288 if (PI::processSwitch(dev, name, states, names, n))
289 return true;
290
291 // Power-Dew SwitchA
292 if(PowerDewSwitchASP.isNameMatch(name))
293 {
294 PowerDewSwitchASP.update(states, names, n);
295 auto newState = PowerDewSwitchASP[DEW].getState() == ISS_ON ? 0 : 1;
296 auto result = setPowerDewPortMode(1, newState) ? IPS_OK : IPS_ALERT;
297 PowerDewSwitchASP.setState(result ? IPS_OK : IPS_ALERT);
298 PowerDewSwitchASP.apply();
299 saveConfig(PowerDewSwitchASP);
300
301 return true;
302 }
303 //Power-Dew SwitchB
304 if(PowerDewSwitchBSP.isNameMatch(name))
305 {
306 PowerDewSwitchBSP.update(states, names, n);
307 auto newState = PowerDewSwitchBSP[DEW].getState() == ISS_ON ? 0 : 1;
308 auto result = setPowerDewPortMode(2, newState) ? IPS_OK : IPS_ALERT;
309 PowerDewSwitchBSP.setState(result ? IPS_OK : IPS_ALERT);
310 PowerDewSwitchBSP.apply();
311 saveConfig(PowerDewSwitchBSP);
312 return true;
313 }
314 }
315
316 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
317}
318
319bool PegasusSPB::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
320{
321 if (dev != nullptr && !strcmp(dev, getDeviceName()))
322 {
323 // Process power-related numbers via PowerInterface
324 if (PI::processNumber(dev, name, values, names, n))
325 return true;
326
327 // DewAggress (custom property)
328 if(DewAggressNP.isNameMatch(name))
329 {
330 DewAggressNP.update(values, names, n);
331 IPState result = IPS_OK;
332 if (isConnected())
333 {
334 if(!setDewAggressiveness(static_cast<uint8_t>(values[0])))
335 {
336 result = IPS_ALERT;
337 }
338 }
339 DewAggressNP.setState(result);
340 DewAggressNP.apply();
341 return true;
342 }
343 // HumidityOffset (custom property)
344 if(HumidityOffsetNP.isNameMatch(name))
345 {
346 HumidityOffsetNP.update(values, names, n);
347 IPState result = IPS_OK;
348 if (isConnected())
349 {
350 if(!setHumdityOffset(static_cast<int>(values[0])))
351 {
352 result = IPS_ALERT;
353 }
354 }
355 HumidityOffsetNP.setState(result);
356 HumidityOffsetNP.apply();
357 return true;
358 }
359 // TemperatureOffset (custom property)
360 if(TemperatureOffsetNP.isNameMatch(name))
361 {
362 TemperatureOffsetNP.update(values, names, n);
363 IPState result = IPS_OK;
364 if (isConnected())
365 {
366 if(!setTemperatureOffset(static_cast<int>(values[0])))
367 {
368 result = IPS_ALERT;
369 }
370 }
371 TemperatureOffsetNP.setState(result);
372 TemperatureOffsetNP.apply();
373 return true;
374 }
375 }
376 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
377}
378
379bool PegasusSPB::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
380{
381 if (dev != nullptr && !strcmp(dev, getDeviceName()))
382 {
383 // Process power-related text via PowerInterface
384 if (PI::processText(dev, name, texts, names, n))
385 return true;
386 }
387 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
388}
389
390int PegasusSPB::getDewPortPower(int portNumber)
391{
392 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
393 snprintf(cmd, PEGASUS_LEN, "PA");
394 int power = -1;
395 if(sendCommand(cmd, res))
396 {
397 std::vector<std::string> result = split(res, ":");
398
399 if(portNumber == 1)
400 power = std::stod(result[PA_DEW_1]);
401 else if(portNumber == 2)
402 power = std::stod(result[PA_DEW_2]);
403 }
404 else
405 {
406 LOGF_ERROR("Error on set power-dew port mode [Port=%d]", portNumber);
407 }
408 return power / 255.0 * 100.0;
409}
410
411
412
413bool PegasusSPB::setDewPortPower(int portNumber, int power)
414{
415 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
416 snprintf(cmd, PEGASUS_LEN, "P%d:%d", portNumber + 2, static_cast<uint8_t>(power / 100.0 * 255.0));
417 if(sendCommand(cmd, res))
418 {
419 if(!strstr(cmd, res))
420 {
421 LOGF_ERROR("Error on set dew port power [Cmd=%s Res=%s]", cmd, res);
422 return false;
423 }
424 }
425 else
426 {
427 LOGF_ERROR("Error on set dew port power [Port=%d Power=%d]", portNumber, power);
428 return false;
429 }
430 return true;
431}
432
433int PegasusSPB::getPowerDewPortMode(int portNumber)
434{
435 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
436 snprintf(cmd, PEGASUS_LEN, "D%d:99", portNumber + 2);
437 if(sendCommand(cmd, res))
438 {
439 std::vector<std::string> result = split(res, ":");
440 return std::stod(result[1]);
441 }
442 else
443 {
444 LOGF_ERROR("Error on set power-dew port mode [Port=%d]", portNumber);
445 }
446 return -1;
447}
448
449bool PegasusSPB::setPowerDewPortMode(int portNumber, int mode)
450{
451 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
452 snprintf(cmd, PEGASUS_LEN, "D%d:%d", portNumber + 2, mode);
453 if(sendCommand(cmd, res))
454 {
455 if(!strstr(cmd, res))
456 {
457 LOGF_ERROR("Error on set power-dew port mode [Cmd=%s Res=%s]", cmd, res);
458 return false;
459 }
460 }
461 else
462 {
463 LOGF_ERROR("Error on set power-dew port mode [Port=%d Mode=%d]", portNumber, mode);
464 return false;
465 }
466 return true;
467}
468
469bool PegasusSPB::setDewAutoState(bool enabled)
470{
471 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
472 snprintf(cmd, PEGASUS_LEN, "PD:%d", enabled ? 1 : 0);
473 if(sendCommand(cmd, res))
474 {
475 if(!strstr(cmd, res))
476 {
477 LOGF_ERROR("Error on set dew auto state [Cmd=%s Res=%s]", cmd, res);
478 return false;
479 }
480 }
481 else
482 {
483 LOG_ERROR("Error on set set dew auto state");
484 return false;
485 }
486 return true;
487}
488
489bool PegasusSPB::setDewAggressiveness(uint8_t level)
490{
491 int mappedLevel = map(level, 0, 100, 10, 255);
492 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
493 snprintf(cmd, PEGASUS_LEN, "PD:%d", mappedLevel);
494 if(sendCommand(cmd, res))
495 {
496 if(!strstr(cmd, res))
497 {
498 LOGF_ERROR("Error on set dew aggresiveness [Cmd=%s Res=%s]", cmd, res);
499 return false;
500 }
501 }
502 else
503 {
504 LOG_ERROR("Error on set dew aggresiveness");
505 return false;
506 }
507 return true;
508}
509
510int PegasusSPB::getDewAggressiveness()
511{
512 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
513 snprintf(cmd, PEGASUS_LEN, "DA");
514 if(sendCommand(cmd, res))
515 {
516 std::vector<std::string> result = split(res, ":");
517 int value = map(std::stoi(result[1]), 10, 255, 0, 100);
518 return value;
519 }
520 else
521 {
522 LOGF_ERROR("Error on get dew aggresiveness [Cmd=%s Res=%s]", cmd, res);
523 }
524 return -1;
525}
526
527bool PegasusSPB::setHumdityOffset(int level)
528{
529 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
530 snprintf(cmd, PEGASUS_LEN, "CH:%d", level);
531 if(sendCommand(cmd, res))
532 {
533 if(!strstr(cmd, res))
534 {
535 LOGF_ERROR("Error on set humidity offset [Cmd=%s Res=%s]", cmd, res);
536 return false;
537 }
538 }
539 else
540 {
541 LOG_ERROR("Error on set humidity offset");
542 return false;
543 }
544 return true;
545}
546
547int PegasusSPB::getHumidityOffset()
548{
549 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
550 snprintf(cmd, PEGASUS_LEN, "CR");
551 if(sendCommand(cmd, res))
552 {
553 std::vector<std::string> result = split(res, ":");
554 int value = std::stoi(result[2]);
555 return value;
556 }
557 else
558 {
559 LOGF_ERROR("Error on get humidity offset [Cmd=%s Res=%s]", cmd, res);
560 }
561 return -1;
562}
563
564bool PegasusSPB::setTemperatureOffset(int level)
565{
566 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
567 snprintf(cmd, PEGASUS_LEN, "CT:%d", (level * 100));
568 if(sendCommand(cmd, res))
569 {
570 if(!strstr(cmd, res))
571 {
572 LOGF_ERROR("Error on set temperature offset [Cmd=%s Res=%s]", cmd, res);
573 return false;
574 }
575 }
576 else
577 {
578 LOG_ERROR("Error on set temperature offset");
579 return false;
580 }
581 return true;
582}
583
584int PegasusSPB::getTemperatureOffset()
585{
586 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
587 snprintf(cmd, PEGASUS_LEN, "CR");
588 if(sendCommand(cmd, res))
589 {
590 std::vector<std::string> result = split(res, ":");
591 int value = std::stoi(result[1]);
592 return (value / 100);
593 }
594 else
595 {
596 LOGF_ERROR("Error on get temperature offset [Cmd=%s Res=%s]", cmd, res);
597 }
598 return -1;
599}
600
601bool PegasusSPB::getFirmware()
602{
603 char res[PEGASUS_LEN] = {0};
604 if (sendCommand("PV", res))
605 {
606 std::vector<std::string> result = split(res, ":");
607 if (result.size() > 1)
608 {
609 LOGF_INFO("Detected firmware %s", result[1].c_str());
610 FirmwareTP[FIRMWARE_VERSION].setText(result[1]);
611 FirmwareTP.setState(IPS_OK);
612 FirmwareTP.apply();
613 return true;
614 }
615 else
616 {
617 LOGF_WARN("Unexpected firmware response: %s", res);
618 FirmwareTP.setState(IPS_ALERT);
619 FirmwareTP.apply();
620 return false;
621 }
622 }
623 LOGF_WARN("Retreiving firmware failed: %s", res);
624 FirmwareTP.setState(IPS_ALERT);
625 FirmwareTP.apply();
626 return false;
627}
628
629// Removed updatePropertiesPowerDewMode function definition
630
631bool PegasusSPB::setFixedPowerPortState(int portNumber, bool enabled)
632{
633 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
634 // Assuming fixed power ports are P1, P2, P3, P4.
635 // INDI::PowerInterface ports 0-3 map to physical ports 1-4.
636 snprintf(cmd, PEGASUS_LEN, "P%d:%d", portNumber + 1, enabled ? 1 : 0);
637 if (sendCommand(cmd, res))
638 {
639 return (!strstr(cmd, res));
640 }
641 return false;
642}
643
644bool PegasusSPB::getSensorData()
645{
646 char res[PEGASUS_LEN] = {0};
647 if (sendCommand("PA", res))
648 {
649 std::vector<std::string> result = split(res, ":");
650 if (result.size() < PA_N)
651 {
652 LOG_WARN("Received wrong number of detailed sensor data. Retrying...");
653 return false;
654 }
655
656 if (result == lastSensorData)
657 return true;
658
659 // Power Sensors
660 IPState oldPowerSensorsState = PI::PowerSensorsNP.getState();
661 double oldVoltage = PI::PowerSensorsNP[PI::SENSOR_VOLTAGE].getValue();
662 double oldCurrent = PI::PowerSensorsNP[PI::SENSOR_CURRENT].getValue();
663 double oldPower = PI::PowerSensorsNP[PI::SENSOR_POWER].getValue();
664
665 PI::PowerSensorsNP[PI::SENSOR_VOLTAGE].setValue(std::stod(result[PA_VOLTAGE]));
666 PI::PowerSensorsNP[PI::SENSOR_CURRENT].setValue(std::stod(result[PA_CURRENT]) / 65.0);
670
671 if (oldVoltage != PI::PowerSensorsNP[PI::SENSOR_VOLTAGE].getValue() ||
672 oldCurrent != PI::PowerSensorsNP[PI::SENSOR_CURRENT].getValue() ||
673 oldPower != PI::PowerSensorsNP[PI::SENSOR_POWER].getValue() ||
674 oldPowerSensorsState != PI::PowerSensorsNP.getState())
675 {
677 }
678
679 // Power Channels (fixed DC outputs)
680 // Assuming PI::PowerChannelsSP[0-3] map to the 4 fixed DC outputs.
681 // The original driver only has QuadPowerSP for group control.
682 // We need to update the state of PI::PowerChannelsSP based on the QuadPowerSP state.
683 bool quadPowerEnabled = (std::stoi(result[PA_PORT_STATUS]) == 1);
684 PI::PowerChannelsSP[0].setState(quadPowerEnabled ? ISS_ON : ISS_OFF);
686 if (lastSensorData.size() < PA_N || lastSensorData[PA_PORT_STATUS] != result[PA_PORT_STATUS])
688
689 // Dew Channels (switchable ports)
690 // Port 0 maps to original Port A (physical port 1).
691 // Port 1 maps to original Port B (physical port 2).
692 // Update DewChannelDutyCycleNP
693 PI::DewChannelDutyCycleNP[0].setValue(std::stod(result[PA_DEW_1]) / 255.0 * 100.0);
694 PI::DewChannelDutyCycleNP[1].setValue(std::stod(result[PA_DEW_2]) / 255.0 * 100.0);
696 if (lastSensorData.size() < PA_N || lastSensorData[PA_DEW_1] != result[PA_DEW_1]
697 || lastSensorData[PA_DEW_2] != result[PA_DEW_2])
699
700 // Auto Dew
701 bool autodew = std::stoi(result[PA_AUTO_DEW]);
702 PI::AutoDewSP[0].setState(autodew ? ISS_ON : ISS_OFF);
704 if (lastSensorData.size() < PA_N || lastSensorData[PA_AUTO_DEW] != result[PA_AUTO_DEW])
706
707 // ensure that dew heater channels are on, since the PPBA auto dew control handles both ports automatically
708 if (autodew)
709 {
710 bool changed = false;
711 for (size_t i = 0; i < DewChannelsSP.size(); i++)
712 {
713 if (DewChannelsSP[i].getState() == ISS_OFF)
714 {
716 LOGF_INFO("Auto dew on, enabling channel %s", (i == 0 ? "A" : "B"));
717 changed = true;
718 }
719 }
720 if (changed)
722 }
723
724 // Environment Sensors (remain as is, handled by WeatherInterface)
725 setParameterValue("WEATHER_TEMPERATURE", std::stod(result[PA_TEMPERATURE]));
726 setParameterValue("WEATHER_HUMIDITY", std::stod(result[PA_HUMIDITY]));
727 setParameterValue("WEATHER_DEWPOINT", std::stod(result[PA_DEW_POINT]));
728 if (lastSensorData.size() < PA_N ||
729 lastSensorData[PA_TEMPERATURE] != result[PA_TEMPERATURE] ||
730 lastSensorData[PA_HUMIDITY] != result[PA_HUMIDITY] ||
731 lastSensorData[PA_DEW_POINT] != result[PA_DEW_POINT])
732 {
737 }
738
739 lastSensorData = result;
740 return true;
741 }
742
743 return false;
744}
745
746bool PegasusSPB::getConsumptionData()
747{
748 char res[PEGASUS_LEN] = {0};
749 if (sendCommand("PS", res))
750 {
751 std::vector<std::string> result = split(res, ":");
752 if (result.size() < PS_N)
753 {
754 LOG_WARN("Received wrong number of detailed consumption data. Retrying...");
755 return false;
756 }
757
758 if (result == lastConsumptionData)
759 return true;
760
761 // Power Statistics
762 PowerStatisticsNP[STATS_AVG_AMPS].setValue(std::stod(result[PS_AVG_AMPS]));
763 PowerStatisticsNP[STATS_AMP_HOURS].setValue(std::stod(result[PS_AMP_HOURS]));
764 PowerStatisticsNP[STATS_WATT_HOURS].setValue(std::stod(result[PS_WATT_HOURS]));
765 PowerStatisticsNP.setState(IPS_OK);
766 if (lastConsumptionData.size() < PS_N ||
767 lastConsumptionData[PS_AVG_AMPS] != result[PS_AVG_AMPS] ||
768 lastConsumptionData[PS_AMP_HOURS] != result[PS_AMP_HOURS] ||
769 lastConsumptionData[PS_WATT_HOURS] != result[PS_WATT_HOURS])
770 PowerStatisticsNP.apply();
771
772 lastConsumptionData = result;
773
774 return true;
775 }
776
777 return false;
778}
779
780bool PegasusSPB::getMetricsData()
781{
782 char res[PEGASUS_LEN] = {0};
783 if (sendCommand("PC", res))
784 {
785 std::vector<std::string> result = split(res, ":");
786 if (result.size() < PC_N)
787 {
788 LOG_WARN("Received wrong number of detailed metrics data. Retrying...");
789 return false;
790 }
791
792 if (result == lastMetricsData)
793 return true;
794
795 // Power Sensors
796 PowerStatisticsNP[STATS_TOTAL_CURRENT].setValue(std::stod(result[PC_TOTAL_CURRENT]));
797 if (lastMetricsData.size() < PC_N || lastMetricsData[PC_TOTAL_CURRENT] != result[PC_TOTAL_CURRENT])
798 PowerStatisticsNP.apply();
799
800 // Power Sensors (Per-port current monitoring)
801 if (PI::PowerChannelCurrentNP.size() > 0)
802 {
803 PI::PowerChannelCurrentNP[0].setValue(std::stod(result[PC_12V_CURRENT]));
804 if (lastMetricsData.size() < PC_N || lastMetricsData[PC_12V_CURRENT] != result[PC_12V_CURRENT])
805 {
808 }
809 }
810
811 // Update PI::DewChannelCurrentNP for Dew ports
812 if (PI::DewChannelCurrentNP.size() > 1)
813 {
814 PI::DewChannelCurrentNP[0].setValue(std::stod(result[PC_DEWA_CURRENT]));
815 PI::DewChannelCurrentNP[1].setValue(std::stod(result[PC_DEWB_CURRENT]));
817 if (lastMetricsData.size() < PC_N ||
818 lastMetricsData[PC_DEWA_CURRENT] != result[PC_DEWA_CURRENT] ||
819 lastMetricsData[PC_DEWB_CURRENT] != result[PC_DEWB_CURRENT])
821 }
822
823 std::chrono::milliseconds uptime(std::stol(result[PC_UPTIME]));
824 using dhours = std::chrono::duration<double, std::ratio<3600 >>;
825 std::stringstream ss;
826 ss << std::fixed << std::setprecision(3) << dhours(uptime).count();
827 FirmwareTP[FIRMWARE_UPTIME].setText(ss.str().c_str());
828 FirmwareTP.apply();
829
830 lastMetricsData = result;
831
832 return true;
833 }
834
835 return false;
836}
837
838
839bool PegasusSPB::sendCommand(const char * cmd, char * res)
840{
841 int nbytes_read = 0, nbytes_written = 0, tty_rc = 0;
842 char command[PEGASUS_LEN] = {0};
843 LOGF_DEBUG("CMD <%s>", cmd);
844
845 for (int i = 0; i < 2; i++)
846 {
847 tcflush(PortFD, TCIOFLUSH);
848 snprintf(command, PEGASUS_LEN, "%s\n", cmd);
849 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
850 continue;
851
852 if (!res)
853 {
854 tcflush(PortFD, TCIOFLUSH);
855 return true;
856 }
857
858 if ( (tty_rc = tty_nread_section(PortFD, res, PEGASUS_LEN, stopChar, PEGASUS_TIMEOUT, &nbytes_read)) != TTY_OK
859 || nbytes_read == 1)
860 continue;
861
862 tcflush(PortFD, TCIOFLUSH);
863 res[nbytes_read - 1] = '\0';
864 LOGF_DEBUG("RES <%s>", res);
865 return true;
866 }
867
868 if (tty_rc != TTY_OK)
869 {
870 char errorMessage[MAXRBUF];
871 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
872 LOGF_ERROR("Serial error: %s", errorMessage);
873 }
874
875 return false;
876}
877
878std::vector<std::string> PegasusSPB::split(const std::string &input, const std::string &regex)
879{
880 // passing -1 as the submatch index parameter performs splitting
881 std::regex re(regex);
882 std::sregex_token_iterator
883 first{input.begin(), input.end(), re, -1},
884 last;
885 return {first, last};
886}
887
889{
893
894 DewAggressNP.save(fp);
895 HumidityOffsetNP.save(fp);
896 TemperatureOffsetNP.save(fp);
897 PowerDewSwitchASP.save(fp);
898 PowerDewSwitchBSP.save(fp);
899
900 return true;
901}
902
906bool PegasusSPB::SetPowerPort(size_t port, bool enabled)
907{
908 return setFixedPowerPortState(port, enabled);
909}
910
911bool PegasusSPB::SetDewPort(size_t port, bool enabled, double dutyCycle)
912{
913 // INDI::PowerInterface ports 0-1 map to the 2 switchable Dew ports.
914 // Port 0 maps to original Port A (physical port 1).
915 // Port 1 maps to original Port B (physical port 2).
916 int originalPortNumber = port + 1; // Map 0-based INDI port to 1-based original port
917
918 if (getPowerDewPortMode(originalPortNumber) == PegasusSPB::PORT_MODE::DEW)
919 {
920 return setDewPortPower(originalPortNumber, enabled ? dutyCycle : 0);
921 }
922 else // If the port is in POWER mode, control it as a fixed power port
923 {
924 // INDI::PowerInterface dew port 0 (original port 1) maps to fixed power port 3 (P3)
925 // INDI::PowerInterface dew port 1 (original port 2) maps to fixed power port 4 (P4)
926 // setFixedPowerPortState expects 0-indexed ports for P1-P4, so P3 is port 2, P4 is port 3.
927 // originalPortNumber is 1-indexed (1 or 2).
928 // So, originalPortNumber + 1 will map 1->2 (P3) and 2->3 (P4).
929 return setFixedPowerPortState(originalPortNumber + 1, enabled);
930 }
931}
932
933bool PegasusSPB::SetVariablePort(size_t port, bool enabled, double voltage)
934{
935 INDI_UNUSED(port);
936 INDI_UNUSED(enabled);
937 INDI_UNUSED(voltage);
938 // Pegasus SPB does not have variable voltage outputs.
939 return false;
940}
941
943{
944 INDI_UNUSED(enabled);
945 // Pegasus SPB does not have LED control.
946 return false;
947}
948
949bool PegasusSPB::SetAutoDewEnabled(size_t port, bool enabled)
950{
951 INDI_UNUSED(port);
952 // Pegasus SPB has global auto dew control.
953 return setDewAutoState(enabled);
954}
955
956bool PegasusSPB::SetUSBPort(size_t port, bool enabled)
957{
958 INDI_UNUSED(port);
959 INDI_UNUSED(enabled);
960 // Pegasus SPB does not have USB port control.
961 return false;
962}
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
bool isConnected() const
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
void 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::PropertySwitch DewChannelsSP
bool saveConfigItems(FILE *fp)
saveConfigItems Save power properties in config file
INDI::PropertySwitch AutoDewSP
void SetCapability(uint32_t cap)
SetPowerCapability sets the Power capabilities. All capabilities must be initialized.
INDI::PropertyNumber PowerSensorsNP
INDI::PropertyNumber PowerChannelCurrentNP
INDI::PropertyNumber DewChannelCurrentNP
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process Power number properties.
void initProperties(const char *groupName, size_t nPowerPorts=0, size_t nDewPorts=0, size_t nVariablePorts=0, size_t nAutoDewPorts=0, size_t nUSBPorts=0)
Initialize Power properties. It is recommended to call this function within initProperties() of your ...
INDI::PropertyText PowerChannelLabelsTP
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process Power text properties.
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process Power switch properties.
INDI::PropertySwitch PowerChannelsSP
INDI::PropertyNumber DewChannelDutyCycleNP
bool updateProperties()
updateProperties Define or Delete Power properties based on the connection status of the base device
INDI::PropertyText DewChannelLabelsTP
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
void setLabel(const char *label)
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
Provides interface to implement weather reporting and safety monitoring functionality.
void addParameter(std::string name, std::string label, double numMinOk, double numMaxOk, double percWarning, bool flipWarning=false)
Add a physical weather measurable parameter to the weather driver.
bool setCriticalParameter(std::string name)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
bool syncCriticalParameters()
Synchronize and update the state of all critical parameters.
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save parameters ranges in the config file.
INDI::PropertyNumber ParametersNP
void initProperties(const char *statusGroup, const char *paramsGroup)
Initialize focuser properties. It is recommended to call this function within initProperties() of you...
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
INDI::PropertyLight critialParametersLP
virtual bool 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 SetAutoDewEnabled(size_t port, bool enabled) override
SetAutoDewEnabled Enable/disable automatic dew control.
virtual bool SetPowerPort(size_t port, bool enabled) override
Power Interface Implementations.
virtual bool SetLEDEnabled(bool enabled) override
SetLEDEnabled Enable/disable power LEDs.
virtual bool SetVariablePort(size_t port, bool enabled, double voltage) override
SetVariablePort Set variable voltage port.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
const char * getDefaultName() override
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool SetDewPort(size_t port, bool enabled, double dutyCycle) override
SetDewPort Set DEW/Dew port duty cycle.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool SetUSBPort(size_t port, bool enabled) override
SetUSBPort Set USB port on/off.
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_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
#define MAXINDILABEL
Definition indiapi.h:190
@ ISR_1OFMANY
Definition indiapi.h:171
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 LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
Namespace to encapsulate INDI client, drivers, and mediator classes.
uint8_t cmd[4]
Definition pwc-ioctl.h:2