Instrument Neutral Distributed Interface INDI 2.2.5
cheapodc.cpp
Go to the documentation of this file.
1/*
2 CheapoDC - Dew Controller
3 https://github.com/hcomet/CheapoDC
4
5 Copyright (C) 2024 Stephen Hillier
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
21*/
22
23#include "cheapodc.h"
26#include "indicom.h"
27#include <cstring>
28#include <string>
29#include <memory>
30
31#include <termios.h>
32#include <unistd.h>
33
34#ifdef _USE_SYSTEM_JSONLIB
35#include <nlohmann/json.hpp>
36#else
37#include <indijson.hpp>
38#endif
39
41
42std::unique_ptr<CheapoDC> cheapodc(new CheapoDC());
43
48
50{
51 DefaultDevice::initProperties();
52
54
56
57 /* Minimum Output Power */
58 MinimumOutputNP[0].fill("MINIMUMOUTPUT", "Power (%)", "%3.0f", 0, 99, 1, prevMinOutput);
59 MinimumOutputNP.fill(getDeviceName(), "MINIMUMOUTPUT", "Controller Min", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
60
61 /* Maximum Output Power */
62 MaximumOutputNP[0].fill("MAXIMUMOUTPUT", "Power (%)", "%3.0f", 1, 100, 1, prevMaxOutput);
63 MaximumOutputNP.fill(getDeviceName(), "MAXIMUMOUTPUT", "Controller Max", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
64
65 XtrnTemperatureNP[WEATHER_QUERY].fill("WEATHERQUERY", "Weather Source (\u2103)", "%3.2f", -50., 120., 0., 0.);
66 XtrnTemperatureNP[EXTERNAL_INPUT].fill("EXTERNALINPUT", "Focuser Device (\u2103)", "%3.2f", -50., 120., 0., 0.);
67 XtrnTemperatureNP.fill(getDeviceName(), "TEMPERATURE", "Temperature", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
68
69 HumidityNP[0].fill("HUMIDITY", "Relative (%)", "%3.0f", 0., 100., 0., 0.);
70 HumidityNP.fill(getDeviceName(), "HUMIDITY", "Humidity", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
71
72 /* Dew point */
73 DewpointNP[0].fill("DEWPOINT", "(\u2103)", "%3.2f", -50., 120., 0., 0.);
74 DewpointNP.fill(getDeviceName(), "DEWPOINT", "Dew point", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
75
76 /* Set Point Temperature */
77 SetPointTemperatureNP[0].fill("SETPOINT", "Set Point (\u2103)", "%3.2f", -50., 120., 0., 0.);
78 SetPointTemperatureNP.fill(getDeviceName(), "SETPOINT", "Temperature", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
79
80 /* Track Point Offset */
81 TrackPointOffsetNP[0].fill("TRACKPOINTOFFSET", "-5.0 to 5.0 (\u2103)", "%2.1f", -5., 5., 0.5, 0.);
82 TrackPointOffsetNP.fill(getDeviceName(), "TRACKPOINTOFFSET", "Track Point Offset", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
83
84 /* Tracking Range */
85 TrackingRangeNP[0].fill("TRACKINGRANGE", "4.0 to 10.0 (\u2103)", "%2.1f", 4, 10, .5, 5.0);
86 TrackingRangeNP.fill(getDeviceName(), "TRACKINGRANGE", "Tracking Range", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
87
88 /* Temperature mode */
89 TemperatureModeSP[WEATHER_QUERY].fill("WEATHER_QUERY", "Weather Source", ISS_ON);
90 TemperatureModeSP[EXTERNAL_INPUT].fill("EXTERNAL_INPUT", "Focuser Device", ISS_OFF);
91 TemperatureModeSP.fill(getDeviceName(), "TEMPERATURE_MODE", "Temperature Mode", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
92
93 /* Set Point mode */
94 SetPointModeSP[DEWPOINT].fill("DEWPOINT", "Dew Point", ISS_ON);
95 SetPointModeSP[TEMPERATURE].fill("TEMPERATURE", "Temperature", ISS_OFF);
96 SetPointModeSP[MIDPOINT].fill("MIDPOINT", "Midpoint", ISS_OFF);
97 SetPointModeSP.fill(getDeviceName(), "SETPOINT_MODE", "Set Point Mode", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
98
99 /* Update Output Every 1 to 20 minutes */
100 UpdateOutputEveryNP[0].fill("UPDATE_PERIOD", "Period (min)", "%2.0f", 1, 20, 1, 1);
101 UpdateOutputEveryNP.fill(getDeviceName(), "UPDATE_OUTPUT", "Update Output", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
102
103 /* Query Weather Every 0 to 20 minutes */
104 QueryWeatherEveryNP[0].fill("UPDATE_PERIOD", "Period (min)", "%2.0f", 0, 20, 1, 5);
105 QueryWeatherEveryNP.fill(getDeviceName(), "QUERY_WEATHER", "Query Weather", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
106
107 /* Firmware version */
108 FWversionTP[0].fill("FIRMWARE", "Firmware Version", nullptr);
109 FWversionTP.fill(getDeviceName(), "FW_VERSION", "Device", CONNECTION_TAB, IP_RO, 0, IPS_IDLE);
110
111 /* Set Weather Source */
112 WeatherSourceSP[OPENMETEO].fill("OPENMETEO", "Open-Meteo", ISS_ON);
113 WeatherSourceSP[OPENWEATHER].fill("OPENWEATHER", "OpenWeather", ISS_OFF);
114 WeatherSourceSP[EXTERNALSOURCE].fill("EXTERNALSOURCE", "Weather Device", ISS_OFF);
115 WeatherSourceSP[INTERNALSOURCE].fill("INTERNALSOURCE", "CheapoDC Sensor", ISS_OFF);
116 WeatherSourceSP.fill(getDeviceName(), "WEATHER_SOURCE", "Weather Source", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
117
118 /* Weather Query API Key*/
119 WeatherQueryAPIKeyTP[0].fill("API_KEY", "OpenWeather API Key", nullptr);
120 WeatherQueryAPIKeyTP.fill(getDeviceName(), "WEATHER_API_KEY", "Weather API Key", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
121
122 /* Weather Updated */
123 WeatherUpdatedTP[0].fill("LAST_UPDATED", "Last Updated", nullptr);
124 WeatherUpdatedTP.fill(getDeviceName(), "WEATHER_UPDATED", "Weather", OPTIONS_TAB, IP_RO, 0, IPS_IDLE);
125
126 /* Location coordinates */
127 LocationNP[LOCATION_LATITUDE].fill(CDC_SNOOP_LOCATION_LATITUDE, "Lat (dd:mm:ss)", "%010.6m", -90, 90, 0, 0.0);
128 LocationNP[LOCATION_LONGITUDE].fill(CDC_SNOOP_LOCATION_LONGITUDE, "Lon (dd:mm:ss)", "%010.6m", 0, 360, 0, 0.0);
129 LocationNP.fill(getDeviceName(), CDC_SNOOP_LOCATION_PROPERTY, "Location", SITE_TAB, IP_RW, 60, IPS_IDLE);
130
131 /* Device Time */
132 DeviceTimeTP[LOCAL_TIME].fill("CDC_DEVICE_TIME", "Local Time", nullptr);
133 DeviceTimeTP[UTC_OFFSET].fill("UTC_OFFSET", "UTC Offset (hours)", nullptr);
134 DeviceTimeTP.fill(getDeviceName(), "DEVICE_TIME", "Device Time", SITE_TAB, IP_RO, 0, IPS_IDLE);
135
136
137 /* Active Devices */
138 ActiveDeviceTP[ACTIVE_TELESCOPE].fill("ACTIVE_TELESCOPE", "Telescope", activeTelescopeDevice);
139 ActiveDeviceTP[ACTIVE_FOCUSER].fill("ACTIVE_FOCUSER", "Focuser", activeFocuserDevice);
140 ActiveDeviceTP[ACTIVE_WEATHER].fill("ACTIVE_WEATHER", "Weather", activeWeatherDevice);
141 ActiveDeviceTP.fill(getDeviceName(), "ACTIVE_DEVICES", "Snoop Devices", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
142 ActiveDeviceTP.load();
143
144 // Refresh
145 RefreshSP[0].fill("REFRESH", "Refresh", ISS_OFF);
146 RefreshSP.fill(getDeviceName(), "CHEAPODC_REFRESH", "CheapoDC", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
147
148 // setDriverInterface(AUX_INTERFACE); // Moved above
149
154
155 // No simulation control for now
156
157 if (cdcConnection & CONNECTION_TCP)
158 {
159 tcpConnection = new Connection::TCP(this);
160 tcpConnection->setDefaultHost(CDC_DEFAULT_HOST);
161 tcpConnection->setDefaultPort(CDC_DEFAULT_PORT);
162 tcpConnection->registerHandshake([&]()
163 { return Handshake(); });
164
165 registerConnection(tcpConnection);
166 }
167
168 return true;
169}
170
172{
173 DefaultDevice::updateProperties();
175
176 if (isConnected())
177 {
178 // Enable Snoop Devices
179 indi_strlcpy(activeTelescopeDevice, ActiveDeviceTP[ACTIVE_TELESCOPE].getText(), MAXINDINAME);
180 if (strlen(activeTelescopeDevice) > 0)
181 {
182 IDSnoopDevice(activeTelescopeDevice, CDC_SNOOP_LOCATION_PROPERTY);
183 if (!fwVOneDetected)
184 IDSnoopDevice(activeTelescopeDevice, CDC_SNOOP_TIME_PROPERTY);
185 }
186
187 indi_strlcpy(activeFocuserDevice, ActiveDeviceTP[ACTIVE_FOCUSER].getText(), MAXINDINAME);
188 if (strlen(activeFocuserDevice) > 0)
189 IDSnoopDevice(activeFocuserDevice, CDC_SNOOP_FOCUSER_PROPERTY);
190
191 // If fw V1 then blank out the activeWeatherDevice since it is not supported
192 if (fwVOneDetected)
193 ActiveDeviceTP[ACTIVE_WEATHER].setText("");
194
195 indi_strlcpy(activeWeatherDevice, ActiveDeviceTP[ACTIVE_WEATHER].getText(), MAXINDINAME);
196 if (strlen(activeWeatherDevice) > 0)
197 IDSnoopDevice(activeWeatherDevice, CDC_SNOOP_WEATHER_PROPERTY);
198
199 // Main Control Tab
200 defineProperty(WeatherSourceSP);
201 defineProperty(TemperatureModeSP);
202 defineProperty(XtrnTemperatureNP);
203 defineProperty(SetPointModeSP);
204 defineProperty(HumidityNP);
205 defineProperty(DewpointNP);
206 defineProperty(SetPointTemperatureNP);
207 defineProperty(MinimumOutputNP);
208 defineProperty(MaximumOutputNP);
209 defineProperty(TrackPointOffsetNP);
210 defineProperty(TrackingRangeNP);
211 defineProperty(RefreshSP);
212 // Options Tab
213 defineProperty(UpdateOutputEveryNP);
214 defineProperty(QueryWeatherEveryNP);
215 if (fwVOneDetected) // Set Weather Source selection to RO
216 {
217 WeatherSourceSP.setPermission(IP_RO);
218 }
219 defineProperty(WeatherQueryAPIKeyTP);
220 defineProperty(WeatherUpdatedTP);
221 defineProperty(ActiveDeviceTP);
222
223 // Site Tab
224 defineProperty(LocationNP);
225 if (!fwVOneDetected)
226 defineProperty(DeviceTimeTP);
227
228 // Connection Tab
229 defineProperty(FWversionTP);
230
231 readSettings();
232 if (fwVOneDetected)
233 {
234 LOG_WARN("Go to https://github.com/hcomet/CheapoDC/releases to download the latest firmware release");
235 LOGF_WARN("CheapoDC firmware V%s detected. Please upgrade firmware to latest V2+.", FWversionTP[0].getText());
236 }
237 timerIndex = SetTimer(getCurrentPollingPeriod());
238 }
239 else
240 {
241 deleteProperty(WeatherSourceSP);
242 deleteProperty(MinimumOutputNP);
243 deleteProperty(MaximumOutputNP);
244 deleteProperty(TemperatureModeSP);
245 deleteProperty(SetPointModeSP);
246 deleteProperty(XtrnTemperatureNP);
247 deleteProperty(HumidityNP);
248 deleteProperty(DewpointNP);
249 deleteProperty(SetPointTemperatureNP);
250 deleteProperty(RefreshSP);
251 deleteProperty(TrackPointOffsetNP);
252 deleteProperty(TrackingRangeNP);
253 deleteProperty(UpdateOutputEveryNP);
254 deleteProperty(QueryWeatherEveryNP);
255 deleteProperty(WeatherQueryAPIKeyTP);
256 deleteProperty(WeatherUpdatedTP);
257 deleteProperty(ActiveDeviceTP);
258
259 // Site Tab
260 deleteProperty(LocationNP);
261 if (!fwVOneDetected)
262 deleteProperty(DeviceTimeTP);
263
264 // Connection Tab
265 deleteProperty(FWversionTP);
266 }
267
268 return true;
269}
270
272{
273 return "CheapoDC";
274}
275
276// sleep for a number of milliseconds
277int CheapoDC::msleep(long duration)
278{
279 struct timespec ts;
280 int res;
281
282 if (duration < 0)
283 {
284 errno = EINVAL;
285 return -1;
286 }
287
288 ts.tv_sec = duration / 1000;
289 ts.tv_nsec = (duration % 1000) * 1000000;
290
291 do
292 {
293 res = nanosleep(&ts, &ts);
294 } while (res && errno == EINTR);
295
296 return res;
297}
298
299bool CheapoDC::sendCommand(const char *cmd, char *resp)
300{
301 int nbytes_written = 0, nbytes_read = 0, rc = -1;
302 char errstr[MAXRBUF];
303
304 LOGF_DEBUG("CMD: [%s]", cmd);
305 tcflush(PortFD, TCIOFLUSH);
306 if ((rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK)
307 {
308 tty_error_msg(rc, errstr, MAXRBUF);
309 LOGF_ERROR("Error writing command %s: %s.", cmd, errstr);
310 return false;
311 }
312
313 // Small delay to allow controller to process command
314 msleep(CDC_SMALL_DELAY);
315
316 if (resp)
317 {
318 if ((rc = tty_nread_section(PortFD, resp, CDC_RESPONSE_LENGTH, '\n', CDC_READ_TIMEOUT, &nbytes_read)) != TTY_OK)
319 {
320 tty_error_msg(rc, errstr, MAXRBUF);
321 LOGF_ERROR("Error reading response for command [%s]: %s.", cmd, errstr);
322 return false;
323 }
324
325 if (nbytes_read < 2)
326 {
327 LOGF_ERROR("Invalid response [%s] for command [%s].", resp, cmd);
328 return false;
329 }
330 }
331
332 return true;
333}
334
335bool CheapoDC::sendGetCommand(const char *cmd, char *resp)
336{
337 char getResponse[CDC_RESPONSE_LENGTH] = {};
338 char getCommand[CDC_COMMAND_LENGTH] = {};
339
340 snprintf(getCommand, CDC_COMMAND_LENGTH, CDC_GET_COMMAND, cmd);
341
342 if (!this->sendCommand(getCommand, getResponse))
343 return false;
344 else
345 {
346 std::string getResponseST(getResponse, strlen(getResponse));
347 try
348 {
349 json jsonResponse = json::parse(getResponseST);
350
351 try
352 {
353 std::string jsonValue;
354 jsonResponse[cmd].get_to(jsonValue);
355 strcpy(resp, jsonValue.c_str());
356 }
357 catch (const std::exception &e)
358 {
359 try
360 {
361 int errorCode;
362 jsonResponse["RESULT"].get_to(errorCode);
363 LOGF_DEBUG("GET command %s not supported.", cmd);
364 return false;
365 }
366 catch(const std::exception& e)
367 {
368 LOGF_ERROR("Error parsing GET %s response for value: %s Error: %s", cmd, getResponse, e.what());
369 return false;
370 }
371
372 }
373 }
374 catch (json::exception &e)
375 {
376 // output exception information
377 LOGF_ERROR("Error parsing GET %s response %s Error: %s", cmd, getResponseST.c_str(), e.what());
378 return false;
379 }
380 }
381 return true;
382}
383
384bool CheapoDC::sendSetCommand(const char *cmd, char *value)
385{
386 char setResponse[CDC_RESPONSE_LENGTH] = {};
387 char setCommand[CDC_COMMAND_LENGTH] = {};
388 int result = -1;
389
390 snprintf(setCommand, CDC_COMMAND_LENGTH, CDC_SET_COMMAND, cmd, value);
391
392 if (this->sendCommand(setCommand, setResponse))
393 {
394 std::string setResponseST(setResponse, strlen(setResponse));
395 try
396 {
397 json jsonResponse = json::parse(setResponseST);
398
399 try
400 {
401 jsonResponse["RESULT"].get_to(result);
402 }
403 catch (const std::exception &e)
404 {
405 LOGF_ERROR("Error parsing SET %s response for RESULT: %s Error: %s", cmd, setResponse, e.what());
406 return false;
407 }
408 }
409 catch (json::exception &e)
410 {
411 // output exception information
412 LOGF_ERROR("Error parsing SET %s response %s Error: %s", cmd, setResponse, e.what());
413 return false;
414 }
415 }
416 return (result == 0);
417}
418
419bool CheapoDC::Handshake()
420{
421 if (getActiveConnection() == tcpConnection)
422 {
423 PortFD = tcpConnection->getPortFD();
424 }
425 else
426 {
427 return false;
428 LOG_ERROR("CheapoDC is not connected.");
429 }
430
431 int tries = 2;
432 do
433 {
434 if (Ack())
435 {
436 //LOG_INFO("CheapoDC is online. Getting device parameters...");
437 return true;
438 }
439 LOG_ERROR("Error retrieving data from CheapoDC, retrying...");
440 } while (--tries > 0);
441
442 LOG_ERROR("Error retrieving data from CheapoDC, please ensure controller "
443 "is powered and the port is correct.");
444
445 return false;
446}
447
448bool CheapoDC::Ack()
449{
450 char resp[CDC_RESPONSE_LENGTH] = {};
451
452 fwVOneDetected = false;
453 additionalOutputsSupported = false;
454 internalHumiditySensorSupported = false;
455
456 memset(resp, '\0', sizeof(resp));
457
458 if (!this->sendGetCommand(CDC_CMD_FW, resp))
459 return false;
460
461 FWversionTP[0].setText(resp);
462 FWversionTP.setState(IPS_OK);
463 FWversionTP.apply();
464 //LOG_INFO("CheapoDC ACK.");
465 //LOG_INFO(resp);
466
467 if (!sscanf(resp, "%d.%d.%d", &fwMajorVersion, &fwMinorVersion, &fwPatchVersion))
468 {
469 LOGF_ERROR("Parsing Firmware Version: Response (%s) for Command (%s) not valid.", resp, CDC_CMD_FW);
470 return false;
471 }
472
473 if (fwMajorVersion == 1)
474 {
475 fwVOneDetected = true;
476 }
477 else
478 {
479 // Addtional outputs supported in release 2.20 FW
480 if ((fwMajorVersion > 2) || (fwMinorVersion >= 2)) // 2.2.0+
481 {
482 additionalOutputsSupported = true;
483 }
484
485 // Internal Humidity Sensor supported in release 2.3.0 FW
486 if ((fwMajorVersion > 2) || (fwMinorVersion >= 3)) // 2.3.0+
487 {
488 internalHumiditySensorSupported = false;
489
490 // Test to see if the internal sensor is configured
491 memset(resp, '\0', sizeof(resp));
492 if (this->sendGetCommand(CDC_CMD_SDAP, resp))
493 {
494 int internalSDAPin;
495
496 if (sscanf(resp, "%d", &internalSDAPin) == 1)
497 {
498 if (internalSDAPin >= 0)
499 {
500 internalHumiditySensorSupported = true;
501 }
502 }
503 }
504 }
505 }
506
507 return checkOutputConfiguration();
508}
509
510// Check available outputs and configure INDI:PowerInterface properties
511bool CheapoDC::checkOutputConfiguration()
512{
513 int dewOutputs = 0;
514 int powerOutputs = 0;
515 char resp[CDC_RESPONSE_LENGTH] = {};
516 char command[CDC_COMMAND_LENGTH] = {};
517 bool useDewLabels = false;
518 bool usePowerLabels = false;
519
520 if (additionalOutputsSupported)
521 {
522 // Calculate number of Dew and Power outputs
523 for (int i = 0; i < CDC_TOTAL_ADDITIONAL_OUTPUTS; i++)
524 {
525 int mode;
526 int pin = i + CDC_MIN_ADDITIONAL_OUTPUT;
527
528 memset(resp, '\0', sizeof(resp));
529 memset(command, '\0', sizeof(command));
530
531 snprintf(command, CDC_COMMAND_LENGTH, CDC_CMD_CPM, pin);
532 if (!this->sendGetCommand(command, resp))
533 return false;
534
535 if (sscanf(resp, "%d", &mode)) {
536
537 switch (mode)
538 {
539 case CONTROLLER_PIN_MODE_DISABLED:
540 // Do nothing since we don't show Disabled Outputs
541 break;
542 case CONTROLLER_PIN_MODE_CONTROLLER:
543 dewOutputs++;
544 break;
545 case CONTROLLER_PIN_MODE_PWM:
546 dewOutputs++;
547 break;
548 case CONTROLLER_PIN_MODE_BOOLEAN:
549 powerOutputs++;
550 break;
551 default:
552 LOGF_ERROR("Get Output Mode Pin %d: Response [%s] for Command [%s] not valid.", pin, resp, command);
553 }
554
555 lastControllerPinMode[i] = mode;
556
557 }
558 else
559 LOGF_ERROR("Get Output Mode Pin %d: Response [%s] for Command [%s] not valid.", pin, resp, command);
560
561 }
562 }
563
564 //LOGF_INFO("CheapoDC Additional Outputs: Dew Outputs: %d, Power Outputs: %d", dewOutputs, powerOutputs);
565
566 PI::initProperties(DEW_TAB, powerOutputs, dewOutputs+1, 0, 1, 0);
567
568 // Set up initial Min and Max output values for controller output
569 memset(resp, '\0', sizeof(resp));
570
571 if (!sendGetCommand(CDC_CMD_OMIN, resp))
572 return false;
573
574 if (!sscanf(resp, "%d", &prevMinOutput))
575 return false;
576
577 memset(resp, '\0', sizeof(resp));
578
579 if (!sendGetCommand(CDC_CMD_OMAX, resp))
580 return false;
581
582 if (!sscanf(resp, "%d", &prevMaxOutput))
583 return false;
584
585 DewChannelDutyCycleNP[0].setMin(prevMinOutput);
586 DewChannelDutyCycleNP[0].setMax(prevMaxOutput);
588 MinimumOutputNP[0].setMax(prevMaxOutput - 1);
589 MinimumOutputNP[0].setValue(prevMinOutput);
590 MinimumOutputNP.apply();
591 MaximumOutputNP[0].setMin(prevMinOutput + 1);
592 MaximumOutputNP[0].setValue(prevMaxOutput);
593 MaximumOutputNP.apply();
594
595 useDewLabels = PI::DewChannelLabelsTP.load();
596
597 // Overwrite default Dew Channel Labels but allow for custom labels
598 if ((!useDewLabels) || (strcmp( PI::DewChannelLabelsTP[0].getText(), PI::DewChannelLabelsTP[0].getLabel()) == 0))
599 {
600 LOG_INFO("Setting default Dew Channel Labels.");
601 PI::DewChannelLabelsTP[0].setText("Controller Output");
602 PI::DewChannelsSP[0].setLabel("Controller Output");
603 PI::DewChannelDutyCycleNP[0].setLabel("Controller Output");
604 PI::AutoDewSP[0].setLabel("Controller Output");
605 }
606
607 if (powerOutputs > 0)
608 {
609 usePowerLabels = PI::PowerChannelLabelsTP.load();
610 }
611
612 if ((dewOutputs + powerOutputs) > 0)
613 {
614 dewOutputs = 1;
615 powerOutputs = 0;
616 for (int i = 0; i < CDC_TOTAL_ADDITIONAL_OUTPUTS; i++)
617 {
618 char labelC[MAXINDILABEL] = {};
619 char dewLabel[MAXINDILABEL] = {};
620 int pin = i + CDC_MIN_ADDITIONAL_OUTPUT;
621 memset(labelC, '\0', sizeof(labelC));
622 memset(dewLabel, '\0', sizeof(dewLabel));
623
624 snprintf(labelC, MAXINDILABEL, channelLabels[lastControllerPinMode[i]], pin);
625 snprintf(dewLabel, MAXINDILABEL, "%s (%%)", labelC);
626
627 //LOGF_INFO("Dew count %d Power count %d", dewOutputs, powerOutputs);
628 //LOGF_INFO("CheapoDC Additional Output Pin %d Mode %d Label: %s", pin, lastControllerPinMode[i], labelC);
629 outputToChannel[i] = -1;
630
631 switch (lastControllerPinMode[i])
632 {
633 case CONTROLLER_PIN_MODE_DISABLED:
634 // Do nothing since we don't show Disabled Outputs
635 break;
636 case CONTROLLER_PIN_MODE_CONTROLLER:
637 case CONTROLLER_PIN_MODE_PWM:
638 // Check to see if pin modes have been changed sin last time...
639 if (useDewLabels)
640 {
641 int lastPin = -1;
642 char lastMode[MAXINDILABEL] = {};
643
644
645 if (sscanf(PI::DewChannelLabelsTP[dewOutputs].getText(), "Output %d (%s)", &lastPin, lastMode) == 2)
646 {
647 if (lastControllerPinMode[i] == CONTROLLER_PIN_MODE_CONTROLLER)
648 {
649 useDewLabels = (strcmp(lastMode, "Controller") == 0);
650 }
651 else
652 {
653 useDewLabels = (strcmp(lastMode, "PWM") == 0);
654 }
655 useDewLabels = (lastPin == pin) && useDewLabels;
656 }
657 }
658 if ((!useDewLabels) || (strcmp(PI::DewChannelLabelsTP[dewOutputs].getText(), PI::DewChannelLabelsTP[dewOutputs].getLabel()) == 0))
659 {
660 PI::DewChannelLabelsTP[dewOutputs].setText(labelC);
661 PI::DewChannelsSP[dewOutputs].setLabel(labelC);
662 PI::DewChannelDutyCycleNP[dewOutputs].setLabel(dewLabel);
663 }
664 dewChannelToOutput[dewOutputs] = pin;
665 outputToChannel[i] = dewOutputs;
666 //LOGF_INFO("Mapping Output to Dew Channel %d, Pin %d to Channel %d", i, pin, dewOutputs);
667 dewOutputs++;
668 break;
669 case CONTROLLER_PIN_MODE_BOOLEAN:
670 // Check to see if pin modes have been changed since last time...
671 if (usePowerLabels)
672 {
673 int lastPin = -1;
674
675 if (sscanf(PI::PowerChannelLabelsTP[powerOutputs].getText(), channelLabels[lastControllerPinMode[i]], &lastPin) == 1)
676 {
677 usePowerLabels = (lastPin == pin);
678 }
679 }
680 if ((!usePowerLabels) || (strcmp(PI::PowerChannelLabelsTP[powerOutputs].getText(), PI::PowerChannelLabelsTP[powerOutputs].getLabel()) == 0))
681 {
682 PI::PowerChannelLabelsTP[powerOutputs].setText(labelC);
683 PI::PowerChannelsSP[powerOutputs].setLabel(labelC);
684 }
685 powerChannelToOutput[powerOutputs] = pin;
686 outputToChannel[i] = powerOutputs;
687 //LOGF_INFO("Mapping Output tp Power Channel %d,Pin %d to Channel %d", i, pin, powerOutputs);
688 powerOutputs++;
689 break;
690 default:
691 // Do nothing
692 break;
693 }
694 }
695 }
696
698 if (powerOutputs > 0)
700
701 return true;
702
703}
704
705void CheapoDC::getWeatherSource()
706{
707 char resp[CDC_RESPONSE_LENGTH] = {};
708 bool usingOpenWeather = false;
709
710 if (sendGetCommand(CDC_CMD_WS, resp))
711 {
712 if (fwVOneDetected)
713 {
714 usingOpenWeather = (strcmp(resp, "OpenWeather") == 0);
715 WeatherSourceSP.reset();
716 if (usingOpenWeather)
717 WeatherSourceSP[OPENWEATHER].setState(ISS_ON);
718 else
719 WeatherSourceSP[OPENMETEO].setState(ISS_ON);
720
721 WeatherSourceSP.setState(IPS_OK);
722 WeatherSourceSP.apply();
723 }
724 else
725 {
726 int newWeatherSource;
727 int ok = 0;
728
729 ok = sscanf(resp, "%d", &newWeatherSource);
730 usingExternalWeatherSource = (newWeatherSource == EXTERNALSOURCE);
731
732 if ((ok == 1) && (newWeatherSource <= INTERNALSOURCE))
733 {
734 WeatherSourceSP.reset();
735 WeatherSourceSP[newWeatherSource].setState(ISS_ON);
736 WeatherSourceSP.setState(IPS_OK);
737 WeatherSourceSP.apply();
738 }
739 else
740 LOGF_ERROR("Get Weather Source: Response [%s] for Command [%s] not valid.", resp, CDC_CMD_WS);
741 }
742 }
743}
744
745// Set Controller Mode (Automatic, Manual, Off)
746bool CheapoDC::setControllerMode(int value)
747{
748 if ((value < AUTOMATIC) || (value > OFF))
749 return false;
750 else
751 {
752 char valBuf[CDC_SET_VALUE_LENGTH] = {};
753
754 sprintf(valBuf, CDC_INT_VALUE, value);
755 return sendSetCommand(CDC_CMD_DCM, valBuf);
756 }
757}
758
759// Set Temperature Mode (Weather Query, External Input)
760bool CheapoDC::setTemperatureMode(int value)
761{
762 if ((value < WEATHER_QUERY) || (value > EXTERNAL_INPUT))
763 {
764 LOGF_ERROR("Invalid Temperature Mode value: %d.", value);
765
766 return false;
767 }
768 else
769 {
770 char valBuf[CDC_SET_VALUE_LENGTH] = {};
771
772 // Check Focuser Snoop if EXTERNAL_INPUT
773 if ((value == EXTERNAL_INPUT) && (strlen(ActiveDeviceTP[ACTIVE_FOCUSER].getText()) == 0))
774 LOG_INFO("Temperature Mode set to Focuser Device. Configure Snoop Device for Focuser to send temperatures from the Focuser Device.");
775
776 sprintf(valBuf, CDC_INT_VALUE, value);
777 return sendSetCommand(CDC_CMD_DCTM, valBuf);
778 }
779}
780
781// Set SetPoint Mode (Dew Point, Temperature)
782bool CheapoDC::setSetPointMode(int value)
783{
784 if ((value < DEWPOINT) || (value > MIDPOINT))
785 return false;
786 else
787 {
788 char valBuf[CDC_SET_VALUE_LENGTH] = {};
789
790 sprintf(valBuf, CDC_INT_VALUE, value);
791 return sendSetCommand(CDC_CMD_SPM, valBuf);
792 }
793}
794
795bool CheapoDC::setWeatherSource(int value)
796{
797 int maxValue = internalHumiditySensorSupported ? INTERNALSOURCE : EXTERNALSOURCE;
798
799 if (fwVOneDetected)
800 {
801 LOGF_WARN("CheapoDC firmware V%s does not support Set Weather Source. Please upgrade firmware to latest V2+.", FWversionTP[0].getText());
802 return false;
803 }
804 if ((value < OPENMETEO) || (value > maxValue))
805 {
806 if (value == INTERNALSOURCE)
807 {
808 LOG_WARN("Setting Weather Source to CheapoDC Sensor requires firmware v2.3.0+ with a configured sensor.");
809 }
810 else
811 {
812 LOGF_ERROR("Invalid Weather Source value: %d.", value);
813 }
814 refreshSettings( true ); // Refresh to show actual state
815 return false;
816 }
817 else
818 {
819 char valBuf[CDC_SET_VALUE_LENGTH] = {};
820
821 if (value == EXTERNALSOURCE)
822 LOG_INFO("Weather Source set to Weather Device. Configure Snoop Device for Weather to receive weather data.");
823
824 sprintf(valBuf, CDC_INT_VALUE, value);
825 return sendSetCommand(CDC_CMD_WS, valBuf);
826 }
827}
828
829bool CheapoDC::SetPowerPort(size_t port, bool enabled)
830{
831 bool returnValue = false;
832 int output = powerChannelToOutput[port];
833
834 //LOGF_INFO("SetPowerPort: port=%zu, enabled=%s, Output=%d", port, enabled ? "true" : "false", output);
835
837 {
838 if (enabled)
839 {
840 returnValue = setAdditionalOutput(output, 100);
841 }
842 else
843 {
844 returnValue = setAdditionalOutput(output, 0);
845 }
846 }
847 else
848 {
849 LOGF_ERROR("SetPowerPort: Invalid Output %d to port mapping %zu.", output, port);
850 returnValue = false;
851 }
852 if (returnValue)
853 {
856 refreshSettings();
857 return true;
858 }
859 return false;
860}
861
862bool CheapoDC::SetAutoDewEnabled(size_t port, bool enabled)
863{
864 if (port != 0) // CheapoDC has a single auto dew control
865 {
866 LOGF_WARN("SetAutoDewEnabled: Invalid port number %zu.", port);
867 return false;
868 }
869
870 if (setControllerMode(enabled ? AUTOMATIC : OFF))
871 {
874 refreshSettings();
875 return true;
876 }
877 return false;
878}
879
880bool CheapoDC::SetDewPort(size_t port, bool enabled, double dutyCycle)
881{
882 bool returnValue = false;
883
884 //LOGF_INFO("SetDewPort: port=%zu, enabled=%s, dutyCycle=%.2f", port, enabled ? "true" : "false", dutyCycle);
885 if (port == 0) // Main dew output
886 {
887 if (AutoDewSP[0].getState() == ISS_ON)
888 {
889 LOG_WARN("Cannot set duty cycle for Controller while Auto Dew Control is enabled.");
890 refreshSettings( true ); // Refresh to show actual state
891 return false;
892 }
893 if (enabled)
894 {
895 if (previousControllerMode != MANUAL)
896 {
897 // Ignore the duty cycle and just set to Manual mode
898 returnValue = setControllerMode(MANUAL);
899 }
900 else if ((dutyCycle < prevMinOutput) || (dutyCycle > prevMaxOutput))
901 {
902 LOGF_WARN("Controller Duty cycle %.2f is out of range (%.2f - %.2f).",
903 dutyCycle, static_cast<double>(prevMinOutput), static_cast<double>(prevMaxOutput));
904 }
905 else
906 {
907 returnValue = setControllerMode(MANUAL) && setOutput(static_cast<int>(dutyCycle));
908 }
909 }
910 else
911 {
912 returnValue = setControllerMode(OFF);
913 }
914 }
915 else if (port >= 1 && port <= CDC_TOTAL_ADDITIONAL_OUTPUTS) // Additional 4 outputs (2 to 5) 4 Ports (1 to 4)
916 {
917 int output = dewChannelToOutput[port];
918 // Additional outputs are 1-based in the interface, map to CDC_MIN_ADDITIONAL_OUTPUT + (port - 1)
919 if (lastControllerPinMode[output - CDC_MIN_ADDITIONAL_OUTPUT] == CONTROLLER_PIN_MODE_PWM)
920 {
921 if (enabled)
922 {
923 returnValue = setAdditionalOutput(output, static_cast<int>(dutyCycle));
924 }
925 else
926 {
927 returnValue = setAdditionalOutput(output, 0);
928 }
929 }
930 else
931 {
932 LOGF_WARN("Duty cycle for output %d is set by %s.", output, PI::AutoDewSP[0].getLabel());
933 }
934 }
935
936 if (returnValue)
937 {
942 }
943
944 refreshSettings( true ); // Delay since multiple Dew Port settings can be done in succession
945 return returnValue;
946}
947
948bool CheapoDC::SetVariablePort(size_t port, bool enabled, double voltage)
949{
950 INDI_UNUSED(port);
951 INDI_UNUSED(enabled);
952 INDI_UNUSED(voltage);
953 LOG_DEBUG("SetVariablePort not supported by CheapoDC.");
954 return false;
955}
956
957bool CheapoDC::SetLEDEnabled(bool enabled)
958{
959 INDI_UNUSED(enabled);
960 LOG_DEBUG("SetLEDEnabled not supported by CheapoDC.");
961 return false;
962}
963
965{
966 LOG_DEBUG("CyclePower not supported by CheapoDC.");
967 return false;
968}
969
970bool CheapoDC::SetUSBPort(size_t port, bool enabled)
971{
972 INDI_UNUSED(port);
973 INDI_UNUSED(enabled);
974 LOG_DEBUG("SetUSBPort not supported by CheapoDC.");
975 return false;
976}
977
978bool CheapoDC::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
979{
980 bool result = false;
981 if (!dev || strcmp(dev, getDeviceName()))
982 return false;
983
984 if (INDI::PowerInterface::processSwitch(dev, name, states, names, n))
985 return true;
986
987 if (TemperatureModeSP.isNameMatch(name))
988 {
989 TemperatureModeSP.update(states, names, n);
990 TemperatureModeSP.setState(IPS_BUSY);
991 TemperatureModeSP.apply();
992 result = setTemperatureMode(TemperatureModeSP.findOnSwitchIndex());
993 return result && readSettings();
994 }
995
996 if (SetPointModeSP.isNameMatch(name))
997 {
998 SetPointModeSP.update(states, names, n);
999 SetPointModeSP.setState(IPS_BUSY);
1000 SetPointModeSP.apply();
1001 result = setSetPointMode(SetPointModeSP.findOnSwitchIndex());
1002 return result && readSettings();
1003 }
1004
1005 if (WeatherSourceSP.isNameMatch(name))
1006 {
1007 WeatherSourceSP.update(states, names, n);
1008 WeatherSourceSP.setState(IPS_BUSY);
1009 WeatherSourceSP.apply();
1010 result = setWeatherSource(WeatherSourceSP.findOnSwitchIndex());
1011 return result && readSettings();
1012 }
1013
1014 if (RefreshSP.isNameMatch(name))
1015 {
1016 char buf[3] = {"NA"};
1017
1018 RefreshSP.update(states, names, n);
1019 RefreshSP.setState(IPS_BUSY);
1020 RefreshSP.apply();
1021 result = sendSetCommand(CDC_CMD_QN, buf);
1022 return result && readSettings();
1023 }
1024
1025 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
1026}
1027
1028bool CheapoDC::setSetPoint(float value)
1029{
1030 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1031
1032 sprintf(valBuf, CDC_FLOAT_VALUE, value);
1033 return sendSetCommand(CDC_CMD_SP, valBuf);
1034}
1035
1036bool CheapoDC::setTrackPointOffset(float value)
1037{
1038 if ((value < -5.0) || (value > 5.0))
1039 return false;
1040 else
1041 {
1042 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1043
1044 sprintf(valBuf, CDC_FLOAT_VALUE, value);
1045 return sendSetCommand(CDC_CMD_TPO, valBuf);
1046 }
1047}
1048
1049bool CheapoDC::setTrackingRange(float value)
1050{
1051 if ((value < 4.0) || (value > 10.0))
1052 return false;
1053 else
1054 {
1055 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1056
1057 sprintf(valBuf, CDC_FLOAT_VALUE, value);
1058 return sendSetCommand(CDC_CMD_TKR, valBuf);
1059 }
1060}
1061
1062bool CheapoDC::setOutput(int value)
1063{
1064 if ((value < MinimumOutputNP[0].getValue()) || (value > MaximumOutputNP[0].getValue()))
1065 return false;
1066 else
1067 {
1068 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1069
1070 sprintf(valBuf, CDC_INT_VALUE, value);
1071 return sendSetCommand(CDC_CMD_DCO, valBuf);
1072 }
1073}
1074
1075bool CheapoDC::setAdditionalOutput(int pin, int value)
1076{
1077 if ((value < 0) || (value > 100))
1078 return false;
1079 else
1080 {
1081 char command[CDC_COMMAND_LENGTH] = {};
1082 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1083
1084 sprintf(valBuf, CDC_INT_VALUE, value);
1085 snprintf(command, CDC_COMMAND_LENGTH, CDC_CMD_CPO, pin);
1086 return sendSetCommand(command, valBuf);
1087 }
1088}
1089
1090bool CheapoDC::setMinimumOutput(int value)
1091{
1092 if (value >= MaximumOutputNP[0].getValue())
1093 {
1094 LOGF_WARN("Minimum Output %d must be less than Maximum Output %d.", value, MaximumOutputNP[0].getValue());
1095 return false;
1096 }
1097 else
1098 {
1099 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1100
1101 sprintf(valBuf, CDC_INT_VALUE, value);
1102 return sendSetCommand(CDC_CMD_OMIN, valBuf);
1103 }
1104}
1105
1106bool CheapoDC::setMaximumOutput(int value)
1107{
1108 if (value <= MinimumOutputNP[0].getValue())
1109 {
1110 LOGF_WARN("Maximum Output %d must be greater than Minimum Output %d.", value, MinimumOutputNP[0].getValue());
1111 return false;
1112 }
1113 else
1114 {
1115 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1116
1117 sprintf(valBuf, CDC_INT_VALUE, value);
1118 return sendSetCommand(CDC_CMD_OMAX, valBuf);
1119 }
1120}
1121
1122bool CheapoDC::setUpdateOutputEvery(int value)
1123{
1124 if ((value < 0) || (value > 20))
1125 return false;
1126 else
1127 {
1128 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1129
1130 sprintf(valBuf, CDC_INT_VALUE, value);
1131 return sendSetCommand(CDC_CMD_UOE, valBuf);
1132 }
1133}
1134
1135bool CheapoDC::setWeatherQueryEvery(int value)
1136{
1137 if ((value < 0) || (value > 20))
1138 return false;
1139 else
1140 {
1141 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1142
1143 sprintf(valBuf, CDC_INT_VALUE, value);
1144 return sendSetCommand(CDC_CMD_WQE, valBuf);
1145 }
1146}
1147
1148bool CheapoDC::setWeatherQueryEnabled(bool enabled)
1149{
1150 char disabledBuf[2] = "0";
1151 char enabledBuf[2] = "1";
1152
1153 if (enabled)
1154 return sendSetCommand(CDC_CMD_WQEN, enabledBuf);
1155 else
1156 return sendSetCommand(CDC_CMD_WQEN, disabledBuf);
1157}
1158
1159bool CheapoDC::setLatitude(float value)
1160{
1161 if ((value < -90.0) || (value > 90.0))
1162 return false;
1163 else
1164 {
1165 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1166
1167 sprintf(valBuf, CDC_FLOAT_VALUE, value);
1168 return sendSetCommand(CDC_CMD_LAT, valBuf);
1169 }
1170}
1171
1172bool CheapoDC::setLongitude(float value)
1173{
1174 if (value > 180)
1175 value = value - 360;
1176 if ((value < -180.0) || (value > 180.0))
1177 return false;
1178 else
1179 {
1180 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1181
1182 sprintf(valBuf, CDC_FLOAT_VALUE, value);
1183 return sendSetCommand(CDC_CMD_LON, valBuf);
1184 }
1185}
1186
1187bool CheapoDC::setUTCOffset(int offset)
1188{
1189 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1190 char zero[] = "0";
1191
1192 if (fwVOneDetected)
1193 return false;
1194
1195 sprintf(valBuf, CDC_INT_VALUE, offset*3600);
1196 return sendSetCommand(CDC_CMD_TMZ, valBuf) && sendSetCommand(CDC_CMD_DST, zero);
1197}
1198
1199bool CheapoDC::setLocation(float latitude, float longitude)
1200{
1201 bool result = true;
1202 char buf[3] = {"NA"};
1203 // CheapoDC longitude expected -180 to +180
1204 if (longitude > 180)
1205 longitude -= 360;
1206
1207 if (!fwVOneDetected)
1208 result = result && setWeatherQueryEnabled( false);
1209
1210 result = result && setLatitude(latitude);
1211 result = result && setLongitude(longitude);
1212
1213 if (!fwVOneDetected)
1214 {
1215 result = result && setWeatherQueryEnabled(true);
1216 result = result && sendSetCommand(CDC_CMD_QN, buf);
1217 }
1218 return result;
1219}
1220
1221bool CheapoDC::setExternalTemperature(float value)
1222{
1223 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1224
1225 sprintf(valBuf, CDC_FLOAT_VALUE, value);
1226 return sendSetCommand(CDC_CMD_ATPX, valBuf);
1227}
1228
1229bool CheapoDC::setWeatherTemperature(float value)
1230{
1231 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1232 if (usingExternalWeatherSource)
1233 {
1234 sprintf(valBuf, CDC_FLOAT_VALUE, value);
1235 return sendSetCommand(CDC_CMD_ATPQ, valBuf);
1236 }
1237 return false;
1238}
1239
1240bool CheapoDC::setWeatherHumidity(float value)
1241{
1242 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1243 if (usingExternalWeatherSource)
1244 {
1245 sprintf(valBuf, CDC_FLOAT_VALUE, value);
1246 return sendSetCommand(CDC_CMD_HU, valBuf);
1247 }
1248 return false;
1249}
1250
1251bool CheapoDC::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
1252{
1253 bool result = false;
1254
1255 if (!dev || strcmp(dev, getDeviceName()))
1256 return false;
1257
1258 if (INDI::PowerInterface::processNumber(dev, name, values, names, n))
1259 return true;
1260
1261 if (TrackPointOffsetNP.isNameMatch(name))
1262 {
1263 TrackPointOffsetNP.update(values, names, n);
1264 TrackPointOffsetNP.setState(IPS_BUSY);
1265 TrackPointOffsetNP.apply();
1266 result = setTrackPointOffset(TrackPointOffsetNP[0].getValue());
1267 return result && readSettings();
1268 }
1269
1270 if (TrackingRangeNP.isNameMatch(name))
1271 {
1272 TrackingRangeNP.update(values, names, n);
1273 TrackingRangeNP.setState(IPS_BUSY);
1274 TrackingRangeNP.apply();
1275 result = setTrackingRange(TrackingRangeNP[0].getValue());
1276 return result && readSettings();
1277 }
1278
1279 if (MinimumOutputNP.isNameMatch(name))
1280 {
1281 MinimumOutputNP.update(values, names, n);
1282 MinimumOutputNP.setState(IPS_BUSY);
1283 MinimumOutputNP.apply();
1284 result = setMinimumOutput(MinimumOutputNP[0].getValue());
1285 return result && readSettings();
1286 }
1287
1288 if (MaximumOutputNP.isNameMatch(name))
1289 {
1290 MaximumOutputNP.update(values, names, n);
1291 MaximumOutputNP.setState(IPS_BUSY);
1292 MaximumOutputNP.apply();
1293 result = setMaximumOutput(MaximumOutputNP[0].getValue());
1294 return result && readSettings();
1295 }
1296
1297 if (SetPointTemperatureNP.isNameMatch(name))
1298 {
1299 SetPointTemperatureNP.update(values, names, n);
1300 SetPointTemperatureNP.setState(IPS_BUSY);
1301 SetPointTemperatureNP.apply();
1302 result = setSetPoint(SetPointTemperatureNP[0].getValue());
1303 return result && readSettings();
1304 }
1305
1306 if (UpdateOutputEveryNP.isNameMatch(name))
1307 {
1308 UpdateOutputEveryNP.update(values, names, n);
1309 UpdateOutputEveryNP.setState(IPS_BUSY);
1310 UpdateOutputEveryNP.apply();
1311 result = setUpdateOutputEvery(UpdateOutputEveryNP[0].getValue());
1312 return result && readSettings();
1313 }
1314
1315 if (QueryWeatherEveryNP.isNameMatch(name))
1316 {
1317 QueryWeatherEveryNP.update(values, names, n);
1318 QueryWeatherEveryNP.setState(IPS_BUSY);
1319 QueryWeatherEveryNP.apply();
1320 result = setWeatherQueryEvery(QueryWeatherEveryNP[0].getValue());
1321 return result && readSettings();
1322 }
1323
1324 if (LocationNP.isNameMatch(name))
1325 {
1326 LocationNP.update(values, names, n);
1327 LocationNP.setState(IPS_BUSY);
1328 LocationNP.apply();
1329 result = setLocation(LocationNP[LOCATION_LATITUDE].getValue(), LocationNP[LOCATION_LONGITUDE].getValue());
1330 return result && readSettings();
1331 }
1332
1333 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
1334}
1335
1336void CheapoDC::setActiveDevice(const char *telescopeDevice, const char *focuserDevice, const char *weatherDevice)
1337{
1338 if (strcmp(telescopeDevice, activeTelescopeDevice) != 0)
1339 {
1340 indi_strlcpy(activeTelescopeDevice, telescopeDevice, MAXINDINAME);
1341 LOGF_DEBUG("Set snoop for %s device", activeTelescopeDevice);
1342 if (strlen(activeTelescopeDevice) > 0)
1343 {
1344 IDSnoopDevice(activeTelescopeDevice, CDC_SNOOP_LOCATION_PROPERTY);
1345 if (!fwVOneDetected)
1346 IDSnoopDevice(activeTelescopeDevice, CDC_SNOOP_TIME_PROPERTY);
1347 }
1348 }
1349
1350 if (strcmp(focuserDevice, activeFocuserDevice) != 0)
1351 {
1352 indi_strlcpy(activeFocuserDevice, focuserDevice, MAXINDINAME);
1353 LOGF_DEBUG("Set snoop for %s device", activeFocuserDevice);
1354 if (strlen(activeFocuserDevice) > 0)
1355 IDSnoopDevice(activeFocuserDevice, CDC_SNOOP_FOCUSER_PROPERTY);
1356 }
1357
1358 if (strcmp(weatherDevice, activeWeatherDevice) != 0)
1359 {
1360 // Not supported for firmware 1 devices
1361 if (fwVOneDetected && (strlen(weatherDevice) > 0))
1362 {
1363 indi_strlcpy(activeWeatherDevice, "", MAXINDINAME);
1364 LOG_WARN("Snoop Weather Device not supported by CheapoDC firmware V1. Upgrade to V2+ for support.");
1365 } else
1366 {
1367 indi_strlcpy(activeWeatherDevice, weatherDevice, MAXINDINAME);
1368 LOGF_DEBUG("Set snoop for %s device", activeWeatherDevice);
1369 if (strlen(activeWeatherDevice) > 0)
1370 IDSnoopDevice(activeWeatherDevice, CDC_SNOOP_WEATHER_PROPERTY);
1371 }
1372 }
1373}
1374
1375bool CheapoDC::setWeatherQueryAPIKey(const char *key)
1376{
1377
1378 char valBuf[CDC_SET_VALUE_LENGTH] = {};
1379
1380 sprintf(valBuf, CDC_TEXT_VALUE, key);
1381 return sendSetCommand(CDC_CMD_WKEY, valBuf);
1382}
1383
1384bool CheapoDC::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
1385{
1386 bool result = false;
1387
1388 if (!dev || strcmp(dev, getDeviceName()))
1389 return false;
1390
1391 if (INDI::PowerInterface::processText(dev, name, texts, names, n))
1392 return true;
1393
1394 if (WeatherQueryAPIKeyTP.isNameMatch(name))
1395 {
1396 WeatherQueryAPIKeyTP.update(texts, names, n);
1397 WeatherQueryAPIKeyTP.setState(IPS_BUSY);
1398 WeatherQueryAPIKeyTP.apply();
1399 result = setWeatherQueryAPIKey(WeatherQueryAPIKeyTP[0].getText());
1400 return readSettings() && result;
1401 }
1402
1403 if (ActiveDeviceTP.isNameMatch(name))
1404 {
1405 ActiveDeviceTP.update(texts, names, n);
1406 ActiveDeviceTP.setState(IPS_BUSY);
1407 ActiveDeviceTP.apply();
1408 setActiveDevice(ActiveDeviceTP[ACTIVE_TELESCOPE].getText(),
1409 ActiveDeviceTP[ACTIVE_FOCUSER].getText(),
1410 ActiveDeviceTP[ACTIVE_WEATHER].getText());
1411 return readSettings();
1412 }
1413
1414
1415 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
1416}
1417
1419{
1420 XMLEle *ep = nullptr;
1421 const char *propName = findXMLAttValu(root, "name");
1422 const char *deviceName = findXMLAttValu(root, "device");
1423 bool result = false;
1424
1425 LOGF_DEBUG("ISSNoopDevice %s, %s", deviceName, propName);
1426
1427 if (!(cdcConnection & CONNECTION_TCP))
1428 {
1429 return true;
1430 }
1431
1432 if ((!strcmp(propName, CDC_SNOOP_FOCUSER_PROPERTY)) &&
1433 (!strcmp(deviceName, activeFocuserDevice)))
1434 {
1435 bool tempAtributeFound = false;
1436
1437 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1438 {
1439 const char *name = findXMLAttValu(ep, "name");
1440
1441 if (!strcmp(name, CDC_SNOOP_FOCUSER_TEMPERATURE))
1442 {
1443 float externalTemp = atof(pcdataXMLEle(ep));
1444
1445 if (externalTemp != XtrnTemperatureNP[EXTERNAL_INPUT].getValue())
1446 result = setExternalTemperature(externalTemp) || result;
1447 LOGF_DEBUG("External Temp set to: %.2f", externalTemp);
1448 tempAtributeFound = true;
1449 }
1450 }
1451
1452 if (!tempAtributeFound)
1453 LOGF_WARN("Focuser TEMPERATURE attribute, %s, not found for %s:%s", CDC_SNOOP_FOCUSER_TEMPERATURE, ActiveDeviceTP[ACTIVE_FOCUSER].getText(), CDC_SNOOP_FOCUSER_PROPERTY);
1454 }
1455
1456 if ((!fwVOneDetected) && (!strcmp(propName, CDC_SNOOP_TIME_PROPERTY)) && (!strcmp(deviceName, activeTelescopeDevice)))
1457 {
1458 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1459 {
1460 const char *name = findXMLAttValu(ep, "name");
1461
1462 if (!strcmp(name, CDC_SNOOP_TIME_OFFSET))
1463 {
1464 int offset = atoi(pcdataXMLEle(ep));
1465
1466 result=setUTCOffset(offset);
1467 }
1468 }
1469 }
1470
1471
1472 if ((!strcmp(propName, CDC_SNOOP_LOCATION_PROPERTY)) && (!strcmp(deviceName, activeTelescopeDevice)))
1473 {
1474 bool latAtributeFound = false;
1475 bool longAtributeFound = false;
1476 bool updateLocation = false;
1477 float latitude = LocationNP[LOCATION_LATITUDE].getValue();
1478 float longitude = LocationNP[LOCATION_LONGITUDE].getValue();
1479
1480 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1481 {
1482 const char *name = findXMLAttValu(ep, "name");
1483
1484 if (!strcmp(name, CDC_SNOOP_LOCATION_LONGITUDE))
1485 {
1486 longitude = atof(pcdataXMLEle(ep));
1487 updateLocation = (longitude != LocationNP[LOCATION_LONGITUDE].getValue()) || updateLocation;
1488 longAtributeFound = true;
1489 } else if (!strcmp(name, CDC_SNOOP_LOCATION_LATITUDE))
1490 {
1491 latitude = atof(pcdataXMLEle(ep));
1492 latAtributeFound = true;
1493 updateLocation = (latitude != LocationNP[LOCATION_LATITUDE].getValue()) || updateLocation;
1494 }
1495 }
1496 if (updateLocation)
1497 {
1498 result= setLocation(latitude, longitude) || result;
1499 }
1500 if (!longAtributeFound)
1501 LOGF_WARN("LONG attribute, %s, not found for %s:%s", CDC_SNOOP_LOCATION_LONGITUDE, activeTelescopeDevice, CDC_SNOOP_LOCATION_PROPERTY);
1502 if (!latAtributeFound)
1503 LOGF_WARN("LAT attribute, %s, not found for %s:%s", CDC_SNOOP_LOCATION_LATITUDE, activeTelescopeDevice, CDC_SNOOP_LOCATION_PROPERTY);
1504 }
1505
1506 if (usingExternalWeatherSource && (!strcmp(propName, CDC_SNOOP_WEATHER_PROPERTY)) &&
1507 (!strcmp(deviceName, ActiveDeviceTP[ACTIVE_WEATHER].getText())))
1508 {
1509 bool temperatureAttributeFound = false;
1510 bool humidityAttributeFound = false;
1511
1512 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1513 {
1514 const char *name = findXMLAttValu(ep, "name");
1515
1516 if (!strcmp(name, CDC_SNOOP_WEATHER_TEMPERATURE))
1517 {
1518 float temperature = atof(pcdataXMLEle(ep));
1519
1520 if (temperature != XtrnTemperatureNP[WEATHER_QUERY].getValue())
1521 result = setWeatherTemperature(temperature) || result;
1522 temperatureAttributeFound = true;
1523 }
1524 else if (!strcmp(name, CDC_SNOOP_WEATHER_HUMIDITY))
1525 {
1526 float humidity = atof(pcdataXMLEle(ep));
1527
1528 if (humidity != HumidityNP[0].getValue())
1529 result = setWeatherHumidity(humidity) || result;
1530 humidityAttributeFound = true;
1531 }
1532 }
1533 if (!temperatureAttributeFound)
1534 LOGF_WARN("TEMPERATURE attribute, %s, not found for %s:%s", CDC_SNOOP_WEATHER_TEMPERATURE, activeWeatherDevice, CDC_SNOOP_WEATHER_PROPERTY);
1535 if (!humidityAttributeFound)
1536 LOGF_WARN("HUMIDITY attribute, %s, not found for %s:%s", CDC_SNOOP_WEATHER_HUMIDITY, activeWeatherDevice, CDC_SNOOP_WEATHER_PROPERTY);
1537 }
1538
1539 if (result)
1540 result = readSettings() || result;
1541 return result;
1542}
1543
1544bool CheapoDC::readSettings()
1545{
1546 char resp[CDC_RESPONSE_LENGTH] = {};
1547 char dateBuf[32] = {};
1548 char timeBuf[32] = {};
1549 int ok = -1;
1550 int tmzOffset;
1551 float temp_ambient, temp_external, humidity, dewpoint, setPoint, trackingRange;
1552 unsigned int output, minOutput, maxOutput, updatePeriod;
1553 float trackPointOffset, latitude, longitude;
1554 unsigned int controllerMode, temperatureMode, setPointMode, queryPeriod;
1555 bool powerChannelsExist = false;
1556
1557 // Get Temperatures first
1558 if (!sendGetCommand(CDC_CMD_ATPQ, resp))
1559 return false;
1560
1561 ok = sscanf(resp, "%f", &temp_ambient);
1562
1563 memset(resp, '\0', sizeof(resp));
1564 if (!sendGetCommand(CDC_CMD_ATPX, resp))
1565 return false;
1566
1567 ok = ok * sscanf(resp, "%f", &temp_external);
1568
1569 if (ok == 1)
1570 {
1571 XtrnTemperatureNP[0].setValue(temp_ambient);
1572 XtrnTemperatureNP[1].setValue(temp_external);
1573 XtrnTemperatureNP.setState(IPS_OK);
1574 XtrnTemperatureNP.apply();
1575 }
1576 else
1577 LOG_ERROR("GET temperature values failure");
1578
1579 // Get Humidity
1580 memset(resp, '\0', sizeof(resp));
1581
1582 if (!sendGetCommand(CDC_CMD_HU, resp))
1583 return false;
1584
1585 ok = sscanf(resp, "%f", &humidity);
1586
1587 if (ok == 1)
1588 {
1589 HumidityNP[0].setValue(humidity);
1590 HumidityNP.setState(IPS_OK);
1591 HumidityNP.apply();
1592 }
1593 else
1594 LOGF_ERROR("Get Humidity: Response [%s] for Command [%s] not valid.", resp, CDC_CMD_HU);
1595
1596 // Get Dew Point
1597 memset(resp, '\0', sizeof(resp));
1598
1599 if (!sendGetCommand(CDC_CMD_DP, resp))
1600 return false;
1601
1602 ok = sscanf(resp, "%f", &dewpoint);
1603
1604 if (ok == 1)
1605 {
1606 DewpointNP[0].setValue(dewpoint);
1607 DewpointNP.setState(IPS_OK);
1608 DewpointNP.apply();
1609 }
1610 else
1611 LOGF_ERROR("Get Dew point: Response [%s] for Command [%s] not valid.", resp, CDC_CMD_DP);
1612 // Get Set Point
1613 memset(resp, '\0', sizeof(resp));
1614
1615 if (!sendGetCommand(CDC_CMD_SP, resp))
1616 return false;
1617
1618 ok = sscanf(resp, "%f", &setPoint);
1619
1620 if (ok == 1)
1621 {
1622 SetPointTemperatureNP[0].setValue(setPoint);
1623 SetPointTemperatureNP.setState(IPS_OK);
1624 SetPointTemperatureNP.apply();
1625 }
1626 else
1627 LOGF_ERROR("Get Set Point: Response [%s] for Command [%s] not valid.", resp, CDC_CMD_SP);
1628
1629 // Get Controller Mode to set Dew Channel and Auto Dew states
1630 memset(resp, '\0', sizeof(resp));
1631 if (!sendGetCommand(CDC_CMD_DCM, resp))
1632 return false;
1633
1634 ok = sscanf(resp, "%d", &controllerMode);
1635
1636 if (ok != 1)
1637 LOGF_ERROR("Get Controller Mode: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_DCM);
1638
1639 previousControllerMode = controllerMode;
1640 if (controllerMode == OFF)
1641 {
1644 DewChannelDutyCycleNP[0].setValue(0);
1645 }
1646 else
1647 {
1648 DewChannelsSP[0].setState(controllerMode == MANUAL ? ISS_ON : ISS_OFF);
1649 AutoDewSP[0].setState(controllerMode == AUTOMATIC ? ISS_ON : ISS_OFF);
1650
1651 memset(resp, '\0', sizeof(resp));
1652
1653 if (!sendGetCommand(CDC_CMD_DCO, resp))
1654 return false;
1655
1656 if (sscanf(resp, "%d", &output))
1657 {
1658 DewChannelDutyCycleNP[0].setValue(output);
1659 }
1660 else
1661 LOGF_ERROR("Get Power Output: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_DCO);
1662 }
1663
1664 // Addtional Outputs added in release 2.20 FW
1665 if (additionalOutputsSupported)
1666 {
1667 for (int p = 0; p < CDC_TOTAL_ADDITIONAL_OUTPUTS; p++)
1668 {
1669 //LOGF_INFO("Output to Channel %d with Channel %d", p, outputToChannel[p]);
1670 if (outputToChannel[p] >= 0)
1671 {
1672 char command[CDC_COMMAND_LENGTH] = {};
1673 int pin = p + CDC_MIN_ADDITIONAL_OUTPUT;
1674
1675 memset(resp, '\0', sizeof(resp));
1676
1677 snprintf(command, CDC_COMMAND_LENGTH, CDC_CMD_CPO, pin);
1678 if (!this->sendGetCommand(command, resp))
1679 return false;
1680
1681 if (sscanf(resp, "%d", &output))
1682 {
1683 switch (lastControllerPinMode[p])
1684 {
1685 case CONTROLLER_PIN_MODE_CONTROLLER:
1686 //LOGF_INFO("Controller PIN Mode Controller for output %d, Duty cycle: %d", pin, output);
1687 DewChannelDutyCycleNP[outputToChannel[p]].setValue(output);
1688 DewChannelsSP[outputToChannel[p]].setState(ISS_OFF);
1689 break;
1690 case CONTROLLER_PIN_MODE_PWM:
1691 //LOGF_INFO("Controller PIN Mode PWM for output %d, Duty cycle: %d", pin, output);
1692 DewChannelDutyCycleNP[outputToChannel[p]].setValue(output);
1693 if (output > 0)
1694 DewChannelsSP[outputToChannel[p]].setState(ISS_ON);
1695 //DewChannelsSP[outputToChannel[p]].setState(output > 0 ? ISS_ON : ISS_OFF );
1696 break;
1697 case CONTROLLER_PIN_MODE_BOOLEAN:
1698 //LOGF_INFO("Controller PIN Mode BOOLEAN for output %d, State: %d", pin, output);
1699 PowerChannelsSP[outputToChannel[p]].setState(output > 0 ? ISS_ON : ISS_OFF );
1700 powerChannelsExist = true;
1701 break;
1702 default:
1703 break;
1704 }
1705 }
1706 }
1707 }
1708 }
1709
1715 AutoDewSP.apply();
1716 if (powerChannelsExist)
1717 {
1720 }
1721
1722 // Get Minimum output
1723 memset(resp, '\0', sizeof(resp));
1724
1725 if (!sendGetCommand(CDC_CMD_OMIN, resp))
1726 return false;
1727
1728 ok = sscanf(resp, "%d", &minOutput);
1729
1730 if (ok == 1)
1731 {
1732 if (minOutput != prevMinOutput)
1733 {
1734 MinimumOutputNP[0].setValue(minOutput);
1735 DewChannelDutyCycleNP[0].setMin(minOutput);
1737 MaximumOutputNP[0].setMin(minOutput + 1);
1738 MaximumOutputNP.apply();
1739 prevMinOutput = minOutput;
1740 }
1741 MinimumOutputNP.setState(IPS_OK);
1742 MinimumOutputNP.apply();
1743 }
1744 else
1745 LOGF_ERROR("Get Minimum Output: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_OMIN);
1746
1747 // Get Maximum output
1748 memset(resp, '\0', sizeof(resp));
1749
1750 if (!sendGetCommand(CDC_CMD_OMAX, resp))
1751 return false;
1752
1753 ok = sscanf(resp, "%d", &maxOutput);
1754
1755 if (ok == 1)
1756 {
1757
1758 if (maxOutput != prevMaxOutput)
1759 {
1760 MaximumOutputNP[0].setValue(maxOutput);
1761 DewChannelDutyCycleNP[0].setMax(maxOutput);
1763 MinimumOutputNP[0].setMax(maxOutput - 1);
1764 MinimumOutputNP.apply();
1765 prevMaxOutput = maxOutput;
1766 }
1767 MaximumOutputNP.setState(IPS_OK);
1768 MaximumOutputNP.apply();
1769 }
1770 else
1771 LOGF_ERROR("Get Maximum Output: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_OMAX);
1772
1773 // Get Track Point Offset
1774 memset(resp, '\0', sizeof(resp));
1775
1776 if (!sendGetCommand(CDC_CMD_TPO, resp))
1777 return false;
1778
1779 ok = sscanf(resp, "%f", &trackPointOffset);
1780
1781 if (ok == 1)
1782 {
1783 TrackPointOffsetNP[0].setValue(trackPointOffset);
1784 TrackPointOffsetNP.setState(IPS_OK);
1785 TrackPointOffsetNP.apply();
1786 }
1787 else
1788 LOGF_ERROR("Get Track Point Offset: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_TPO);
1789
1790 // Get Tracking Range
1791 memset(resp, '\0', sizeof(resp));
1792
1793 if (!sendGetCommand(CDC_CMD_TKR, resp))
1794 return false;
1795
1796 ok = sscanf(resp, "%f", &trackingRange);
1797
1798 if (ok == 1)
1799 {
1800 TrackingRangeNP[0].setValue(trackingRange);
1801 TrackingRangeNP.setState(IPS_OK);
1802 TrackingRangeNP.apply();
1803 }
1804 else
1805 LOGF_ERROR("Get Update Output Every: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_TKR);
1806
1807 // Get Output Update Period
1808 memset(resp, '\0', sizeof(resp));
1809
1810 if (!sendGetCommand(CDC_CMD_UOE, resp))
1811 return false;
1812
1813 ok = sscanf(resp, "%d", &updatePeriod);
1814
1815 if (ok == 1)
1816 {
1817 UpdateOutputEveryNP[0].setValue(updatePeriod);
1818 UpdateOutputEveryNP.setState(IPS_OK);
1819 UpdateOutputEveryNP.apply();
1820 }
1821 else
1822 LOGF_ERROR("Get Query Weather Every: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_UOE);
1823
1824 // Get Query Weather Period
1825 memset(resp, '\0', sizeof(resp));
1826
1827 if (!sendGetCommand(CDC_CMD_WQE, resp))
1828 return false;
1829
1830 ok = sscanf(resp, "%d", &queryPeriod);
1831
1832 if (ok == 1)
1833 {
1834 QueryWeatherEveryNP[0].setValue(queryPeriod);
1835 QueryWeatherEveryNP.setState(IPS_OK);
1836 QueryWeatherEveryNP.apply();
1837 }
1838 else
1839 LOGF_ERROR("Get Query Weather Every: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_WQE);
1840
1841 // Get Device Date-Time and UTC Offset (Not supported in FW V1)
1842 if (!fwVOneDetected)
1843 {
1844 memset(resp, '\0', sizeof(resp));
1845
1846 if (!sendGetCommand(CDC_CMD_CDT, resp))
1847 return false;
1848
1849 DeviceTimeTP[LOCAL_TIME].setText(resp);
1850
1851 memset(resp, '\0', sizeof(resp));
1852
1853 if (!sendGetCommand(CDC_CMD_TMZ, resp))
1854 return false;
1855
1856 ok = sscanf(resp, "%d", &tmzOffset);
1857
1858 if (ok == 1)
1859 {
1860 memset(resp, '\0', sizeof(resp));
1861 sprintf(resp, CDC_INT_VALUE, tmzOffset/3600);
1862 DeviceTimeTP[UTC_OFFSET].setText(resp);
1863 DeviceTimeTP.setState(IPS_OK);
1864 DeviceTimeTP.apply();
1865 }
1866 else
1867 {
1868 LOGF_ERROR("Get UTC Offset: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_TMZ);
1869 DeviceTimeTP.setState(IPS_ALERT);
1870 }
1871 }
1872
1873 // Get Location - Latitude
1874 memset(resp, '\0', sizeof(resp));
1875
1876 if (!sendGetCommand(CDC_CMD_LAT, resp))
1877 return false;
1878
1879 ok = sscanf(resp, "%f", &latitude);
1880
1881 if ((ok == 1) && (latitude >= -90.0) && (latitude <= 90.0))
1882 {
1883 LocationNP[LOCATION_LATITUDE].setValue(latitude);
1884 LocationNP.setState(IPS_OK);
1885 LocationNP.apply();
1886 }
1887 else
1888 LOGF_ERROR("Get Latitude: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_LAT);
1889
1890 // Get Location - Longitude
1891 memset(resp, '\0', sizeof(resp));
1892
1893 if (!sendGetCommand(CDC_CMD_LON, resp))
1894 return false;
1895
1896 ok = sscanf(resp, "%f", &longitude);
1897
1898 if ((ok == 1) && (longitude >= -180.0) && (longitude <= 180.0))
1899 {
1900 if (longitude < 0)
1901 longitude = longitude + 360;
1902 LocationNP[LOCATION_LONGITUDE].setValue(longitude);
1903 LocationNP.setState(IPS_OK);
1904 LocationNP.apply();
1905 }
1906 else
1907 LOGF_ERROR("Get Longitude: Response [%s] for Command [%s] invalid.", resp, CDC_CMD_LON);
1908
1909 // Get Set Point Mode
1910 memset(resp, '\0', sizeof(resp));
1911
1912 if (!sendGetCommand(CDC_CMD_SPM, resp))
1913 return false;
1914
1915 ok = sscanf(resp, "%d", &setPointMode);
1916
1917 if ((ok == 1) && (setPointMode <= MIDPOINT))
1918 {
1919 SetPointModeSP.reset();
1920 SetPointModeSP[setPointMode].setState(ISS_ON);
1921 SetPointModeSP.setState(IPS_OK);
1922 SetPointModeSP.apply();
1923 }
1924 else
1925 LOGF_ERROR("Get Set Point Mode: Response [%s] for Command [%s] not valid.", resp, CDC_CMD_SPM);
1926
1927 // Get weather source
1928 getWeatherSource();
1929
1930 // Get OpenWeather API Key
1931 memset(resp, '\0', sizeof(resp));
1932
1933 if (!sendGetCommand(CDC_CMD_WKEY, resp))
1934 return false;
1935
1936 WeatherQueryAPIKeyTP[0].setText(resp);
1937 WeatherQueryAPIKeyTP.setState(IPS_OK);
1938 WeatherQueryAPIKeyTP.apply();
1939
1940 // Get Last Weather Update date time
1941 memset(resp, '\0', sizeof(resp));
1942
1943 if (!sendGetCommand(CDC_CMD_LWUD, dateBuf))
1944 return false;
1945
1946 if (!sendGetCommand(CDC_CMD_LWUT, timeBuf))
1947 return false;
1948
1949 sprintf(resp, "%s %s", dateBuf, timeBuf);
1950
1951 WeatherUpdatedTP[0].setText(resp);
1952 WeatherUpdatedTP.setState(IPS_OK);
1953 WeatherUpdatedTP.apply();
1954
1955 // Active Devices
1956 ActiveDeviceTP[ACTIVE_TELESCOPE].setText(activeTelescopeDevice);
1957 ActiveDeviceTP[ACTIVE_FOCUSER].setText(activeFocuserDevice);
1958 ActiveDeviceTP[ACTIVE_WEATHER].setText(activeWeatherDevice);
1959 ActiveDeviceTP.setState(IPS_OK);
1960 ActiveDeviceTP.apply();
1961
1962 // Get Temperature Mode
1963 memset(resp, '\0', sizeof(resp));
1964
1965 if (!sendGetCommand(CDC_CMD_DCTM, resp))
1966 return false;
1967
1968 ok = sscanf(resp, "%d", &temperatureMode);
1969
1970 if ((ok == 1) && (temperatureMode <= TEMPERATURE))
1971 {
1972 TemperatureModeSP.reset();
1973 TemperatureModeSP[temperatureMode].setState(ISS_ON);
1974 TemperatureModeSP.setState(IPS_OK);
1975 TemperatureModeSP.apply();
1976 }
1977 else
1978 LOGF_ERROR("Get Temperature Mode: Response [%s] for Command [%s] not valid.", resp, CDC_CMD_DCTM);
1979
1980 // Refresh
1981 RefreshSP.reset();
1982 RefreshSP[0].setState(ISS_OFF);
1983 RefreshSP.setState(IPS_OK);
1984 RefreshSP.apply();
1985
1986 // Check for Snoop and settings alignment
1987 // For Temperature Device
1988 if ((previousTemperatureMode != temperatureMode) &&
1989 (temperatureMode == EXTERNAL_INPUT) &&
1990 (strlen(ActiveDeviceTP[ACTIVE_FOCUSER].getText()) == 0))
1991 {
1992 LOG_INFO("Temperature Mode set to External Input. Set Snoop Device for Focuser to send temperature from the Focuser Device.");
1993 }
1994 previousTemperatureMode = temperatureMode;
1995
1996 // For Weather Device
1997 if (!fwVOneDetected)
1998 {
1999 if ((usingExternalWeatherSource) &&
2000 (!previuoslyUsingExternalWeatherSource) &&
2001 (strlen(ActiveDeviceTP[ACTIVE_WEATHER].getText()) == 0))
2002 {
2003 LOG_INFO("Weather Source set to Weather Device. Configure Snoop Device for Weather to send temperature/humidity from the Weather Device.");
2004 }
2005 previuoslyUsingExternalWeatherSource = usingExternalWeatherSource;
2006
2007 }
2008
2009 return true;
2010}
2011
2013{
2016 ActiveDeviceTP.save(fp);
2017 /* MinimumOutputNP.save(fp);
2018 MaximumOutputNP.save(fp);
2019 TrackPointOffsetNP.save(fp);
2020 TrackingRangeNP.save(fp);
2021 UpdateOutputEveryNP.save(fp);
2022 QueryWeatherEveryNP.save(fp);
2023 WeatherSourceSP.save(fp);
2024 WeatherQueryAPIKeyTP.save(fp);
2025 LocationNP.save(fp);
2026 SetPointModeSP.save(fp);
2027 TemperatureModeSP.save(fp);
2028 SetPointTemperatureNP.save(fp); */
2029 return true;
2030}
2031
2032void CheapoDC::refreshSettings(bool delayRefresh )
2033{
2034 // Stop current timer
2035 if (timerIndex != -1)
2036 {
2037
2038 RemoveTimer(timerIndex);
2039 }
2040 if (!delayRefresh)
2041 {
2042 // Read settings immediately
2043 readSettings();
2044 // Restart timer
2045 timerIndex = SetTimer(getCurrentPollingPeriod()); //
2046 }
2047 else
2048 {
2049 // Wait then refresh
2050 timerIndex = SetTimer(500);
2051 }
2052}
2053
2055{
2056 if (!isConnected())
2057 {
2058 return;
2059 }
2060
2061 // Get temperatures etc.
2062 readSettings();
2063 timerIndex = SetTimer(getCurrentPollingPeriod());
2064}
double humidity
double temperature
int updateLocation(double lng, double lat)
std::unique_ptr< CheapoDC > cheapodc(new CheapoDC())
#define CDC_COMMAND_LENGTH
Definition cheapodc.h:93
#define CDC_CMD_ATPX
Definition cheapodc.h:78
#define CDC_TEXT_VALUE
Definition cheapodc.h:97
#define CDC_CMD_CPO
Definition cheapodc.h:87
#define CDC_SNOOP_WEATHER_HUMIDITY
Definition cheapodc.h:115
#define CDC_CMD_WQE
Definition cheapodc.h:67
#define CDC_CMD_OMAX
Definition cheapodc.h:77
#define CDC_CMD_LAT
Definition cheapodc.h:71
#define CDC_CMD_TPO
Definition cheapodc.h:58
#define CDC_CMD_WQEN
Definition cheapodc.h:83
#define CDC_CMD_DST
Definition cheapodc.h:75
#define CDC_DEFAULT_HOST
Definition cheapodc.h:99
#define CDC_SNOOP_FOCUSER_PROPERTY
Definition cheapodc.h:111
#define CDC_CMD_LWUT
Definition cheapodc.h:82
#define CDC_SNOOP_WEATHER_TEMPERATURE
Definition cheapodc.h:114
#define CDC_SNOOP_WEATHER_PROPERTY
Definition cheapodc.h:113
#define CHEAPODC_VERSION_MAJOR
Definition cheapodc.h:50
#define CDC_FLOAT_VALUE
Definition cheapodc.h:96
#define CDC_SET_VALUE_LENGTH
Definition cheapodc.h:94
#define CDC_CMD_CPM
Definition cheapodc.h:86
#define CDC_CMD_SPM
Definition cheapodc.h:66
#define CDC_INT_VALUE
Definition cheapodc.h:95
#define CDC_CMD_DCTM
Definition cheapodc.h:65
#define CDC_CMD_CDT
Definition cheapodc.h:73
#define CDC_MIN_ADDITIONAL_OUTPUT
Definition cheapodc.h:101
#define CDC_CMD_WKEY
Definition cheapodc.h:70
#define CDC_CMD_LWUD
Definition cheapodc.h:81
#define CDC_CMD_LON
Definition cheapodc.h:72
#define CDC_CMD_FW
Definition cheapodc.h:63
#define CDC_CMD_WS
Definition cheapodc.h:61
#define CDC_CMD_UOE
Definition cheapodc.h:68
#define CHEAPODC_VERSION_MINOR
Definition cheapodc.h:51
#define CDC_DEFAULT_POLLING_PERIOD
Definition cheapodc.h:98
#define CDC_SET_COMMAND
Definition cheapodc.h:91
#define CDC_SNOOP_TIME_PROPERTY
Definition cheapodc.h:109
#define CDC_CMD_SDAP
Definition cheapodc.h:88
#define CDC_SNOOP_LOCATION_LATITUDE
Definition cheapodc.h:107
#define CDC_CMD_QN
Definition cheapodc.h:62
#define CDC_RESPONSE_LENGTH
Definition cheapodc.h:92
#define CDC_DEFAULT_PORT
Definition cheapodc.h:100
#define CDC_SNOOP_LOCATION_PROPERTY
Definition cheapodc.h:106
#define CDC_SNOOP_LOCATION_LONGITUDE
Definition cheapodc.h:108
#define CDC_CMD_SP
Definition cheapodc.h:57
#define CDC_CMD_HU
Definition cheapodc.h:55
#define CDC_CMD_OMIN
Definition cheapodc.h:76
#define CDC_SNOOP_FOCUSER_TEMPERATURE
Definition cheapodc.h:112
#define CDC_CMD_TKR
Definition cheapodc.h:59
#define CDC_GET_COMMAND
Definition cheapodc.h:90
#define CDC_CMD_ATPQ
Definition cheapodc.h:54
#define CDC_CMD_TMZ
Definition cheapodc.h:74
#define CDC_CMD_DCM
Definition cheapodc.h:64
#define CDC_CMD_DCO
Definition cheapodc.h:60
#define CDC_CMD_DP
Definition cheapodc.h:56
#define CDC_SNOOP_TIME_OFFSET
Definition cheapodc.h:110
#define CDC_TOTAL_ADDITIONAL_OUTPUTS
Definition cheapodc.h:102
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool SetPowerPort(size_t port, bool enabled) override
SetPowerPort Set power port on/off.
Definition cheapodc.cpp:829
virtual bool SetDewPort(size_t port, bool enabled, double dutyCycle) override
SetDewPort Set DEW/Dew port duty cycle.
Definition cheapodc.cpp:880
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
@ CONNECTION_TCP
Definition cheapodc.h:152
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition cheapodc.cpp:171
virtual bool CyclePower() override
CyclePower Cycle power to all ports.
Definition cheapodc.cpp:964
virtual bool SetAutoDewEnabled(size_t port, bool enabled) override
SetAutoDewEnabled Enable/disable automatic dew control.
Definition cheapodc.cpp:862
virtual const char * getDefaultName() override
Definition cheapodc.cpp:271
virtual bool SetUSBPort(size_t port, bool enabled) override
SetUSBPort Set USB port on/off.
Definition cheapodc.cpp:970
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool SetLEDEnabled(bool enabled) override
SetLEDEnabled Enable/disable power LEDs.
Definition cheapodc.cpp:957
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool SetVariablePort(size_t port, bool enabled, double voltage) override
SetVariablePort Set variable voltage port.
Definition cheapodc.cpp:948
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition cheapodc.cpp:978
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 bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition cheapodc.cpp:49
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The TCP class manages connection with devices over the network via TCP/IP. Upon successful connection...
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
int getPortFD() const
bool isConnected() const
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
void addPollPeriodControl()
Add Polling period control to the driver.
void addConfigurationControl()
Add Configuration control to 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...
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
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 ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
void RemoveTimer(int id)
Remove timer added with SetTimer.
Connection::Interface * getActiveConnection()
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.
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.
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
void setPermission(IPerm permission)
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)
a class to store JSON values
ValueType & get_to(ValueType &v) const noexcept(noexcept(JSONSerializer< ValueType >::from_json(std::declval< const basic_json_t & >(), v)))
get a value (explicit)
general exception of the basic_json class
const char * what() const noexcept override
returns the explanatory string
const char * CONNECTION_TAB
CONNECTION_TAB Where all device connection settings (serial, usb, ethernet) are defined and controlle...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * SITE_TAB
SITE_TAB Where all site information setting are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
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
@ IPS_BUSY
Definition indiapi.h:161
@ 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
@ ISR_ATMOST1
Definition indiapi.h:172
#define MAXINDINAME
Definition indiapi.h:189
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
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition indidriver.c:143
#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
#define LOG_INFO(txt)
Definition indilogger.h:74
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition lilxml.cpp:644
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition lilxml.cpp:555
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition lilxml.cpp:606
int setUTCOffset(int fd, double hours)
Namespace to encapsulate INDI client, drivers, and mediator classes.
basic_json<> json
default specialization
uint8_t cmd[4]
Definition pwc-ioctl.h:2
#define OFF
Definition stvdriver.h:26