Instrument Neutral Distributed Interface INDI 2.2.5
universal_ror.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Universal Roll Off Roof driver.
3 Copyright(c) 2014 Jasem Mutlaq. All rights reserved.
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
8 .
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13 .
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18*******************************************************************************/
19
20#include "universal_ror.h"
21#include <memory>
22#include <functional>
23#include <regex>
24
25// We declare an auto pointer to UniversalROR.
26std::unique_ptr<UniversalROR> ror(new UniversalROR());
27
34
39{
43
44 // Input Indexes
45 InputTP[FullyOpened].fill("FULLY_OPENED", "Fully Opened", "Comma separated indexes");
46 InputTP[FullyClosed].fill("FULLY_CLOSED", "Fully Closed", "Comma separated indexes");
47 InputTP.fill(getDeviceName(), "INPUT_INDEX", "Input Indexes", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
48 InputTP.load();
49
50 // Limit Switch Indicators
51 LimitSwitchLP[FullyOpened].fill("FULLY_OPENED", "Fully Opened", IPS_IDLE);
52 LimitSwitchLP[FullyClosed].fill("FULLY_CLOSED", "Fully Closed", IPS_IDLE);
53 LimitSwitchLP.fill(getDeviceName(), "LIMIT_SWITCHES", "Limit Switches", MAIN_CONTROL_TAB, IPS_IDLE);
54
55 // Output Indexes
56 OutputTP[OpenRoof].fill("OPEN_ROOF", "Open Roof", "Comma separated indexes");
57 OutputTP[CloseRoof].fill("CLOSE_ROOF", "Close Roof", "Comma separated indexes");
58 OutputTP.fill(getDeviceName(), "OUTPUT_INDEX", "Output Indexes", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
59 OutputTP.load();
60
61 return true;
62}
63
71
75bool UniversalROR::setupParms()
76{
77 // If we have parking data
78 InitPark();
79
80 // If limit switches are not identical then we have a known
81 // parking state that we should set.
82 if (m_FullClosedLimitSwitch != m_FullOpenLimitSwitch)
83 {
84 if (m_FullClosedLimitSwitch && !isParked())
85 SetParked(true);
86 else if (m_FullOpenLimitSwitch && isParked())
87 SetParked(false);
88 }
89
90 return true;
91}
92
96// Connection status monitoring function
97void UniversalROR::checkConnectionStatus()
98{
99 if (!m_Client)
100 return;
101
102 if (!m_Client->isConnected())
103 {
104 m_ConnectionAttempts++;
105 if (m_ConnectionAttempts >= MAX_CONNECTION_ATTEMPTS)
106 {
107 LOG_ERROR("Devices did not connect within the timeout period. Please check your configuration.");
108 m_ConnectionAttempts = 0;
109 // Stop the connection timer
110 m_ConnectionTimer.stop();
111 }
112 else
113 {
114 // Continue checking every 5 seconds
115 m_ConnectionTimer.start(5000);
116 }
117 }
118 else
119 {
120 // We're connected, stop the timer
121 m_ConnectionAttempts = 0;
122 m_ConnectionTimer.stop();
123 }
124}
125
127{
128 // Reset connection attempts counter
129 m_ConnectionAttempts = 0;
130
131 // Check if client is initialized and connected
132 if (!m_Client || !m_Client->isConnected())
133 {
134 // If the client is already initialized, let's connect server again
135 if (m_Client)
136 m_Client->connectServer();
137 // Otherwise need to initialize the client again completely
138 else
140
141 // Check again if that worked.
142 if (!m_Client)
143 {
144 LOG_ERROR("ROR Client is not initialized. Specify the input and output drivers in Options tab.");
145 return false;
146 }
147
148 // If client is initialized but not connected, we'll continue anyway
149 // The connection callback will handle syncing indexes when devices connect
150 if (!m_Client->isConnected())
151 {
152 LOG_INFO("ROR Client initialized but devices not connected yet. Will sync when devices connect.");
153
154 // Start a separate timer to monitor connection status
155 m_ConnectionTimer.stop(); // Stop any existing timer
156 m_ConnectionTimer.callOnTimeout(std::bind(&UniversalROR::checkConnectionStatus, this));
157 m_ConnectionTimer.start(5000);
158 return true;
159 }
160 }
161
162 // Only if all three conditions are met (client connected, input device connected, output device connected)
163 // which is what m_Client->isConnected() checks, then we can sync indexes
164 if (m_Client->isConnected())
165 {
166 LOG_INFO("All devices connected. Syncing indexes...");
167 syncIndexes();
169 }
170 else
171 {
172 LOG_INFO("Waiting for devices to connect...");
173 // Don't set the regular timer yet - it will be set when devices connect
174 }
175 return true;
176}
177
182{
183 // Stop the connection timer if it's running
184 m_ConnectionTimer.stop();
185
186 m_InputFullyOpened.clear();
187 m_InputFullyClosed.clear();
188 return true;
189}
190
195{
196 return "Universal ROR";
197}
198
202bool UniversalROR::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
203{
204 return INDI::Dome::ISNewSwitch(dev, name, states, names, n);
205}
206
210bool UniversalROR::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
211{
212 if (dev && !strcmp(dev, getDeviceName()))
213 {
214 // Input Indexes
215 if (InputTP.isNameMatch(name))
216 {
217 InputTP.update(texts, names, n);
218 InputTP.setState(IPS_OK);
219 InputTP.apply();
220 saveConfig(InputTP);
221
222 // Only sync indexes if all devices are connected
223 if (m_Client && m_Client->isConnected())
224 syncIndexes();
225 else
226 LOG_INFO("Indexes updated. Will sync when devices connect.");
227
228 return true;
229 }
230
231 // Output Indexes
232 if (OutputTP.isNameMatch(name))
233 {
234 OutputTP.update(texts, names, n);
235 OutputTP.setState(IPS_OK);
236 OutputTP.apply();
237 saveConfig(OutputTP);
238
239 // Only sync indexes if all devices are connected
240 if (m_Client && m_Client->isConnected())
241 syncIndexes();
242 else
243 LOG_INFO("Indexes updated. Will sync when devices connect.");
244
245 return true;
246 }
247 }
248
249 return INDI::Dome::ISNewText(dev, name, texts, names, n);
250}
251
256{
258
259 if (isConnected())
260 {
261 setupParms();
262
263 defineProperty(InputTP);
264 defineProperty(OutputTP);
265 defineProperty(LimitSwitchLP);
266 }
267 else
268 {
269 deleteProperty(InputTP);
270 deleteProperty(OutputTP);
271 deleteProperty(LimitSwitchLP);
272 }
273
274 return true;
275}
276
281{
282 if (!isConnected())
283 return;
284
285 // In case the limit switch status is unknown, try to sync them up from client.
286 // Only do this if the client is fully connected to both input and output devices
287 if (m_FullClosedLimitSwitch == m_FullOpenLimitSwitch && m_Client && m_Client->isConnected())
288 {
289 // We know the client is connected, but we'll still check the return values
290 // to avoid logging errors for expected conditions
291 m_Client->syncFullyOpenedState();
292 m_Client->syncFullyClosedState();
293 }
294
296 {
297 // Roll off is opening
298 if (DomeMotionSP[DOME_CW].getState() == ISS_ON)
299 {
300 if (m_FullOpenLimitSwitch)
301 {
302 LOG_INFO("Roof is open.");
303 SetParked(false);
304 // Make sure the relays are off
305 if (m_Client)
306 m_Client->stop();
307 }
308 }
309 // Roll Off is closing
310 else if (DomeMotionSP[DOME_CCW].getState() == ISS_ON)
311 {
312 if (m_FullClosedLimitSwitch)
313 {
314 LOG_INFO("Roof is closed.");
315 SetParked(true);
316 // Make sure the relays are off
317 if (m_Client)
318 m_Client->stop();
319 }
320 }
321 }
322 // If state is not known and park is set, unpark.
323 else if (isParked() && m_FullClosedLimitSwitch == false && m_FullOpenLimitSwitch == false)
324 SetParked(false);
325
327}
328
333{
335 InputTP.save(fp);
336 OutputTP.save(fp);
337 return true;
338}
339
344{
345 if (operation == MOTION_START)
346 {
347 // DOME_CW --> OPEN. If can we are ask to "open" while we are fully opened as the limit switch indicates, then we simply return false.
348 if (dir == DOME_CW && m_FullOpenLimitSwitch)
349 {
350 LOG_WARN("Roof is already fully opened.");
351 return IPS_ALERT;
352 }
353 else if (dir == DOME_CCW && m_FullClosedLimitSwitch)
354 {
355 LOG_WARN("Roof is already fully closed.");
356 return IPS_ALERT;
357 }
358 else if (dir == DOME_CCW && INDI::Dome::isLocked())
359 {
360 DEBUG(INDI::Logger::DBG_WARNING, "Cannot close dome when mount is locking. See: Telescope parking policy, in options tab");
361 return IPS_ALERT;
362 }
363
364 if (dir == DOME_CW)
365 {
366 if (m_Client)
367 {
368 m_Client->openRoof();
369 }
370 else
371 {
372 LOG_ERROR("Failed to open roof. ROR Client is not connected!");
373 return IPS_ALERT;
374 }
375 }
376 else
377 {
378 if (m_Client)
379 {
380 m_Client->closeRoof();
381 }
382 else
383 {
384 LOG_ERROR("Failed to close roof. ROR Client is not connected!");
385 return IPS_ALERT;
386 }
387 }
388
389 return IPS_BUSY;
390 }
391
392 return (Dome::Abort() ? IPS_OK : IPS_ALERT);
393}
394
399{
401 if (rc == IPS_BUSY)
402 {
403 LOG_INFO("Roll off is parking...");
404 return IPS_BUSY;
405 }
406 else
407 return IPS_ALERT;
408}
409
414{
416 if (rc == IPS_BUSY)
417 {
418 LOG_INFO("Roll off is unparking...");
419 return IPS_BUSY;
420 }
421 else
422 return IPS_ALERT;
423}
424
429{
430 if (m_Client)
431 return m_Client->stop();
432
433 return false;
434}
435
436
442{
443 auto input = std::string(ActiveDeviceTP[ACTIVE_INPUT].getText());
444 auto output = std::string(ActiveDeviceTP[ACTIVE_OUTPUT].getText());
445
446 // If input OR output drivers are missing, do not initialize client.
447 if (input.empty() || output.empty())
448 return;
449
450 // If there is no change, return
451 if (m_Client && m_Client->inputDevice() == input && m_Client->outputDevice() == output)
452 return;
453
454 m_Client.reset(new UniversalRORClient(input, output));
455 m_Client->setFullyClosedCallback([&](bool on)
456 {
457 m_FullClosedLimitSwitch = on;
458 LimitSwitchLP[FullyClosed].setState(on ? IPS_OK : IPS_IDLE);
459 LimitSwitchLP.apply();
460 });
461 m_Client->setFullyOpenedCallback([&](bool on)
462 {
463 m_FullOpenLimitSwitch = on;
464 LimitSwitchLP[FullyOpened].setState(on ? IPS_OK : IPS_IDLE);
465 LimitSwitchLP.apply();
466 });
467 m_Client->setConnectionCallback([&](bool connected)
468 {
469 if (connected)
470 {
471 LOG_INFO("Devices connected. Syncing indexes...");
472 syncIndexes();
474 }
475 });
476 m_Client->watchDevice(input.c_str());
477 m_Client->watchDevice(output.c_str());
478 m_Client->connectServer();
479
480}
481
485bool UniversalROR::syncIndexes()
486{
487 if (!m_Client)
488 return false;
489
490 // Input --> Fully Opened Indexes
491 auto fullyOpenedList = extract(InputTP[FullyOpened].getText());
492 if (!fullyOpenedList.empty() && fullyOpenedList != m_InputFullyOpened)
493 {
494 m_InputFullyOpened = fullyOpenedList;
495 m_Client->setInputFullyOpened(m_InputFullyOpened);
496 }
497
498 // Input --> Fully Closed Indexes
499 auto fullyClosedList = extract(InputTP[FullyClosed].getText());
500 if (!fullyClosedList.empty() && fullyClosedList != m_InputFullyClosed)
501 {
502 m_InputFullyClosed = fullyClosedList;
503 m_Client->setInputFullyClosed(m_InputFullyClosed);
504 }
505
506 // Output --> Open Roof Indexes
507 auto openRoofList = extract(OutputTP[OpenRoof].getText());
508 if (!openRoofList.empty() && openRoofList != m_OutputOpenRoof)
509 {
510 m_OutputOpenRoof = openRoofList;
511 m_Client->setOutputOpenRoof(m_OutputOpenRoof);
512 }
513
514 // Output --> Close Roof Indexes
515 auto closeRoofList = extract(OutputTP[CloseRoof].getText());
516 if (!closeRoofList.empty() && closeRoofList != m_OutputCloseRoof)
517 {
518 m_OutputCloseRoof = closeRoofList;
519 m_Client->setOutputCloseRoof(m_OutputCloseRoof);
520 }
521
522 return true;
523}
524
528std::vector<uint8_t> UniversalROR::extract(const std::string &text)
529{
530 std::vector<uint8_t> result;
531 std::regex pattern(R"(\d+)");
532 std::smatch matches;
533 std::string remaining = text;
534
535 while (std::regex_search(remaining, matches, pattern))
536 {
537 result.push_back(std::stoi(matches[0]));
538 remaining = matches.suffix().str();
539 }
540
541 return result;
542}
bool isConnected() const
INDI::PropertyText getText(const char *name) const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
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.
uint32_t getPollingPeriod() const
getPollingPeriod Return the polling period.
void defineProperty(INumberVectorProperty *property)
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...
bool isParked()
isParked is dome currently parked?
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
INDI::PropertySwitch DomeMotionSP
Definition indidome.h:534
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
@ ACTIVE_OUTPUT
Definition indidome.h:559
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.
INDI::PropertyText ActiveDeviceTP
Definition indidome.h:554
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
bool load()
load Attempt to load property values from configuration file.
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)
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)
void callOnTimeout(const std::function< void()> &callback)
Definition inditimer.cpp:76
void start()
Starts or restarts the timer with the timeout specified in interval.
Definition inditimer.cpp:82
void stop()
Stops the timer.
Definition inditimer.cpp:97
bool Disconnect() override
Disconnect from device.
const char * getDefaultName() override
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool Abort() override
Abort all dome motion.
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 Park() override
Goto Park Position. The park position is an absolute azimuth value.
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual IPState UnPark() override
UnPark dome. The action of the Unpark command is dome specific, but it may include opening the shutte...
virtual void ActiveDevicesUpdated() override
virtual IPState Move(DomeDirection dir, DomeMotionCommand operation) override
Move the Dome in a particular direction.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Dome Presets in the configuration file
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
ISState
Switch state.
Definition indiapi.h:148
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
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
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
Definition indilogger.h:56
#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 LOG_INFO(txt)
Definition indilogger.h:74
#define getDeviceName
std::unique_ptr< UniversalROR > ror(new UniversalROR())