Instrument Neutral Distributed Interface INDI 2.2.5
pegasus_flatmaster_neo.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2026 Dominik Merkel. All rights reserved.
3
4 Pegasus FlatMaster Neo
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
26
27#include "indicom.h"
29
30#include <cerrno>
31#include <cstring>
32#include <memory>
33#include <regex>
34#include <termios.h>
35#include <unistd.h>
36#include <inttypes.h>
37#include <sys/ioctl.h>
38#include <math.h>
39
40
41static std::unique_ptr<PegasusFlatMasterNeo> flatmaster(new PegasusFlatMasterNeo());
42
43
45{
46 setVersion(1, 2);
47}
48
50{
52
53 // Firmware version
54 IUFillText(&FirmwareT[0], "Version", "Version", nullptr);
55 IUFillTextVector(&FirmwareTP, FirmwareT, 1, getDeviceName(), "Firmware", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
56
57 LI::initProperties(LIGHTBOX_TAB, LI::CAN_DIM);
59 WI::initProperties(ENVIRONMENT_TAB, ENVIRONMENT_TAB);
60
62
63 addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -15, 35, 15);
64 addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15);
65 addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15);
66 setCriticalParameter("WEATHER_TEMPERATURE");
67
68 WeatherOffsetNP[OFFSET_TEMPERATURE].fill("OFFSET_TEMPERATURE", "Temperature Offset (C)", "%.1f", -20, 20, 0.1, 0);
69 WeatherOffsetNP[OFFSET_HUMIDITY].fill( "OFFSET_HUMIDITY", "Humidity Offset (%)", "%.1f", -20, 20, 0.1, 0);
70 WeatherOffsetNP.fill(getDeviceName(), "WEATHER_OFFSET", "Calibration", ENVIRONMENT_TAB, IP_RW, 60, IPS_IDLE);
71
72 LightIntensityNP[0].setMin(0);
73 LightIntensityNP[0].setMax(100);
74 LightIntensityNP[0].setStep(1);
75
76 // Dew Control
77 DewHeaterNP[0].fill("DEW_HEATER_VALUE", "Heater (%)", "%.0f", 0, 100, 1, 0);
78 DewHeaterNP.fill(getDeviceName(), "DEW_HEATER", "Dew Heater", DEW_TAB, IP_RW, 60, IPS_IDLE);
79
80 AutoDewSP[0].fill("AUTO_DEW_ENABLE", "Enable", ISS_OFF);
81 AutoDewSP[1].fill("AUTO_DEW_DISABLE", "Disable", ISS_ON);
82 AutoDewSP.fill(getDeviceName(), "AUTO_DEW", "Auto Dew", DEW_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
83
84 // Cap opening angle
85 CapAngleNP[0].fill("CAP_ANGLE_VALUE", "Angle (°)", "%.0f", 0, 270, 1, 270);
86 CapAngleNP.fill(getDeviceName(), "CAP_ANGLE", "Opening Angle", DUSTCAP_TAB, IP_RW, 60, IPS_IDLE);
87
88 // Device overview (FA)
89 DeviceStatusNP[STATUS_LIGHT_INTENSITY].fill( "STATUS_LIGHT_INTENSITY", "Light Intensity", "%.0f", 0, 100, 1, 0);
90 DeviceStatusNP[STATUS_LIGHT_ACTIVE].fill( "STATUS_LIGHT_ACTIVE", "Light Active", "%.0f", 0, 1, 1, 0);
91 DeviceStatusNP[STATUS_CAP_ACTUAL_ANGLE].fill("STATUS_CAP_ACTUAL", "Cap Actual Angle (°)", "%.0f", 0, 270, 1, 0);
92 DeviceStatusNP[STATUS_CAP_TARGET_ANGLE].fill("STATUS_CAP_TARGET", "Cap Target Angle (°)", "%.0f", 0, 270, 1, 0);
93 DeviceStatusNP[STATUS_CAP_STATUS].fill( "STATUS_CAP_STATUS", "Cap Status", "%.0f", 0, 9999, 1, 0);
94 DeviceStatusNP[STATUS_DEW_POWER].fill( "STATUS_DEW_POWER", "Dew Power", "%.0f", 0, 100, 1, 0);
95 DeviceStatusNP[STATUS_AUTO_DEW].fill( "STATUS_AUTO_DEW", "Auto Dew Active", "%.0f", 0, 1, 1, 0);
96 DeviceStatusNP[STATUS_DEW_AGGRESSIVE].fill( "STATUS_DEW_AGGRESSIVE", "Dew Aggressiveness (0-10)", "%.0f", 0, 10, 1, 0);
97 DeviceStatusNP[STATUS_LIGHT_SENSOR].fill( "STATUS_LIGHT_SENSOR", "Light Sensor", "%.0f", 0, 100, 1, 0);
98 DeviceStatusNP.fill(getDeviceName(), "DEVICE_STATUS", "Device Status", STATUS_TAB, IP_RO, 60, IPS_IDLE);
99
100 // Uptime (FU)
101 UptimeNP[0].fill("UPTIME_SECONDS", "Uptime (s)", "%.0f", 0, 1e9, 1, 0);
102 UptimeNP.fill(getDeviceName(), "UPTIME", "Uptime", STATUS_TAB, IP_RO, 60, IPS_IDLE);
103
104 // Reboot (FQ)
105 RebootSP[0].fill("REBOOT", "Reboot", ISS_OFF);
106 RebootSP.fill(getDeviceName(), "DEVICE_REBOOT", "Device", STATUS_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
107
108 // Dew aggressiveness logical level 0..10 → DA:<level> + PD:<mapped_byte>
109 DewAggressivenessNP[0].fill("DEW_AGGRESSIVENESS_VALUE", "Aggressiveness (0-10)", "%.0f", 0, 10, 1, 5);
110 DewAggressivenessNP.fill(getDeviceName(), "DEW_AGGRESSIVENESS", "Dew Aggressiveness", DEW_TAB, IP_RW, 60, IPS_IDLE);
111
112 // Dew threshold (DT:1..50)
113 ThresholdNP[0].fill("THRESHOLD_VALUE", "Threshold (1-50)", "%.0f", 1, 50, 1, 10);
114 ThresholdNP.fill(getDeviceName(), "DEW_THRESHOLD", "Dew Threshold", DEW_TAB, IP_RW, 60, IPS_IDLE);
115
116 // WiFi channel — AC (read) / AC:<channel> (write), valid 1..11
117 WifiChannelNP[0].fill("WIFI_CHANNEL_VALUE", "Channel (1-11)", "%.0f", 1, 11, 1, 6);
118 WifiChannelNP.fill(getDeviceName(), "WIFI_CHANNEL", "WiFi Channel", NETWORK_TAB, IP_RW, 60, IPS_IDLE);
119
120 // Save dew levels (DSTR)
121 SaveDewSP[0].fill("SAVE_DEW_LEVELS", "Save", ISS_OFF);
122 SaveDewSP.fill(getDeviceName(), "DEW_SAVE", "Dew Settings", DEW_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
123
124 // Auto-close (DC:99 / DC:0 / DC:1)
125 AutoCloseSP[0].fill("AUTO_CLOSE_ENABLE", "Enable", ISS_OFF);
126 AutoCloseSP[1].fill("AUTO_CLOSE_DISABLE", "Disable", ISS_ON);
127 AutoCloseSP.fill(getDeviceName(), "AUTO_CLOSE", "Auto Close", DUSTCAP_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
128
129 // Hotspot status (A? / AE:0 / AE:1)
130 HotspotStatusSP[0].fill("HOTSPOT_ENABLE", "Enable", ISS_OFF);
131 HotspotStatusSP[1].fill("HOTSPOT_DISABLE", "Disable", ISS_ON);
132 HotspotStatusSP.fill(getDeviceName(), "HOTSPOT_STATUS", "Hotspot", NETWORK_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
133
134 // Hotspot credentials (AL / AN:<ssid> / AP:<password>)
135 HotspotCredentialsTP[0].fill("HOTSPOT_SSID", "SSID", nullptr);
136 HotspotCredentialsTP[1].fill("HOTSPOT_PASSWORD", "Password", nullptr);
137 HotspotCredentialsTP.fill(getDeviceName(), "HOTSPOT_CREDENTIALS", "Hotspot Credentials", NETWORK_TAB, IP_RW, 60, IPS_IDLE);
138
139 // Current WiFi connection (WI)
140 CurrentWifiTP[0].fill("CURRENT_WIFI", "Connection", nullptr);
141 CurrentWifiTP.fill(getDeviceName(), "CURRENT_WIFI", "Current WiFi", NETWORK_TAB, IP_RO, 60, IPS_IDLE);
142
143 // WiFi network scan (WS — 15 s timeout)
144 WifiScanSP[0].fill("WIFI_SCAN", "Scan", ISS_OFF);
145 WifiScanSP.fill(getDeviceName(), "WIFI_SCAN", "Scan Networks", NETWORK_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
146 WifiScanTP[0].fill("WIFI_SCAN_RESULTS", "Networks", nullptr);
147 WifiScanTP.fill(getDeviceName(), "WIFI_SCAN_RESULTS", "Scan Results", NETWORK_TAB, IP_RO, 60, IPS_IDLE);
148
149 // WiFi connect (WN:<ssid> / WP:<password>)
150 WifiConnectTP[0].fill("WIFI_SSID", "SSID", nullptr);
151 WifiConnectTP[1].fill("WIFI_PASSWORD", "Password", nullptr);
152 WifiConnectTP.fill(getDeviceName(), "WIFI_CONNECT_CREDS", "WiFi Credentials", NETWORK_TAB, IP_RW, 60, IPS_IDLE);
153 WifiConnectSP[0].fill("WIFI_CONNECT", "Connect", ISS_OFF);
154 WifiConnectSP.fill(getDeviceName(), "WIFI_CONNECT", "Connect to WiFi", NETWORK_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
155
156 // WiFi factory reset (WZ)
157 WifiFactoryResetSP[0].fill("WIFI_FACTORY_RESET", "Reset", ISS_OFF);
158 WifiFactoryResetSP.fill(getDeviceName(), "WIFI_FACTORY_RESET", "WiFi Factory Reset", NETWORK_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
159
161
162 serialConnection = new Connection::Serial(this);
164 serialConnection->setPortMatchPattern("FlatMaster");
165 serialConnection->registerHandshake([&]()
166 {
167 return Ack();
168 });
169
170
171 registerConnection(serialConnection);
172 return true;
173}
174
176{
178
179 // Get Light box properties
181
182}
183
185{
187
188 if (isConnected())
189 {
190 defineProperty(&FirmwareTP);
191 defineProperty(WeatherOffsetNP);
192 defineProperty(DewHeaterNP);
193 defineProperty(AutoDewSP);
194 defineProperty(DewAggressivenessNP);
195 defineProperty(ThresholdNP);
196 defineProperty(SaveDewSP);
197 defineProperty(AutoCloseSP);
198 defineProperty(CapAngleNP);
199 defineProperty(DeviceStatusNP);
200 defineProperty(UptimeNP);
201 defineProperty(RebootSP);
202 defineProperty(WifiChannelNP);
203 defineProperty(HotspotStatusSP);
204 defineProperty(HotspotCredentialsTP);
205 defineProperty(CurrentWifiTP);
206 defineProperty(WifiScanSP);
207 defineProperty(WifiScanTP);
208 defineProperty(WifiConnectTP);
209 defineProperty(WifiConnectSP);
210 defineProperty(WifiFactoryResetSP);
211 getWeatherOffsets();
212 getThreshold();
213 getAutoClose();
214 getWifiChannel();
215 getHotspotStatus();
216 getHotspot();
217 getCurrentWifi();
218 }
219 else
220 {
221 deleteProperty(FirmwareTP.name);
222 deleteProperty(WeatherOffsetNP);
223 deleteProperty(DewHeaterNP);
224 deleteProperty(AutoDewSP);
225 deleteProperty(DewAggressivenessNP);
226 deleteProperty(ThresholdNP);
227 deleteProperty(SaveDewSP);
228 deleteProperty(AutoCloseSP);
229 deleteProperty(CapAngleNP);
230 deleteProperty(DeviceStatusNP);
231 deleteProperty(UptimeNP);
232 deleteProperty(RebootSP);
233 deleteProperty(WifiChannelNP);
234 deleteProperty(HotspotStatusSP);
235 deleteProperty(HotspotCredentialsTP);
236 deleteProperty(CurrentWifiTP);
237 deleteProperty(WifiScanSP);
238 deleteProperty(WifiScanTP);
239 deleteProperty(WifiConnectTP);
240 deleteProperty(WifiConnectSP);
241 deleteProperty(WifiFactoryResetSP);
242 }
243
247
248 // Fetch current device state immediately after all properties are defined
249 // so the UI reflects the actual hardware state on connection.
250 if (isConnected())
251 getStatusData();
252
253 return true;
254}
255
257{
258 return "Pegasus FlatMaster Neo";
259}
260
261void PegasusFlatMasterNeo::updateFirmwareVersion()
262{
263 char response[16] = {0};
264
265 if(sendCommand("FV", response))
266 {
267 IUSaveText(&FirmwareT[0], response + 3);
268 FirmwareTP.s = IPS_OK;
269 IDSetText(&FirmwareTP, nullptr);
270 }
271 else
272 {
273 FirmwareTP.s = IPS_ALERT;
274 LOG_ERROR("Error on updateFirmware.");
275 }
276}
277
278bool PegasusFlatMasterNeo::Ack()
279{
280 PortFD = serialConnection->getPortFD();
281
282 char response[16] = {0};
283 if (sendCommand("F#", response))
284 {
285 if (strstr(response, "FMNEO") != nullptr)
286 {
287 updateFirmwareVersion();
288 return true;
289 }
290 }
291 LOGF_ERROR("Ack failed: unexpected response '%s'", response);
292 return false;
293}
294
296{
297 char response[16] = {0};
298 char cmd[16] = {0};
299
300 snprintf(cmd, 16, "FE:%d", enable ? 1 : 0);
301
302 if (!sendCommand(cmd, response))
303 {
304 LOGF_ERROR("EnableLightBox: send failed. %s", response);
305 return false;
306 }
307
308 if (strstr(response, "FE:") == nullptr)
309 {
310 LOGF_ERROR("EnableLightBox: unexpected response '%s'", response);
311 return false;
312 }
313
314 return true;
315}
316
318{
319 char response[16] = {0};
320 char cmd[16] = {0};
321
322 snprintf(cmd, 16, "FL:%d", value);
323
324 if (!sendCommand(cmd, response))
325 {
326 LOGF_ERROR("SetLightBoxBrightness: send failed. %s", response);
327 return false;
328 }
329
330 if (strstr(response, "FL:") == nullptr)
331 {
332 LOGF_ERROR("SetLightBoxBrightness: unexpected response '%s'", response);
333 return false;
334 }
335
336 return true;
337}
338
339bool PegasusFlatMasterNeo::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
340{
341 if (LI::processNumber(dev, name, values, names, n))
342 return true;
343
344 if (strstr(name, "WEATHER_"))
345 {
346 if (WI::processNumber(dev, name, values, names, n))
347 return true;
348 }
349
350 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
351 {
352 if (DewHeaterNP.isNameMatch(name))
353 {
354 if (setDewHeater(static_cast<uint8_t>(values[0])))
355 {
356 DewHeaterNP.update(values, names, n);
357 DewHeaterNP.setState(IPS_OK);
358 }
359 else
360 DewHeaterNP.setState(IPS_ALERT);
361 DewHeaterNP.apply();
362 return true;
363 }
364
365 if (CapAngleNP.isNameMatch(name))
366 {
367 if (setCapAngle(static_cast<uint16_t>(values[0])))
368 {
369 CapAngleNP.update(values, names, n);
370 CapAngleNP.setState(IPS_OK);
371 }
372 else
373 CapAngleNP.setState(IPS_ALERT);
374 CapAngleNP.apply();
375 return true;
376 }
377
378 if (WeatherOffsetNP.isNameMatch(name))
379 {
380 bool ok = true;
381 for (int i = 0; i < n; i++)
382 {
383 if (strcmp(names[i], "OFFSET_TEMPERATURE") == 0)
384 ok &= setTemperatureOffset(values[i]);
385 else if (strcmp(names[i], "OFFSET_HUMIDITY") == 0)
386 ok &= setHumidityOffset(values[i]);
387 }
388 if (ok)
389 {
390 WeatherOffsetNP.update(values, names, n);
391 WeatherOffsetNP.setState(IPS_OK);
392 }
393 else
394 WeatherOffsetNP.setState(IPS_ALERT);
395 WeatherOffsetNP.apply();
396 return true;
397 }
398
399 if (DewAggressivenessNP.isNameMatch(name))
400 {
401 if (setDewAggressiveness(static_cast<uint8_t>(values[0])))
402 {
403 DewAggressivenessNP.update(values, names, n);
404 DewAggressivenessNP.setState(IPS_OK);
405 }
406 else
407 DewAggressivenessNP.setState(IPS_ALERT);
408 DewAggressivenessNP.apply();
409 return true;
410 }
411
412 if (ThresholdNP.isNameMatch(name))
413 {
414 if (setThreshold(static_cast<uint8_t>(values[0])))
415 {
416 ThresholdNP.update(values, names, n);
417 ThresholdNP.setState(IPS_OK);
418 }
419 else
420 ThresholdNP.setState(IPS_ALERT);
421 ThresholdNP.apply();
422 return true;
423 }
424
425 if (WifiChannelNP.isNameMatch(name))
426 {
427 if (setWifiChannel(static_cast<uint8_t>(values[0])))
428 {
429 WifiChannelNP.update(values, names, n);
430 WifiChannelNP.setState(IPS_OK);
431 }
432 else
433 WifiChannelNP.setState(IPS_ALERT);
434 WifiChannelNP.apply();
435 return true;
436 }
437 }
438
439 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
440}
441
442bool PegasusFlatMasterNeo::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
443{
444 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
445 {
446 if (LI::processText(dev, name, texts, names, n))
447 return true;
448
449 if (HotspotCredentialsTP.isNameMatch(name))
450 {
451 HotspotCredentialsTP.update(texts, names, n);
452 const char *ssid = HotspotCredentialsTP[0].getText();
453 const char *pwd = HotspotCredentialsTP[1].getText();
454 if (ssid && pwd && setHotspot(ssid, pwd))
455 HotspotCredentialsTP.setState(IPS_OK);
456 else
457 HotspotCredentialsTP.setState(IPS_ALERT);
458 HotspotCredentialsTP.apply();
459 return true;
460 }
461
462 if (WifiConnectTP.isNameMatch(name))
463 {
464 WifiConnectTP.update(texts, names, n);
465 WifiConnectTP.setState(IPS_OK);
466 WifiConnectTP.apply();
467 return true;
468 }
469 }
470
471 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
472}
473
474bool PegasusFlatMasterNeo::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
475{
476 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
477 {
478 if (DI::processSwitch(dev, name, states, names, n))
479 return true;
480
481 if (LI::processSwitch(dev, name, states, names, n))
482 return true;
483
484 if (WI::processSwitch(dev, name, states, names, n))
485 return true;
486
487 if (AutoDewSP.isNameMatch(name))
488 {
489 AutoDewSP.update(states, names, n);
490 bool enable = AutoDewSP[0].getState() == ISS_ON;
491 if (setAutoDew(enable))
492 AutoDewSP.setState(IPS_OK);
493 else
494 AutoDewSP.setState(IPS_ALERT);
495 AutoDewSP.apply();
496 return true;
497 }
498
499 if (RebootSP.isNameMatch(name))
500 {
501 char response[16] = {0};
502 sendCommand("FQ", response);
503 RebootSP.setState(IPS_OK);
504 RebootSP.apply();
505 LOG_INFO("Reboot command sent to device.");
506 return true;
507 }
508
509 if (SaveDewSP.isNameMatch(name))
510 {
511 if (saveDewLevels())
512 SaveDewSP.setState(IPS_OK);
513 else
514 SaveDewSP.setState(IPS_ALERT);
515 SaveDewSP.apply();
516 return true;
517 }
518
519 if (AutoCloseSP.isNameMatch(name))
520 {
521 AutoCloseSP.update(states, names, n);
522 bool enable = AutoCloseSP[0].getState() == ISS_ON;
523 if (setAutoClose(enable))
524 AutoCloseSP.setState(IPS_OK);
525 else
526 AutoCloseSP.setState(IPS_ALERT);
527 AutoCloseSP.apply();
528 return true;
529 }
530
531 if (HotspotStatusSP.isNameMatch(name))
532 {
533 HotspotStatusSP.update(states, names, n);
534 bool enable = HotspotStatusSP[0].getState() == ISS_ON;
535 if (setHotspotStatus(enable))
536 HotspotStatusSP.setState(IPS_OK);
537 else
538 HotspotStatusSP.setState(IPS_ALERT);
539 HotspotStatusSP.apply();
540 return true;
541 }
542
543 if (WifiScanSP.isNameMatch(name))
544 {
545 if (scanWifiNetworks())
546 WifiScanSP.setState(IPS_OK);
547 else
548 WifiScanSP.setState(IPS_ALERT);
549 WifiScanSP.apply();
550 return true;
551 }
552
553 if (WifiConnectSP.isNameMatch(name))
554 {
555 const char *ssid = WifiConnectTP[0].getText();
556 const char *pwd = WifiConnectTP[1].getText();
557 if (ssid && connectToWifi(ssid, pwd))
558 {
559 WifiConnectSP.setState(IPS_OK);
560 getCurrentWifi();
561 }
562 else
563 WifiConnectSP.setState(IPS_ALERT);
564 WifiConnectSP.apply();
565 return true;
566 }
567
568 if (WifiFactoryResetSP.isNameMatch(name))
569 {
570 wifiFactoryReset();
571 WifiFactoryResetSP.setState(IPS_OK);
572 WifiFactoryResetSP.apply();
573 LOG_INFO("WiFi factory reset sent to device.");
574 return true;
575 }
576 }
577
578 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
579}
580
582{
583 LI::snoop(root);
584
586}
587
589{
593 WeatherOffsetNP.save(fp);
594 DewHeaterNP.save(fp);
595 AutoDewSP.save(fp);
596 DewAggressivenessNP.save(fp);
597 ThresholdNP.save(fp);
598 CapAngleNP.save(fp);
599 WifiChannelNP.save(fp);
600 return true;
601}
602
603bool PegasusFlatMasterNeo::sendCommand(const char *command, char *res, int timeout_sec)
604{
605 int nbytes_written = 0, nbytes_read = 0, rc = -1;
606 char errstr[MAXRBUF];
607
608 char cmd[32] = {0};
609 snprintf(cmd, sizeof(cmd), "%s\n", command);
610
611 LOGF_DEBUG("CMD <%s>", command);
612
613 tcflush(PortFD, TCIOFLUSH);
614
615 if ((rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
616 {
617 tty_error_msg(rc, errstr, MAXRBUF);
618 LOGF_ERROR("command: %s error: %s.", cmd, errstr);
619 return false;
620 }
621
622 if ((rc = tty_read_section(PortFD, res, 0xA, timeout_sec, &nbytes_read)) != TTY_OK)
623 {
624 tty_error_msg(rc, errstr, MAXRBUF);
625 LOGF_ERROR("command: %s error: %s.", cmd, errstr);
626 return false;
627 }
628
629 // Strip trailing newline and carriage return
630 res[nbytes_read - 1] = 0;
631 if (nbytes_read > 1 && res[nbytes_read - 2] == '\r')
632 res[nbytes_read - 2] = 0;
633
634 LOGF_DEBUG("RES <%s>", res);
635
636 tcflush(PortFD, TCIOFLUSH);
637
638 return true;
639}
640
642{
643 char response[16] = {0};
644
645 if (!sendCommand("FS:0", response))
646 {
647 LOGF_ERROR("Error on ParkCap. %s", response);
648 return IPS_ALERT;
649 }
650
651 if (strstr(response, "FS:") == nullptr)
652 return IPS_ALERT;
653
654 // Command accepted — cap is now moving; TimerHit will finalize the state
655 return IPS_BUSY;
656}
657
659{
660 char response[16] = {0};
661
662 if (!sendCommand("FS:1", response))
663 {
664 LOGF_ERROR("Error on UnParkCap. %s", response);
665 return IPS_ALERT;
666 }
667
668 if (strstr(response, "FS:") == nullptr)
669 return IPS_ALERT;
670
671 // Command accepted — cap is now moving; TimerHit will finalize the state
672 return IPS_BUSY;
673}
674
676{
677 char response[16] = {0};
678 if (sendCommand("FH", response) && strstr(response, "FH:1") != nullptr)
679 return IPS_OK;
680 return IPS_ALERT;
681}
682
683bool PegasusFlatMasterNeo::setDewHeater(uint8_t value)
684{
685 char cmd[16] = {0};
686 char response[16] = {0};
687 snprintf(cmd, 16, "DH:%d", value);
688 if (sendCommand(cmd, response))
689 return strstr(response, "DH:") != nullptr;
690 return false;
691}
692
693bool PegasusFlatMasterNeo::setAutoDew(bool enable)
694{
695 char cmd[16] = {0};
696 char response[16] = {0};
697 snprintf(cmd, 16, "PD:%d", enable ? 1 : 0);
698
699 if (!sendCommand(cmd, response) || strstr(response, "PD:") == nullptr)
700 return false;
701
702 if (!getStatusData())
703 return false;
704
705 const bool actual = (DeviceStatusNP[STATUS_AUTO_DEW].getValue() == 1);
706 if (actual != enable)
707 {
708 LOGF_ERROR("AutoDew %s failed: device reports %s.", enable ? "enable" : "disable", actual ? "enabled" : "disabled");
709 return false;
710 }
711
712 return true;
713}
714
715bool PegasusFlatMasterNeo::setCapAngle(uint16_t angle)
716{
717 char cmd[16] = {0};
718 char response[16] = {0};
719 snprintf(cmd, 16, "CD:%d", angle);
720 if (sendCommand(cmd, response))
721 return strstr(response, "CD:") != nullptr;
722 return false;
723}
724
726{
727 if (!isConnected())
728 {
730 return;
731 }
732
733 getStatusData();
734 getWeatherData();
735 getUptime();
736 getCurrentWifi();
737
739}
740
741bool PegasusFlatMasterNeo::getStatusData()
742{
743 char response[NEO_LEN] = {0};
744
745 if (!sendCommand("FA", response))
746 {
747 LOG_ERROR("Error querying device status.");
748 return false;
749 }
750
751 // FA response: "FMNEO:lightIntensity:lightActive:capActual:capTarget:capStatus:dewPower:autoDew:dewAggressive:lightSensor"
752 std::vector<std::string> result = split(response, ":");
753
754 if (result.size() < FA_N)
755 {
756 LOGF_WARN("Received unexpected FA status format: %s", response);
757 return false;
758 }
759
760 // Skip update if nothing changed, but always re-evaluate when a park/unpark is in progress
761 // to ensure IPS_BUSY is resolved even if the cap was already at the target position.
762 if (result == lastStatusData && ParkCapSP.getState() != IPS_BUSY)
763 return true;
764
765 try
766 {
767 const double lightIntensity = std::stod(result[FA_LIGHT_INTENSITY]);
768 const double lightActive = std::stod(result[FA_LIGHT_ACTIVE]);
769 const double capTargetAngle = std::stod(result[FA_CAP_TARGET_ANGLE]);
770 const double capActualAngle = std::stod(result[FA_CAP_ACTUAL_ANGLE]);
771 const double capStatus = std::stod(result[FA_CAP_STATUS]);
772 const double dewPower = std::stod(result[FA_DEW_POWER]);
773 const double autoDew = std::stod(result[FA_AUTO_DEW_STATUS]);
774 const double dewAggressive = std::stod(result[FA_DEW_AGGRESSIVE]);
775 const double lightSensor = std::stod(result[FA_LIGHT_SENSOR]);
776
777
778 // --- Overview tab ---
779 DeviceStatusNP[STATUS_LIGHT_INTENSITY].setValue(lightIntensity);
780 DeviceStatusNP[STATUS_LIGHT_ACTIVE].setValue(lightActive);
781 DeviceStatusNP[STATUS_CAP_TARGET_ANGLE].setValue(capTargetAngle);
782 DeviceStatusNP[STATUS_CAP_ACTUAL_ANGLE].setValue(capActualAngle);
783 DeviceStatusNP[STATUS_CAP_STATUS].setValue(capStatus);
784 DeviceStatusNP[STATUS_DEW_POWER].setValue(dewPower);
785 DeviceStatusNP[STATUS_AUTO_DEW].setValue(autoDew);
786 DeviceStatusNP[STATUS_DEW_AGGRESSIVE].setValue(dewAggressive);
787 DeviceStatusNP[STATUS_LIGHT_SENSOR].setValue(lightSensor);
788 DeviceStatusNP.setState(IPS_OK);
789 DeviceStatusNP.apply();
790
791 // --- LightBox interface ---
792 const bool lightOn = (lightActive != 0);
793 if (LightSP[FLAT_LIGHT_ON].getState() != (lightOn ? ISS_ON : ISS_OFF))
794 {
795 LightSP.reset();
798 LightSP.apply();
799 }
800 if (LightIntensityNP[0].getValue() != lightIntensity)
801 {
802 LightIntensityNP[0].setValue(lightIntensity);
805 }
806
807 // --- DustCap interface ---
808 // Any other value (e.g. in-motion) leaves IPS_BUSY in place until the
809 // final state arrives; the switch selection is not changed mid-motion.
810 if (capStatus == CAP_STATUS_PARKED)
811 {
812 if (ParkCapSP[CAP_PARK].getState() != ISS_ON || ParkCapSP.getState() != IPS_OK)
813 {
818 }
819 }
820 else if (capStatus == CAP_STATUS_UNPARKED)
821 {
822 if (ParkCapSP[CAP_UNPARK].getState() != ISS_ON || ParkCapSP.getState() != IPS_OK)
823 {
828 }
829 }
830 if (CapAngleNP[0].getValue() != capTargetAngle)
831 {
832 CapAngleNP[0].setValue(capTargetAngle);
833 CapAngleNP.setState(IPS_OK);
834 CapAngleNP.apply();
835 }
836 }
837 catch (const std::exception &e)
838 {
839 LOGF_ERROR("Error parsing FA status data: %s", e.what());
840 return false;
841 }
842
843 lastStatusData = result;
844 return true;
845}
846
847bool PegasusFlatMasterNeo::getWeatherOffsets()
848{
849 char response[NEO_LEN] = {0};
850
851 if (!sendCommand("CR", response))
852 {
853 LOG_ERROR("Error querying weather offsets.");
854 return false;
855 }
856
857 // CR response: "CR:temp_offset:humidity_offset"
858 std::vector<std::string> result = split(response, ":");
859
860 if (result.size() < CR_N)
861 {
862 LOGF_WARN("Received unexpected CR offset format: %s", response);
863 return false;
864 }
865
866 try
867 {
868 WeatherOffsetNP[OFFSET_TEMPERATURE].setValue(std::stod(result[CR_TEMP_OFFSET]));
869 WeatherOffsetNP[OFFSET_HUMIDITY].setValue(std::stod(result[CR_HUM_OFFSET]));
870 WeatherOffsetNP.setState(IPS_OK);
871 WeatherOffsetNP.apply();
872 }
873 catch (const std::exception &e)
874 {
875 LOGF_ERROR("Error parsing CR offset data: %s", e.what());
876 return false;
877 }
878
879 return true;
880}
881
882bool PegasusFlatMasterNeo::setTemperatureOffset(double value)
883{
884 char cmd[16] = {0};
885 char response[16] = {0};
886 snprintf(cmd, 16, "CT:%.1f", value);
887 if (sendCommand(cmd, response))
888 return strstr(response, "CT:") != nullptr;
889 return false;
890}
891
892bool PegasusFlatMasterNeo::setHumidityOffset(double value)
893{
894 char cmd[16] = {0};
895 char response[16] = {0};
896 snprintf(cmd, 16, "CH:%.1f", value);
897 if (sendCommand(cmd, response))
898 return strstr(response, "CH:") != nullptr;
899 return false;
900}
901
902bool PegasusFlatMasterNeo::getWeatherData()
903{
904 char response[NEO_LEN] = {0};
905
906 if (!sendCommand("ES", response))
907 {
908 LOG_ERROR("Error querying weather data.");
909 return false;
910 }
911
912 // ES response: "FlatMasterNeo:temperature:humidity:dewpoint"
913 // Field separator is ':' — adjust if device uses a different delimiter
914 std::vector<std::string> result = split(response, ":");
915
916 if (result.size() < WI_N)
917 {
918 LOGF_WARN("Received unexpected weather data format: %s", response);
919 return false;
920 }
921
922 if (result == lastWeatherData)
923 return true;
924
925 try
926 {
927 setParameterValue("WEATHER_TEMPERATURE", std::stod(result[WI_TEMPERATURE]));
928 setParameterValue("WEATHER_HUMIDITY", std::stod(result[WI_HUMIDITY]));
929 setParameterValue("WEATHER_DEWPOINT", std::stod(result[WI_DEWPOINT]));
930 }
931 catch (const std::exception &e)
932 {
933 LOGF_ERROR("Error parsing weather data: %s", e.what());
934 return false;
935 }
936
939
942
943 lastWeatherData = result;
944 return true;
945}
946
947bool PegasusFlatMasterNeo::getUptime()
948{
949 char response[16] = {0};
950 if (!sendCommand("FU", response))
951 return false;
952
953 // Response is either a plain integer or FU:<seconds>
954 const char *val = strstr(response, "FU:");
955 UptimeNP[0].setValue(atof(val ? val + 3 : response));
956 UptimeNP.setState(IPS_OK);
957 UptimeNP.apply();
958 return true;
959}
960
961bool PegasusFlatMasterNeo::getThreshold()
962{
963 char response[16] = {0};
964 if (!sendCommand("DT", response))
965 return false;
966
967 const char *val = strstr(response, "DT:");
968 if (!val)
969 return false;
970
971 int threshold = atoi(val + 3);
972 if (threshold < 1 || threshold > 50)
973 {
974 LOGF_WARN("DT returned %d which is outside the valid range [1..50], ignoring.", threshold);
975 return false;
976 }
977
978 ThresholdNP[0].setValue(threshold);
979 ThresholdNP.setState(IPS_OK);
980 ThresholdNP.apply();
981 return true;
982}
983
984bool PegasusFlatMasterNeo::setThreshold(uint8_t value)
985{
986 if (value < 1 || value > 50)
987 {
988 LOGF_ERROR("Threshold %d is outside the valid range [1..50].", value);
989 return false;
990 }
991 char cmd[16] = {0};
992 char response[16] = {0};
993 snprintf(cmd, sizeof(cmd), "DT:%d", value);
994 if (sendCommand(cmd, response))
995 return strstr(response, "DT:") != nullptr;
996 return false;
997}
998
999// level 0..10 (logical) → DA:<level> (cache) + PD:<mapped_byte> (apply)
1000// mapped_byte = 9 + round(level * 246 / 10), covering byte range 9..255
1001bool PegasusFlatMasterNeo::setDewAggressiveness(uint8_t level)
1002{
1003 char cmd[16] = {0};
1004 char response[16] = {0};
1005
1006 snprintf(cmd, sizeof(cmd), "DA:%d", level);
1007 if (!sendCommand(cmd, response) || strstr(response, "DA:") == nullptr)
1008 return false;
1009
1010 const uint8_t mapped = static_cast<uint8_t>(9 + static_cast<int>(round(level * 246.0 / 10.0)));
1011 char cmd2[16] = {0};
1012 char response2[16] = {0};
1013 snprintf(cmd2, sizeof(cmd2), "PD:%d", mapped);
1014 if (!sendCommand(cmd2, response2) || strstr(response2, "PD:") == nullptr)
1015 return false;
1016
1017 return true;
1018}
1019
1020bool PegasusFlatMasterNeo::saveDewLevels()
1021{
1022 char response[16] = {0};
1023 sendCommand("DSTR", response);
1024 return true;
1025}
1026
1027bool PegasusFlatMasterNeo::getWifiChannel()
1028{
1029 char response[16] = {0};
1030 if (!sendCommand("AC", response))
1031 return false;
1032
1033 int channel = atoi(response);
1034 if (channel < 1 || channel > 11)
1035 return false;
1036
1037 WifiChannelNP[0].setValue(channel);
1038 WifiChannelNP.setState(IPS_OK);
1039 WifiChannelNP.apply();
1040 return true;
1041}
1042
1043bool PegasusFlatMasterNeo::setWifiChannel(uint8_t channel)
1044{
1045 char cmd[16] = {0};
1046 char response[16] = {0};
1047 snprintf(cmd, sizeof(cmd), "AC:%d", channel);
1048 if (sendCommand(cmd, response))
1049 return atoi(response) != 0;
1050 return false;
1051}
1052
1053bool PegasusFlatMasterNeo::getAutoClose()
1054{
1055 char response[16] = {0};
1056 if (!sendCommand("DC:99", response))
1057 return false;
1058
1059 const bool on = (strstr(response, "DC:1") != nullptr);
1060 AutoCloseSP.reset();
1061 AutoCloseSP[on ? 0 : 1].setState(ISS_ON);
1062 AutoCloseSP.setState(IPS_OK);
1063 AutoCloseSP.apply();
1064 return true;
1065}
1066
1067bool PegasusFlatMasterNeo::setAutoClose(bool enable)
1068{
1069 char cmd[16] = {0};
1070 char response[16] = {0};
1071 snprintf(cmd, sizeof(cmd), "DC:%d", enable ? 1 : 0);
1072 if (!sendCommand(cmd, response))
1073 return false;
1074 return strstr(response, enable ? "DC:1" : "DC:0") != nullptr;
1075}
1076
1077bool PegasusFlatMasterNeo::getHotspotStatus()
1078{
1079 char response[16] = {0};
1080 if (!sendCommand("A?", response))
1081 return false;
1082
1083 const bool on = (response[0] == '1');
1084 HotspotStatusSP.reset();
1085 HotspotStatusSP[on ? 0 : 1].setState(ISS_ON);
1086 HotspotStatusSP.setState(IPS_OK);
1087 HotspotStatusSP.apply();
1088 return true;
1089}
1090
1091bool PegasusFlatMasterNeo::setHotspotStatus(bool enable)
1092{
1093 char cmd[16] = {0};
1094 char response[16] = {0};
1095 snprintf(cmd, sizeof(cmd), "AE:%d", enable ? 1 : 0);
1096 sendCommand(cmd, response);
1097 return getHotspotStatus();
1098}
1099
1100bool PegasusFlatMasterNeo::getHotspot()
1101{
1102 char response[NEO_LEN] = {0};
1103 if (!sendCommand("AL", response))
1104 return false;
1105
1106 // Response: <ssid>:<password>
1107 std::vector<std::string> parts = split(response, ":");
1108 if (parts.size() < 2)
1109 return false;
1110
1111 HotspotCredentialsTP[0].setText(parts[0].c_str());
1112 HotspotCredentialsTP[1].setText(parts[1].c_str());
1113 HotspotCredentialsTP.setState(IPS_OK);
1114 HotspotCredentialsTP.apply();
1115 return true;
1116}
1117
1118bool PegasusFlatMasterNeo::setHotspot(const char *ssid, const char *password)
1119{
1120 char cmd[NEO_LEN] = {0};
1121 char response[16] = {0};
1122
1123 snprintf(cmd, sizeof(cmd), "AN:%s", ssid);
1124 if (!sendCommand(cmd, response) || strstr(response, "AN:") == nullptr)
1125 return false;
1126
1127 snprintf(cmd, sizeof(cmd), "AP:%s", password);
1128 if (!sendCommand(cmd, response) || strstr(response, "AP:") == nullptr)
1129 return false;
1130
1131 return true;
1132}
1133
1134bool PegasusFlatMasterNeo::getCurrentWifi()
1135{
1136 char response[NEO_LEN] = {0};
1137 if (!sendCommand("WI", response))
1138 return false;
1139
1140 if (strcmp(response, "0") == 0)
1141 {
1142 CurrentWifiTP[0].setText("Disconnected");
1143 }
1144 else
1145 {
1146 CurrentWifiTP[0].setText(response);
1147 }
1148 CurrentWifiTP.setState(IPS_OK);
1149 CurrentWifiTP.apply();
1150 return true;
1151}
1152
1153bool PegasusFlatMasterNeo::scanWifiNetworks()
1154{
1155 char response[512] = {0};
1156 // WS has up to 15 second scan timeout
1157 if (!sendCommand("WS", response, 16))
1158 return false;
1159
1160 if (strcmp(response, "0") == 0)
1161 WifiScanTP[0].setText("No networks found");
1162 else
1163 WifiScanTP[0].setText(response);
1164
1165 WifiScanTP.setState(IPS_OK);
1166 WifiScanTP.apply();
1167 return true;
1168}
1169
1170bool PegasusFlatMasterNeo::connectToWifi(const char *ssid, const char *password)
1171{
1172 if (!ssid || ssid[0] == '\0')
1173 {
1174 LOG_ERROR("WiFi SSID cannot be empty.");
1175 return false;
1176 }
1177
1178 char cmd[NEO_LEN] = {0};
1179 char response[16] = {0};
1180
1181 snprintf(cmd, sizeof(cmd), "WN:%s", ssid);
1182 if (!sendCommand(cmd, response) || strstr(response, "WN:") == nullptr)
1183 return false;
1184
1185 // Empty password: send "PASSWORD" placeholder; otherwise min 8 chars
1186 if (!password || password[0] == '\0')
1187 {
1188 if (!sendCommand("WP:PASSWORD", response) || strstr(response, "WP:") == nullptr)
1189 return false;
1190 }
1191 else
1192 {
1193 if (strlen(password) < 8)
1194 {
1195 LOG_ERROR("WiFi password must be at least 8 characters.");
1196 return false;
1197 }
1198 snprintf(cmd, sizeof(cmd), "WP:%s", password);
1199 if (!sendCommand(cmd, response) || strstr(response, "WP:") == nullptr)
1200 return false;
1201 }
1202
1203 return true;
1204}
1205
1206bool PegasusFlatMasterNeo::wifiFactoryReset()
1207{
1208 char response[16] = {0};
1209 // WZ sends without waiting for a response (Channel.Write behavior)
1210 tty_write(PortFD, "WZ\n", 3, nullptr);
1211 (void)response;
1212 return true;
1213}
1214
1215std::vector<std::string> PegasusFlatMasterNeo::split(const std::string &input, const std::string &sep)
1216{
1217 std::vector<std::string> result;
1218 std::regex re(sep);
1219 std::sregex_token_iterator it(input.begin(), input.end(), re, -1);
1220 std::sregex_token_iterator end;
1221 for (; it != end; ++it)
1222 result.push_back(it->str());
1223 return result;
1224}
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 ...
void setPortMatchPattern(const std::string &pattern)
setPortMatchPattern Set a regex pattern used to automatically select a serial port from the discovere...
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
Provides interface to implement remotely controlled dust cover.
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....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
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 ParkCapSP
bool updateProperties()
updateProperties Defines or Delete properties based on default device connection status
void initProperties(const char *group, uint32_t capabilities=0)
Initialize dust cap properties. It is recommended to call this function within initProperties() of yo...
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process dust cap switch properties.
void initProperties(const char *group, uint32_t capabilities)
Initialize light box properties. It is recommended to call this function within initProperties() of y...
void ISGetProperties(const char *deviceName)
isGetLightBoxProperties Get light box properties
INDI::PropertyNumber LightIntensityNP
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process light box switch properties.
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process light box text properties.
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process light box number properties.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
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)
bool update(const char *const texts[], 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)
INDI::PropertyViewText * getText() const
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 processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process weather switch properties.
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
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.
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
Provides interface to implement controllable light box/switch device.
virtual bool SetLightBoxBrightness(uint16_t value) override
setBrightness Set light level. Must be implemented in the child class, if supported.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
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 IPState UnParkCap() override
unPark dust cap (open cover). Must be implemented by child.
virtual IPState ParkCap() override
Park dust cap (close cover). Must be implemented by child.
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
virtual IPState AbortCap() override
Abort motion. Must be implemented by child.
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 EnableLightBox(bool enable) override
EnableLightBox Turn on/off on a light box. Must be implemented in the child class.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
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_BUSY
Definition indiapi.h:161
@ 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_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:653
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
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
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:509
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition indidevapi.c:44
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition indidevapi.c:416
#define MAXRBUF
Definition indidriver.c:52
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
#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
uint8_t cmd[4]
Definition pwc-ioctl.h:2
char name[MAXINDINAME]
Definition indiapi.h:248
double round(double value, int decimal_places)