Instrument Neutral Distributed Interface INDI 2.2.5
dragonlair.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2024 Rick Bassham. All rights reserved.
3
4 Dark Dragons Astronomy DragonLAIR
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 The full GNU General Public License is included in this distribution in the
22 file called LICENSE.
23*******************************************************************************/
24
25#include "dragonlair.h"
26
27#include <httplib.h>
28#include <string.h>
29
30#ifdef _USE_SYSTEM_JSONLIB
31#include <nlohmann/json.hpp>
32#else
33#include <indijson.hpp>
34#endif
35
36// We declare an auto pointer to DragonLAIR.
37std::unique_ptr<DragonLAIR> dragonlair(new DragonLAIR());
38
43
44/************************************************************************************
45 *
46* ***********************************************************************************/
48{
50
52
53 MotorStatsNP[0].fill("Voltage", "Voltage", "%0.2f V", 0.0, 24.0, 0.01, 0.00);
54 MotorStatsNP[1].fill("Current", "Current", "%0.2f A", 0.0, 24.0, 0.01, 0.00);
55 MotorStatsNP.fill(getDeviceName(), "MOTOR_STATS", "Motor Stats", INFO_TAB, IP_RO, 60, IPS_IDLE);
56
57 FirmwareTP[0].fill("Version", "Version", nullptr);
58 FirmwareTP[1].fill("Serial", "Serial", nullptr);
59 FirmwareTP.fill(getDeviceName(), "FIRMWARE", "Firmware", INFO_TAB, IP_RO, 60, IPS_IDLE);
60
61 IPAddressTP[0].fill("IP Address", "IP Address", nullptr);
62 IPAddressTP.fill(getDeviceName(), "IP_ADDRESS", "IP Address", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
63
64 DiscoverSwitchSP[0].fill("DISCOVER", "Discover", ISS_OFF);
65 DiscoverSwitchSP.fill(getDeviceName(), "DISCOVER", "Discover", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
66
67 SafetySensorLP[0].fill("SAFETY_SENSOR_1", "Safety Sensor 1", IPS_IDLE);
68 SafetySensorLP[1].fill("SAFETY_SENSOR_2", "Safety Sensor 2", IPS_IDLE);
69 SafetySensorLP[2].fill("SAFETY_SENSOR_3", "Safety Sensor 3", IPS_IDLE);
70 SafetySensorLP[3].fill("SAFETY_SENSOR_4", "Safety Sensor 4", IPS_IDLE);
71 SafetySensorLP.fill(getDeviceName(), "SAFETY_SENSOR", "Safety Sensor", MAIN_CONTROL_TAB, IPS_IDLE);
72
73 LimitSwitchLP[0].fill("LIMIT_SWITCH_1", "Fully Open Switch", IPS_IDLE);
74 LimitSwitchLP[1].fill("LIMIT_SWITCH_2", "Fully Closed Switch", IPS_IDLE);
75 LimitSwitchLP.fill(getDeviceName(), "LIMIT_SWITCH", "Limit Switch", MAIN_CONTROL_TAB, IPS_IDLE);
76
78
80
81 return true;
82}
83
84void DragonLAIR::ISGetProperties(const char *dev)
85{
87
88 defineProperty(IPAddressTP);
89 defineProperty(DiscoverSwitchSP);
90
91 loadConfig(IPAddressTP);
92}
93
95{
96 return INDI::Dome::ISSnoopDevice(root);
97}
98
100{
101 if (strlen(IPAddressTP[0].getText()) == 0)
102 {
103 LOG_ERROR("IP Address is not set.");
104 return false;
105 }
106
107 InitPark();
108
110 return true;
111}
112
114{
115 return true;
116}
117
119{
120 return (const char *)"DragonLAIR";
121}
122
123bool DragonLAIR::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
124{
125 if (isDeviceNameMatch(dev) && DiscoverSwitchSP.isNameMatch(name))
126 {
127 DiscoverSwitchSP.update(states, names, n);
128 auto isToggled = DiscoverSwitchSP[0].getState() == ISS_ON;
129 DiscoverSwitchSP.setState(isToggled ? IPS_BUSY : IPS_IDLE);
130
131 if (isToggled)
132 {
133 discoverDevices();
134 }
135
136 DiscoverSwitchSP.apply();
137 return true;
138 }
139
140 return INDI::Dome::ISNewSwitch(dev, name, states, names, n);
141}
142
143bool DragonLAIR::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
144{
145 if (isDeviceNameMatch(dev) && IPAddressTP.isNameMatch(name))
146 {
147 IPAddressTP.update(texts, names, n);
148 IPAddressTP.setState(IPS_OK);
149 IPAddressTP.apply();
150
151 return true;
152 }
153
154 return INDI::Dome::ISNewText(dev, name, texts, names, n);
155}
156
158{
160
161 if (isConnected())
162 {
163 defineProperty(MotorStatsNP);
164 defineProperty(FirmwareTP);
165 defineProperty(SafetySensorLP);
166 defineProperty(LimitSwitchLP);
167 }
168 else
169 {
170 deleteProperty(MotorStatsNP);
171 deleteProperty(FirmwareTP);
172 deleteProperty(SafetySensorLP);
173 deleteProperty(LimitSwitchLP);
174 }
175
176 return true;
177}
178
180{
181 if (!isConnected())
182 return;
183
184 updateStatus();
185
187}
188
190{
191 IPAddressTP.save(fp);
192
194}
195
197{
198 updateStatus();
199
200 if (operation == MOTION_START)
201 {
202 auto state = Dome::getDomeState();
203
204 // DOME_CW --> OPEN. If can we are ask to "open" while we are fully opened as the limit switch indicates,
205 // then we simply return false.
206 if (dir == DOME_CW && state == DOME_UNPARKED)
207 {
208 LOG_WARN("Roof is already fully opened.");
209 return IPS_ALERT;
210 }
211 else if (dir == DOME_CCW && state == DOME_PARKED)
212 {
213 LOG_WARN("Roof is already fully closed.");
214 return IPS_ALERT;
215 }
216 else if (dir == DOME_CCW && INDI::Dome::isLocked())
217 {
218 LOG_INFO("Cannot close dome when mount is locking. See: Telescope parking policy, in options tab");
219 return IPS_ALERT;
220 }
221
222 if (dir == DOME_CW)
223 {
224 LOG_INFO("Roll off is opening...");
225 openRoof();
226 }
227 else
228 {
229 LOG_INFO("Roll off is closing...");
230 closeRoof();
231 }
232
233 return IPS_BUSY;
234 }
235
236 return (Dome::Abort() ? IPS_OK : IPS_ALERT);
237}
238
240{
242 if (rc == IPS_BUSY)
243 {
244 LOG_INFO("Roll off is parking...");
245 return IPS_BUSY;
246 }
247 else
248 {
249 return IPS_ALERT;
250 }
251}
252
254{
256 if (rc == IPS_BUSY)
257 {
258 LOG_INFO("Roll off is unparking...");
259 return IPS_BUSY;
260 }
261 else
262 {
263 return IPS_ALERT;
264 }
265}
266
268{
269 stopRoof();
270 updateStatus();
271
272 return true;
273}
274
275void DragonLAIR::updateStatus()
276{
277 if (strlen(IPAddressTP[0].getText()) == 0)
278 {
279 LOG_ERROR("IP Address is not set.");
280 return;
281 }
282
283 httplib::Client cli(IPAddressTP[0].getText(), 80);
284
285 try
286 {
287 httplib::Result result = cli.Get("/indi/status");
288 if (!result)
289 {
290 LOG_ERROR("Unable to connect.");
291 return;
292 }
293
294 if (result.value().status == 200)
295 {
296 nlohmann::json j = nlohmann::json::parse(result.value().body, nullptr, false, true);
297
298 if (j.is_discarded())
299 {
300 LOG_ERROR("Error parsing JSON.");
301 return;
302 }
303
304 std::string version = j["version"];
305 std::string serial = j["serialNumber"];
306 uint32_t voltageMV = j["voltage"];
307 uint32_t currentMA = j["current"];
308
309 MotorStatsNP[0].setValue(voltageMV / 1000.0);
310 MotorStatsNP[1].setValue(currentMA / 1000.0);
311 MotorStatsNP.setState(IPS_OK);
312 MotorStatsNP.apply();
313
314 FirmwareTP[0].setText(version.c_str());
315 FirmwareTP[1].setText(serial.c_str());
316 FirmwareTP.setState(IPS_OK);
317 FirmwareTP.apply();
318
319 std::string safetySensor1 = j["roof"]["safetySensor1"];
320 std::string safetySensor2 = j["roof"]["safetySensor2"];
321 std::string safetySensor3 = j["roof"]["safetySensor3"];
322 std::string safetySensor4 = j["roof"]["safetySensor4"];
323
324 SafetySensorLP[0].setState(safetySensor1 == "disabled" ? IPS_IDLE : safetySensor1 == "unsafe" ? IPS_ALERT : IPS_OK);
325 SafetySensorLP[1].setState(safetySensor2 == "disabled" ? IPS_IDLE : safetySensor2 == "unsafe" ? IPS_ALERT : IPS_OK);
326 SafetySensorLP[2].setState(safetySensor3 == "disabled" ? IPS_IDLE : safetySensor3 == "unsafe" ? IPS_ALERT : IPS_OK);
327 SafetySensorLP[3].setState(safetySensor4 == "disabled" ? IPS_IDLE : safetySensor4 == "unsafe" ? IPS_ALERT : IPS_OK);
328 SafetySensorLP.apply();
329
330 bool isRoofFullyClosed = j["roof"]["isRoofFullyClosed"];
331 bool isRoofFullyOpen = j["roof"]["isRoofFullyOpen"];
332
333 LimitSwitchLP[0].setState(isRoofFullyOpen ? IPS_OK : IPS_BUSY);
334 LimitSwitchLP[1].setState(isRoofFullyClosed ? IPS_OK : IPS_BUSY);
335 LimitSwitchLP.apply();
336
337 bool isRoofClosing = j["roof"]["isRoofClosing"];
338 bool isRoofOpening = j["roof"]["isRoofOpening"];
339
340 if (isRoofFullyClosed)
341 {
342 if (Dome::getDomeState() != DOME_PARKED)
343 {
344 SetParked(true);
345 }
346 }
347 else if (isRoofFullyOpen)
348 {
349 if (Dome::getDomeState() != DOME_UNPARKED)
350 {
351 SetParked(false);
352 }
353 }
354 else if (isRoofClosing)
355 {
356 if (Dome::getDomeState() != DOME_PARKING)
357 {
359 }
360 }
361 else if (isRoofOpening)
362 {
363 if (Dome::getDomeState() != DOME_UNPARKING)
364 {
366 }
367 }
368 else if (Dome::getDomeState() != DOME_IDLE)
369 {
371 }
372 }
373 else
374 {
375 LOG_ERROR("Error on updateStatus.");
376 }
377 }
378 catch(const std::exception &e)
379 {
380 LOGF_ERROR("Error on updateStatus: %s", e.what());
381 }
382}
383
384void DragonLAIR::openRoof()
385{
386 if (strlen(IPAddressTP[0].getText()) == 0)
387 {
388 LOG_ERROR("IP Address is not set.");
389 return;
390 }
391
392 try
393 {
394 httplib::Client cli(IPAddressTP[0].getText(), 80);
395
396 auto result = cli.Post("/indi/roof/open");
397 if (!result)
398 {
399 LOG_ERROR("Unable to connect.");
400 return;
401 }
402
403 if (result.value().status == 200)
404 {
405 LOG_INFO("Roof is opening...");
406 }
407 else
408 {
409 LOG_ERROR("Error on openRoof.");
410 }
411 }
412 catch(const std::exception &e)
413 {
414 LOGF_ERROR("Error on openRoof: %s", e.what());
415 }
416}
417
418void DragonLAIR::closeRoof()
419{
420 if (strlen(IPAddressTP[0].getText()) == 0)
421 {
422 LOG_ERROR("IP Address is not set.");
423 return;
424 }
425
426 try
427 {
428 httplib::Client cli(IPAddressTP[0].getText(), 80);
429
430 auto result = cli.Post("/indi/roof/close");
431 if (!result)
432 {
433 LOG_ERROR("Unable to connect.");
434 return;
435 }
436
437 if (result.value().status == 200)
438 {
439 LOG_INFO("Roof is closing...");
440 }
441 else
442 {
443 LOG_ERROR("Error on closeRoof.");
444 }
445 }
446 catch(const std::exception &e)
447 {
448 LOGF_ERROR("Error on closeRoof: %s", e.what());
449 }
450}
451
452void DragonLAIR::stopRoof()
453{
454 if (strlen(IPAddressTP[0].getText()) == 0)
455 {
456 LOG_ERROR("IP Address is not set.");
457 return;
458 }
459
460 try
461 {
462 httplib::Client cli(IPAddressTP[0].getText(), 80);
463
464 auto result = cli.Post("/indi/roof/abort");
465 if (!result)
466 {
467 LOG_ERROR("Unable to connect.");
468 return;
469 }
470
471 if (result.value().status == 200)
472 {
473 LOG_INFO("Roof is stopping...");
474 }
475 else
476 {
477 LOG_ERROR("Error on stopRoof.");
478 }
479 }
480 catch(const std::exception &e)
481 {
482 LOGF_ERROR("Error on stopRoof: %s", e.what());
483 }
484}
485
486#define DDA_DISCOVERY_PORT 0xdda
487#define DDA_DISCOVERY_TIMEOUT 2 // Seconds
488#define DDA_DISCOVERY_RECEIVE_BUFFER_SIZE 256
489
490void DragonLAIR::discoverDevices()
491{
492 int s;
493
494 s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
495 if (s == -1)
496 {
497 LOG_ERROR("Error creating socket for discovery.");
498 return;
499 }
500
501 int broadcast = 1;
502 setsockopt(s, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
503
504 struct timeval tv;
505 tv.tv_sec = DDA_DISCOVERY_TIMEOUT;
506 tv.tv_usec = 0;
507 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
508
509 struct sockaddr_in Recv_addr;
510
511 socklen_t len = sizeof(struct sockaddr_in);
512
513 Recv_addr.sin_family = AF_INET;
514 Recv_addr.sin_port = htons(DDA_DISCOVERY_PORT);
515 Recv_addr.sin_addr.s_addr = INADDR_BROADCAST;
516
517 const char* sendMSG = "darkdragons";
518
519 sendto(s, sendMSG, strlen(sendMSG), 0, (sockaddr *)&Recv_addr, sizeof(Recv_addr));
520
522 while (true)
523 {
524 memset(recvbuff, 0, DDA_DISCOVERY_RECEIVE_BUFFER_SIZE);
525
526 int n = recvfrom(s, recvbuff, DDA_DISCOVERY_RECEIVE_BUFFER_SIZE, 0, (sockaddr *)&Recv_addr, &len);
527 if (n < 0)
528 break;
529
530 nlohmann::json doc = nlohmann::json::parse(recvbuff, nullptr, false, true);
531
532 if (!doc.is_discarded() && doc.contains("deviceType"))
533 {
534 char deviceIP[INET_ADDRSTRLEN];
535 inet_ntop(AF_INET, &(Recv_addr.sin_addr), deviceIP, INET_ADDRSTRLEN);
536
537 std::string deviceType = doc["deviceType"];
538 std::string serialNumber = doc["serialNumber"];
539
540 LOGF_INFO("Found %s %s at %s", deviceType.c_str(), serialNumber.c_str(), deviceIP);
541 }
542 }
543
544 close(s);
545
546 DiscoverSwitchSP.reset();
547 DiscoverSwitchSP[0].setState(ISS_OFF);
548 DiscoverSwitchSP.setState(IPS_OK);
549 DiscoverSwitchSP.apply();
550}
virtual bool Abort() override
Abort all dome motion.
virtual IPState Move(DomeDirection dir, DomeMotionCommand operation) override
Move the Dome in a particular direction.
virtual IPState Park() override
Goto Park Position. The park position is an absolute azimuth value.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
bool Disconnect() override
Disconnect from device.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Dome Presets in the configuration file
const char * getDefaultName() override
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual IPState UnPark() override
UnPark dome. The action of the Unpark command is dome specific, but it may include opening the shutte...
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 ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
bool isConnected() const
INDI::PropertyText getText(const char *name) const
bool isDeviceNameMatch(const char *otherName) const
Check that the device name matches the argument.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
void SetDomeCapability(uint32_t cap)
SetDomeCapability set the dome capabilities. All capabilities must be initialized.
void setDomeConnection(const uint8_t &value)
setDomeConnection Set Dome connection mode. Child class should call this in the constructor before Do...
@ DOME_UNPARKING
Definition indidome.h:135
@ DOME_UNPARKED
Definition indidome.h:137
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
Definition indidome.cpp:270
DomeMotionCommand
Definition indidome.h:97
@ MOTION_START
Definition indidome.h:98
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition indidome.cpp:105
@ DOME_CAN_PARK
Definition indidome.h:159
@ DOME_CAN_ABORT
Definition indidome.h:156
bool isLocked()
isLocked, is the dome currently locked?
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition indidome.cpp:284
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition indidome.cpp:503
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
Definition indidome.cpp:841
@ CONNECTION_NONE
Definition indidome.h:171
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Dome Presets in the configuration file
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition indidome.cpp:809
void setDomeState(const DomeState &value)
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
void SetParkDataType(DomeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
virtual IPState Move(DomeDirection dir, DomeMotionCommand operation)
Move the Dome in a particular direction.
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
void fill(const char *device, const char *name, const char *label, const char *group, IPState state)
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)
const Response & value() const
Definition httplib.h:1445
a class to store JSON values
bool contains(const typename object_t::key_type &key) const
check the existence of an element in a JSON object
constexpr bool is_discarded() const noexcept
return whether value is discarded
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
std::unique_ptr< DragonLAIR > dragonlair(new DragonLAIR())
#define DDA_DISCOVERY_TIMEOUT
#define DDA_DISCOVERY_RECEIVE_BUFFER_SIZE
#define DDA_DISCOVERY_PORT
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_ATMOST1
Definition indiapi.h:172
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
#define getDeviceName
std::string body
Definition httplib.h:825