Instrument Neutral Distributed Interface INDI 2.2.5
celestron_dewpower.cpp
Go to the documentation of this file.
2#include "indicom.h"
4
5#include <memory>
6#include <termios.h>
7#include <cstring>
8#include <chrono>
9#include <thread>
10
11static std::unique_ptr<CelestronDewPower> celestronDewPower(new CelestronDewPower());
12
13CelestronDewPower::CelestronDewPower() : INDI::DefaultDevice(), INDI::PowerInterface(this), INDI::WeatherInterface(this)
14{
15 setVersion(1, 1);
16
17 DBG_CAUX = INDI::Logger::getInstance().addDebugLevel("AUX Protocol", "CAUX");
18 DBG_SERIAL = INDI::Logger::getInstance().addDebugLevel("Serial Verbose", "SERIAL");
19}
20
22{
23 return "Celestron Dew Power";
24}
25
27{
29 WI::initProperties("Weather", "Weather Parameters");
30
32
33 // Reboot
34 RebootSP[0].fill("REBOOT", "Reboot Device", ISS_OFF);
35 RebootSP.fill(getDefaultName(), "REBOOT_DEVICE", "Device", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1,
36 60, IPS_IDLE);
37
38 // Overall Power Consumption (remains as is, not part of INDI::Power)
39 PowerConsumptionNP[0].fill("CONSUMPTION_AVG_AMPS", "Avg. Amps", "%4.2f", 0, 999, 100, 0);
40 PowerConsumptionNP[1].fill("CONSUMPTION_AMP_HOURS", "Amp Hours", "%4.2f", 0, 999, 100, 0);
41 PowerConsumptionNP[2].fill("CONSUMPTION_WATT_HOURS", "Watt Hours", "%4.2f", 0, 999, 100, 0);
42 PowerConsumptionNP.fill(getDefaultName(), "POWER_CONSUMPTION", "Consumption",
44
45 PowerStatusLP[POWER_STATUS_OVERCURRENT].fill("OVERCURRENT", "Overcurrent", IPS_IDLE);
46 PowerStatusLP[POWER_STATUS_UNDERVOLTAGE].fill("UNDERVOLTAGE", "Under Voltage", IPS_IDLE);
47 PowerStatusLP[POWER_STATUS_OVERVOLTAGE].fill("OVERVOLTAGE", "Over Voltage", IPS_IDLE);
48 PowerStatusLP.fill(getDefaultName(), "POWER_STATUS", "Power Status", MAIN_CONTROL_TAB, IPS_IDLE);
49
50 // Define weather parameters
51 addParameter("WEATHER_AMBIENT_TEMPERATURE", "Ambient Temperature (C)", -50, 50, 15);
52 addParameter("WEATHER_DEW_POINT", "Dew Point (C)", -50, 50, 15);
53 addParameter("WEATHER_HUMIDITY", "Humidity (%)", 0, 100, 15);
54
55 // Set critical weather parameters
56 setCriticalParameter("WEATHER_AMBIENT_TEMPERATURE");
57
58 AutoDewModeSP[0].fill("DEW_MODE_AMBIENT", "Above Ambient", ISS_OFF);
59 AutoDewModeSP[1].fill("DEW_MODE_DEWPOINT", "Above Dew Point", ISS_ON);
60 AutoDewModeSP.fill(getDefaultName(), "AUTO_DEW_MODE", "Auto Dew Mode", DEW_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
61
62 AutoDewTempNP[0].fill("AUTO_DEW_TEMP", "Temp. Offset", "%2.0f", 0, 20, 1, 5);
63 AutoDewTempNP.fill(getDefaultName(), "AUTO_DEW_TEMP", "Auto Dew Temp. Offset", DEW_TAB, IP_RW, 60, IPS_IDLE);
64
65
69 serialConnection = new Connection::Serial(this);
70 serialConnection->registerHandshake([&]()
71 {
72 return Handshake();
73 });
74 registerConnection(serialConnection);
75
77
79
80 return true;
81}
82
84{
86 AUXCommand response;
87 if (sendAUXCommand(cmd) && readAUXResponse(response))
88 {
89 if (response.command() == PORTCTRL_GET_ENVIRONMENT)
90 {
91 // RESP: <0:3 the ambient temperature in mC><4:7 the dew point in mC><8 relative humidity 0-100%>
92 const AUXBuffer &data = response.getDataBuffer();
93 if (data.size() >= 9)
94 {
95 int32_t ambientTemp_mC = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
96 int32_t dewPoint_mC = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
97 uint8_t humidity = data[8];
98
99 setParameterValue("WEATHER_AMBIENT_TEMPERATURE", ambientTemp_mC / 1000.0);
100 setParameterValue("WEATHER_DEW_POINT", dewPoint_mC / 1000.0);
101 setParameterValue("WEATHER_HUMIDITY", humidity);
102
103 lastEnvironmentData = data; // Update lastEnvironmentData
104 return IPS_OK;
105 }
106 }
107 }
108 return IPS_ALERT; // Return ALERT if data fetching or parsing fails
109}
110
112{
114
115 if (isConnected())
116 {
117 defineProperty(RebootSP);
118 defineProperty(PowerConsumptionNP);
119 defineProperty(PowerStatusLP);
120 defineProperty(AutoDewModeSP);
121 defineProperty(AutoDewTempNP);
124 }
125 else
126 {
127 deleteProperty(RebootSP);
128 deleteProperty(PowerConsumptionNP);
129 deleteProperty(PowerStatusLP);
130 deleteProperty(AutoDewModeSP);
131 deleteProperty(AutoDewTempNP);
134 }
135
136 return true;
137}
138
140{
141 PortFD = serialConnection->getPortFD();
142
143 if (PortFD > 0)
144 {
145 // Get version
146 if (!getDewPowerControllerVersion())
147 {
148 LOG_ERROR("Failed to get device version.");
149 return false;
150 }
151
152 // Get number of ports
153 if (!getNumberOfPorts())
154 {
155 LOG_ERROR("Failed to get number of ports.");
156 return false;
157 }
158
159 // Initialize lastPortInfoData and lastDewHeaterPortInfoData vectors
160 lastPortInfoData.resize(m_NumPorts);
161 lastDewHeaterPortInfoData.resize(m_NumPorts); // Max possible dew ports is total ports
162 m_PortTypes.resize(m_NumPorts);
163
164 // Query each port to determine its type and capabilities
165 uint32_t capabilities = 0;
166 for (uint8_t i = 0; i < m_NumPorts; ++i)
167 {
169 cmd.setData(i, 1);
170 AUXCommand response;
171 if (sendAUXCommand(cmd) && readAUXResponse(response))
172 {
173 if (response.command() == PORTCTRL_GET_PORT_INFO)
174 {
175 // The controller only reports full telemetry (enabled/
176 // shorted/power/voltage, >= 7 bytes) when a port is
177 // actively drawing current; an idle port -- the normal
178 // state at connection time -- replies with just the
179 // 1-byte port type. Only require that one byte here so
180 // capability detection doesn't silently misclassify
181 // every idle port as DC output (portType 0).
182 const AUXBuffer &data = response.getDataBuffer();
183 if (!data.empty())
184 {
185 uint8_t portType = data[0];
186 m_PortTypes[i] = portType;
187 // Assuming portType 0 for DC, 1 for Dew, 2 for Variable, etc.
188 // This needs to be clarified from the protocol if different.
189 if (portType == 0) // DC Output
190 {
191 m_NumDCPorts++;
192 capabilities |= POWER_HAS_DC_OUT;
193 capabilities |= POWER_HAS_PER_PORT_CURRENT; // Assuming per-port current for DC
194 }
195 else if (portType == 1) // Dew Heater
196 {
197 m_NumDewPorts++;
198 capabilities |= POWER_HAS_DEW_OUT;
199 capabilities |= POWER_HAS_AUTO_DEW; // Assuming auto dew for dew ports
200 capabilities |= POWER_HAS_PER_PORT_CURRENT; // Assuming per-port current for Dew
201 }
202 else if (portType == 2) // Variable Voltage
203 {
204 m_NumVariablePorts++;
205 capabilities |= POWER_HAS_VARIABLE_OUT;
206 }
207 // Add other port types as needed (e.g., USB)
208 }
209 }
210 }
211 }
212
213 // Set overall capabilities
214 capabilities |= POWER_HAS_VOLTAGE_SENSOR;
215 capabilities |= POWER_HAS_OVERALL_CURRENT;
216 capabilities |= POWER_HAS_LED_TOGGLE; // Assuming LED toggle is always present
217
218 PI::SetCapability(capabilities);
219 PI::initProperties(POWER_TAB, m_NumDCPorts, m_NumDewPorts, m_NumVariablePorts, m_NumDewPorts, m_NumUSBPorts);
220
221 setupComplete = true;
222 return true;
223 }
224 else
225 {
226 return false;
227 }
228}
229
231{
232 if (!isConnected() || setupComplete == false)
233 {
235 return;
236 }
237
238 // Poll for input power
239 getInputPower();
240
241 // Poll for port info for all detected ports
242 for (uint8_t i = 0; i < m_NumPorts; ++i)
243 {
244 if (m_PortTypes[i] == 1) // Dew Heater
245 {
246 getDewHeaterPortInfo(i);
247 }
248 else // DC or Variable
249 {
250 getPortInfo(i);
251 }
252 }
253
255}
256
257bool CelestronDewPower::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
258{
259 if (dev && !strcmp(dev, getDefaultName()))
260 {
261 if (RebootSP.isNameMatch(name))
262 {
263 RebootSP.setState(IPS_OK); // Placeholder, actual reboot command to be implemented
264 RebootSP.apply();
265 LOG_INFO("Rebooting device (not implemented yet)...");
266 return true;
267 }
268
269 if (PI::processSwitch(dev, name, states, names, n))
270 return true;
271
272 if (AutoDewModeSP.isNameMatch(name))
273 {
274 AutoDewModeSP.update(states, names, n);
275 AutoDewModeSP.setState(IPS_OK);
276 AutoDewModeSP.apply();
277 return true;
278 }
279 }
280 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
281}
282
283bool CelestronDewPower::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
284{
285
286 if (dev && !strcmp(dev, getDefaultName()))
287 {
288 if (PI::processNumber(dev, name, values, names, n))
289 return true;
290
291 if (WI::processNumber(dev, name, values, names, n))
292 return true;
293
294 if (AutoDewTempNP.isNameMatch(name))
295 {
296 AutoDewTempNP.update(values, names, n);
297 AutoDewTempNP.setState(IPS_OK);
298 AutoDewTempNP.apply();
299 return true;
300 }
301 }
302 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
303}
304
305bool CelestronDewPower::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
306{
307 if (dev && !strcmp(dev, getDefaultName()))
308 {
309 if (PI::processText(dev, name, texts, names, n))
310 return true;
311 }
312 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
313}
314
322
326bool CelestronDewPower::processResponse(AUXCommand &m)
327{
328 m.logResponse();
329
330 if (m.destination() == APP)
331 {
332 switch (m.command())
333 {
335 // Version is handled in getDewPowerControllerVersion
336 break;
338 m_NumPorts = m.getData();
339 break;
341 // Handled in getInputPower
342 break;
344 // Handled in getPortInfo
345 break;
347 // Handled in getDewHeaterPortInfo
348 break;
350 // Handled by updateWeather()
351 break;
357 // These are "set" commands, no specific data to process in response beyond success/failure
358 break;
359 case PORTCTRL_NAK:
360 {
361 const AUXBuffer &data = m.getDataBuffer();
362 LOGF_DEBUG("Controller NAK'd command 0x%02X (not applicable to that port).",
363 data.empty() ? 0 : data[0]);
364 break;
365 }
366 default:
367 break;
368 }
369 }
370 else
371 {
372 DEBUGF(DBG_CAUX, "Got msg not for me (%s). Ignoring.", m.moduleName(m.destination()));
373 }
374 return true;
375}
376
380bool CelestronDewPower::serialReadResponse(AUXCommand &c)
381{
382 int n;
383 unsigned char buf[32];
384 char hexbuf[24];
385
386 // We are not connected. Nothing to do.
387 if ( PortFD <= 0 )
388 return false;
389
390 // if connected to AUX or PC ports, receive AUX command response.
391 // search for packet preamble (0x3b)
392 do
393 {
394 if (aux_tty_read((char * )buf, 1, READ_TIMEOUT, &n) != TTY_OK)
395 return false;
396 }
397 while (buf[0] != 0x3b);
398
399 // packet preamble is found, now read packet length.
400 if (aux_tty_read((char * )(buf + 1), 1, READ_TIMEOUT, &n) != TTY_OK)
401 return false;
402
403 // now packet length is known, read the rest of the packet.
404 if (aux_tty_read((char * )(buf + 2), buf[1] + 1, READ_TIMEOUT, &n)
405 != TTY_OK || n != buf[1] + 1)
406 {
407 LOG_DEBUG("Did not got whole packet. Dropping out.");
408 return false;
409 }
410
411 AUXBuffer b(buf, buf + (n + 2));
412 hex_dump(hexbuf, b, b.size());
413 DEBUGF(DBG_SERIAL, "RES <%s>", hexbuf);
414 c.parseBuf(b);
415
416 // Got the packet, process it
417 // n:length field >=3
418 // The buffer of n+2>=5 bytes contains:
419 // 0x3b <n>=3> <from> <to> <type> <n-3 bytes> <xsum>
420
421 DEBUGF(DBG_SERIAL, "Got %d bytes: ; payload length field: %d ; MSG:", n, buf[1]);
422 logBytes(buf, n + 2, getDeviceName(), DBG_SERIAL);
423 processResponse(c);
424 return true;
425}
426
430bool CelestronDewPower::readAUXResponse(AUXCommand &c)
431{
432 // Assuming only serial connection for Dew/Power controller
433 return serialReadResponse(c);
434}
435
439int CelestronDewPower::sendBuffer(AUXBuffer buf)
440{
441 if ( PortFD > 0 )
442 {
443 int n;
444
445 if (aux_tty_write((char * )buf.data(), buf.size(), &n) != TTY_OK)
446 return 0;
447
448 std::this_thread::sleep_for(std::chrono::milliseconds(50));
449 if (n == -1)
450 LOG_ERROR("CelestronDewPower::sendBuffer");
451 if ((unsigned)n != buf.size())
452 LOGF_WARN("sendBuffer: incomplete send n=%d size=%d", n, (int)buf.size());
453
454 char hexbuf[32 * 3] = {0};
455 hex_dump(hexbuf, buf, buf.size());
456 DEBUGF(DBG_SERIAL, "CMD <%s>", hexbuf);
457
458 return n;
459 }
460 else
461 return 0;
462}
463
467bool CelestronDewPower::sendAUXCommand(AUXCommand &command)
468{
469 AUXBuffer buf;
470 command.logCommand();
471
472 // For Dew/Power controller, assume direct connection (AUX/PC/USB port)
473 command.fillBuf(buf);
474
475 tcflush(PortFD, TCIOFLUSH);
476 return (sendBuffer(buf) == static_cast<int>(buf.size()));
477}
478
479
481// Wrap functions around the standard driver communication functions tty_read
482// and tty_write.
483//
484// This controller only ever communicates over a USB CDC-ACM virtual serial
485// port (/dev/ttyACM*), never a real RS-232 half-duplex AUX/PC port, so
486// unlike INDI's CelestronAUX telescope driver (which this AUX protocol was
487// adapted from) there is no hardware RTS/CTS flow control or byte-echo to
488// handle here: USB CDC-ACM modem-control lines are virtual and commonly
489// read back as permanently asserted regardless of real hardware state,
490// which previously caused a false-positive half-duplex detection here and
491// broke every connection. Plain full-duplex tty_read/tty_write is correct,
492// matching drivers/focuser/celestronauxpacket.cpp which talks to other
493// Celestron AUX-bus accessories the same way.
495bool CelestronDewPower::tty_set_speed(speed_t speed)
496{
497 struct termios tty_setting;
498
499 if (tcgetattr(PortFD, &tty_setting))
500 {
501 LOGF_ERROR("Error getting tty attributes %s(%d).", strerror(errno), errno);
502 return false;
503 }
504
505 if (cfsetspeed(&tty_setting, speed))
506 {
507 LOGF_ERROR("Error setting serial speed %s(%d).", strerror(errno), errno);
508 return false;
509 }
510
511 if (tcsetattr(PortFD, TCSANOW, &tty_setting))
512 {
513 LOGF_ERROR("Error setting tty attributes %s(%d).", strerror(errno), errno);
514 return false;
515 }
516
517 return true;
518}
519
523void CelestronDewPower::hex_dump(char *buf, AUXBuffer data, size_t size)
524{
525 for (size_t i = 0; i < size; i++)
526 sprintf(buf + 3 * i, "%02X ", data[i]);
527
528 if (size > 0)
529 buf[3 * size - 1] = '\0';
530}
531
532
536int CelestronDewPower::aux_tty_read(char *buf, int bufsiz, int timeout, int *n)
537{
538 int errcode;
539 DEBUGF(DBG_SERIAL, "aux_tty_read: %d", PortFD);
540
541 if((errcode = tty_read(PortFD, buf, bufsiz, timeout, n)) != TTY_OK)
542 {
543 char errmsg[MAXRBUF] = {0};
544 tty_error_msg(errcode, errmsg, MAXRBUF);
545 LOGF_ERROR("%s", errmsg);
546 }
547
548 return errcode;
549}
550
554int CelestronDewPower::aux_tty_write(char *buf, int bufsiz, int *n)
555{
556 int errcode;
557 char errmsg[MAXRBUF];
558
559 errcode = tty_write(PortFD, buf, bufsiz, n);
560
561 if (errcode != TTY_OK)
562 {
563 tty_error_msg(errcode, errmsg, MAXRBUF);
564 LOGF_ERROR("%s", errmsg);
565 return errcode;
566 }
567
568 return TTY_OK;
569}
570
571bool CelestronDewPower::getDewPowerControllerVersion()
572{
574 AUXCommand response;
575 if (sendAUXCommand(cmd) && readAUXResponse(response))
576 {
577 if (response.command() == PORTCTRL_GET_VERSION)
578 {
579 uint32_t version = response.getData();
580 LOGF_INFO("Celestron Dew/Power Controller Version: %d", version);
581 // Update firmware property if available
582 return true;
583 }
584 }
585 return false;
586}
587
588bool CelestronDewPower::getNumberOfPorts()
589{
591 AUXCommand response;
592 if (sendAUXCommand(cmd) && readAUXResponse(response))
593 {
594 if (response.command() == PORTCTRL_GET_NUMBER_OF_PORTS)
595 {
596 m_NumPorts = response.getData();
597 LOGF_INFO("Number of ports: %d", m_NumPorts);
598 return true;
599 }
600 }
601 return false;
602}
603
604bool CelestronDewPower::getInputPower()
605{
607 AUXCommand response;
608 if (sendAUXCommand(cmd) && readAUXResponse(response))
609 {
610 if (response.command() == PORTCTRL_GET_INPUT_POWER)
611 {
612 if (response.getDataBuffer() == lastInputPowerData)
613 return true;
614
615 // RESP: data[0:1] is voltage (mV), data[2:3] is current (mA), data[4] is 0, -1 or 1 if ok/under/over voltage, data[5] is 0/1 for overcurrent
616 const AUXBuffer &data = response.getDataBuffer();
617 if (data.size() >= 6)
618 {
619 uint16_t voltage_mV = (data[0] << 8) | data[1];
620 uint16_t current_mA = (data[2] << 8) | data[3];
621 int8_t voltageStatus = (int8_t)data[4];
622 uint8_t overcurrentStatus = data[5];
623
624 PI::PowerSensorsNP[PI::SENSOR_VOLTAGE].setValue(voltage_mV / 1000.0); // Convert mV to V
625 PI::PowerSensorsNP[PI::SENSOR_CURRENT].setValue(current_mA / 1000.0); // Convert mA to A
626 PI::PowerSensorsNP[PI::SENSOR_POWER].setValue((voltage_mV * current_mA) / 1000000.0); // P = V * I in Watts
627
630
631 // Update PowerStatusLP based on overcurrentStatus and voltageStatus
632 PowerStatusLP[POWER_STATUS_OVERCURRENT].setState((overcurrentStatus == 1) ? IPS_ALERT : IPS_OK);
633 PowerStatusLP[POWER_STATUS_UNDERVOLTAGE].setState((voltageStatus == -1) ? IPS_ALERT : IPS_OK);
634 PowerStatusLP[POWER_STATUS_OVERVOLTAGE].setState((voltageStatus == 1) ? IPS_ALERT : IPS_OK);
635 PowerStatusLP.apply();
636
637 lastInputPowerData = data;
638 return true;
639 }
640 }
641 }
642 return false;
643}
644
645bool CelestronDewPower::getPortInfo(uint8_t portNumber)
646{
648 cmd.setData(portNumber, 1); // Port Number is 1 byte
649 AUXCommand response;
650 if (sendAUXCommand(cmd) && readAUXResponse(response))
651 {
652 if (response.command() == PORTCTRL_GET_PORT_INFO)
653 {
654 // RESP: <0 type><1 enabled><2 isShorted><3:4 power(mW)><5:6 VoltageLevel (mV)>
655 // Only present in full when the port is actively drawing
656 // current; an idle port replies with just the 1-byte type.
657 const AUXBuffer &data = response.getDataBuffer();
658 if (data.size() >= 7)
659 {
660 // Update PI::PowerChannelsSP, PI::PowerChannelCurrentNP, OverCurrentLP
661 // This requires mapping portNumber to the correct INDI property index
662 // For now, just log
663 LOGF_DEBUG("Port %d Info: Type=%d, Enabled=%d, Shorted=%d, Power=%d mW, Voltage=%d mV",
664 portNumber, data[0], data[1], data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6]);
665 return true;
666 }
667 else if (!data.empty())
668 {
669 LOGF_DEBUG("Port %d Info: Type=%d (idle, no telemetry)", portNumber, data[0]);
670 return true;
671 }
672 }
673 }
674 return false;
675}
676
677bool CelestronDewPower::getDewHeaterPortInfo(uint8_t portNumber)
678{
680 cmd.setData(portNumber, 1); // Port Number is 1 byte
681 AUXCommand response;
682 if (sendAUXCommand(cmd) && readAUXResponse(response))
683 {
684 if (response.command() == PORTCTRL_GET_DH_PORT_INFO)
685 {
686 // RESP: <0 type><nmode (-1 == short detected, 0 == manual, 1 == auto_above_dp, 2 == auto_above_ambient)><2 power level><3:4 power(mW)><5 aggression level (C)><6:9 heaterTemp (if present)>
687 const AUXBuffer &data = response.getDataBuffer();
688 if (data.size() >= 6) // Minimum size without heaterTemp
689 {
690 // Update PI::DewChannelsSP, PI::DewChannelDutyCycleNP, PI::DewChannelCurrentNP, PI::AutoDewSP
691 // This requires mapping portNumber to the correct INDI property index
692 // For now, just log
693 LOGF_DEBUG("Dew Port %d Info: Type=%d, Mode=%d, PowerLevel=%d, Power=%d mW, Aggression=%d C",
694 portNumber, data[0], (int8_t)data[1], data[2], (data[3] << 8) | data[4], data[5]);
695 return true;
696 }
697 }
698 }
699 return false;
700}
701
702
703bool CelestronDewPower::setPortEnabled(uint8_t portNumber, bool enabled)
704{
706 AUXBuffer data;
707 data.push_back(portNumber);
708 data.push_back(enabled ? 1 : 0);
709 cmd.setDataBuffer(data);
710 return sendAUXCommand(cmd);
711}
712
713bool CelestronDewPower::setPortVoltage(uint8_t portNumber, uint16_t voltage_mV)
714{
716 AUXBuffer data;
717 data.push_back(portNumber);
718 data.push_back((voltage_mV >> 8) & 0xFF);
719 data.push_back(voltage_mV & 0xFF);
720 cmd.setDataBuffer(data);
721 return sendAUXCommand(cmd);
722}
723
724bool CelestronDewPower::setDewHeaterAuto(uint8_t portNumber, uint8_t mode, uint8_t temp_C)
725{
727 AUXBuffer data;
728 data.push_back(portNumber);
729 data.push_back(mode);
730 data.push_back(temp_C);
731 cmd.setDataBuffer(data);
732 return sendAUXCommand(cmd);
733}
734
735bool CelestronDewPower::setDewHeaterManual(uint8_t portNumber, uint8_t powerLevel)
736{
738 AUXBuffer data;
739 data.push_back(portNumber);
740 data.push_back(powerLevel);
741 cmd.setDataBuffer(data);
742 return sendAUXCommand(cmd);
743}
744
745bool CelestronDewPower::setLEDBrightness(uint8_t brightness)
746{
748 cmd.setData(brightness, 1);
749 return sendAUXCommand(cmd);
750}
751
752// INDI::PowerInterface overrides
753bool CelestronDewPower::SetPowerPort(size_t port, bool enabled)
754{
755 return setPortEnabled(static_cast<uint8_t>(port), enabled);
756}
757
758bool CelestronDewPower::SetDewPort(size_t port, bool enabled, double dutyCycle)
759{
760 // Convert duty cycle percentage (0-100) to 0-255 range
761 auto powerLevel = static_cast<uint8_t>(dutyCycle / 100.0 * 255.0);
762 return setDewHeaterManual(static_cast<uint8_t>(port), enabled ? powerLevel : 0);
763}
764
765bool CelestronDewPower::SetVariablePort(size_t port, bool enabled, double voltage)
766{
767 INDI_UNUSED(port); // Assuming only one variable port for now
768 uint16_t voltage_mV = static_cast<uint16_t>(voltage * 1000.0);
769 return setPortVoltage(static_cast<uint8_t>(port), enabled ? voltage_mV : 0);
770}
771
773{
774 return setLEDBrightness(enabled ? 255 : 0); // Full brightness or off
775}
776
777bool CelestronDewPower::SetAutoDewEnabled(size_t port, bool enabled)
778{
779 if (enabled)
780 {
781 uint8_t mode = AutoDewModeSP.findOnSwitchIndex();
782 uint8_t temp_C = static_cast<uint8_t>(AutoDewTempNP[0].getValue());
783 return setDewHeaterAuto(static_cast<uint8_t>(port), mode, temp_C);
784 }
785 else
786 {
787 // To disable auto dew, we set it to manual mode with 0 power
788 return setDewHeaterManual(static_cast<uint8_t>(port), 0);
789 }
790}
double humidity
void logBytes(unsigned char *buf, int n, const char *deviceName, uint32_t debugLevel)
std::vector< uint8_t > AUXBuffer
@ PORTCTRL_SET_LED_BRIGHTNESS
@ PORTCTRL_SET_PORT_VOLTAGE
@ PORTCTRL_SET_PORT_ENABLED
@ PORTCTRL_DH_ENABLE_MANUAL
@ PORTCTRL_GET_NUMBER_OF_PORTS
@ PORTCTRL_GET_DH_PORT_INFO
AUXCommands command() const
const AUXBuffer & getDataBuffer() const
uint32_t getData()
Set and Get data.
void parseBuf(AUXBuffer buf)
const char * moduleName(AUXTargets n)
static void setDebugInfo(const char *deviceName, uint8_t debugLevel)
void fillBuf(AUXBuffer &buf)
Buffer Management.
const AUXTargets & destination() const
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool SetVariablePort(size_t port, bool enabled, double voltage) override
SetVariablePort Set variable voltage port.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool SetAutoDewEnabled(size_t port, bool enabled) override
SetAutoDewEnabled Enable/disable automatic dew control.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool SetLEDEnabled(bool enabled) override
SetLEDEnabled Enable/disable power LEDs.
virtual bool SetDewPort(size_t port, bool enabled, double dutyCycle) override
SetDewPort Set DEW/Dew port duty cycle.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual const char * getDefaultName() override
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
virtual bool SetPowerPort(size_t port, bool enabled) override
SetPowerPort Set power port on/off.
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
bool isConnected() const
const char * getDeviceName() const
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
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.
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.
void addDebugControl()
Add Debug control to the driver.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
bool saveConfigItems(FILE *fp)
saveConfigItems Save power properties in config file
void SetCapability(uint32_t cap)
SetPowerCapability sets the Power capabilities. All capabilities must be initialized.
INDI::PropertyNumber PowerSensorsNP
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process Power number properties.
void initProperties(const char *groupName, size_t nPowerPorts=0, size_t nDewPorts=0, size_t nVariablePorts=0, size_t nAutoDewPorts=0, size_t nUSBPorts=0)
Initialize Power properties. It is recommended to call this function within initProperties() of your ...
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process Power text properties.
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process Power switch properties.
bool updateProperties()
updateProperties Define or Delete Power properties based on the connection status of the base device
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPState state)
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 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....
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process weather number properties.
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save parameters ranges in the config file.
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...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
int errno
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
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_ATMOST1
Definition indiapi.h:172
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define 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
#define LOG_INFO(txt)
Definition indilogger.h:74
#define DEBUGF(priority, msg,...)
Definition indilogger.h:57
Namespace to encapsulate INDI client, drivers, and mediator classes.
uint8_t cmd[4]
Definition pwc-ioctl.h:2
static Logger & getInstance()
Method to get a reference to the object (i.e., Singleton) It is a static method.
int addDebugLevel(const char *debugLevelName, const char *LoggingLevelName)
Adds a new debugging level to the driver.
struct termios tty_setting
Definition stvdriver.c:51