diff --git a/drivers.xml b/drivers.xml
index 00c597e427..e07ad39f87 100644
--- a/drivers.xml
+++ b/drivers.xml
@@ -843,6 +843,10 @@
indi_dragonlair_dome
1.0
+
+ indi_universalror_dome
+ 0.1
+
diff --git a/drivers/dome/CMakeLists.txt b/drivers/dome/CMakeLists.txt
index 377af11d2f..f2cd19a906 100644
--- a/drivers/dome/CMakeLists.txt
+++ b/drivers/dome/CMakeLists.txt
@@ -82,3 +82,13 @@ SET(dragonlair_SRC
add_executable(indi_dragonlair_dome ${dragonlair_SRC})
target_link_libraries(indi_dragonlair_dome indidriver ${HTTPLIB_LIBRARY})
install(TARGETS indi_dragonlair_dome RUNTIME DESTINATION bin)
+
+# ############### Universal ROR ################
+SET(universalror_SRC
+ universal_ror.cpp
+ universal_ror_client.cpp
+ )
+
+add_executable(indi_universalror_dome ${universalror_SRC})
+target_link_libraries(indi_universalror_dome indidriver indiclient)
+install(TARGETS indi_universalror_dome RUNTIME DESTINATION bin)
diff --git a/drivers/dome/universal_ror.cpp b/drivers/dome/universal_ror.cpp
new file mode 100644
index 0000000000..9b58171c6e
--- /dev/null
+++ b/drivers/dome/universal_ror.cpp
@@ -0,0 +1,423 @@
+/*******************************************************************************
+ Universal Roll Off Roof driver.
+ Copyright(c) 2014 Jasem Mutlaq. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+ .
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*******************************************************************************/
+
+#include "universal_ror.h"
+#include
+#include
+#include
+
+// We declare an auto pointer to UniversalROR.
+std::unique_ptr ror(new UniversalROR());
+
+UniversalROR::UniversalROR()
+{
+ SetDomeCapability(DOME_CAN_ABORT | DOME_CAN_PARK);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::initProperties()
+{
+ INDI::Dome::initProperties();
+ SetParkDataType(PARK_NONE);
+ addAuxControls();
+
+ // Input Indexes
+ InputTP[FullyOpened].fill("FULLY_OPENED", "Fully Opened", "Comma separated indexes");
+ InputTP[FullyClosed].fill("FULLY_CLOSED", "Fully Closed", "Comma separated indexes");
+ InputTP.fill(getDeviceName(), "INPUT_INDEX", "Input Indexes", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
+ InputTP.load();
+
+ // Output Indexes
+ OutputTP[OpenRoof].fill("OPEN_ROOF", "Open Roof", "Comma separated indexes");
+ OutputTP[CloseRoof].fill("CLOSE_ROOF", "Close Roof", "Comma separated indexes");
+ OutputTP.fill(getDeviceName(), "OUTPUT_INDEX", "Output Indexes", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
+ OutputTP.load();
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::ISSnoopDevice(XMLEle *root)
+{
+ return INDI::Dome::ISSnoopDevice(root);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::setupParms()
+{
+ // If we have parking data
+ InitPark();
+
+ // If limit switches are not identical then we have a known
+ // parking state that we should set.
+ if (m_FullClosedLimitSwitch != m_FullOpenLimitSwitch)
+ {
+ if (m_FullClosedLimitSwitch && !isParked())
+ SetParked(true);
+ else if (m_FullOpenLimitSwitch && isParked())
+ SetParked(false);
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::Connect()
+{
+ if (!m_Client || !m_Client->isConnected())
+ {
+ LOG_ERROR("ROR Client is not connected. Specify the input and output drivers in Options tab.");
+ return false;
+
+ }
+
+ syncIndexes();
+ SetTimer(getPollingPeriod());
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::Disconnect()
+{
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+const char *UniversalROR::getDefaultName()
+{
+ return "Universal ROR";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
+{
+ return INDI::Dome::ISNewSwitch(dev, name, states, names, n);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
+{
+ if (dev && !strcmp(dev, getDeviceName()))
+ {
+ // Input Indexes
+ if (InputTP.isNameMatch(name))
+ {
+ InputTP.update(texts, names, n);
+ InputTP.setState(IPS_OK);
+ InputTP.apply();
+ saveConfig(InputTP);
+ syncIndexes();
+ return true;
+ }
+
+ // Output Indexes
+ if (OutputTP.isNameMatch(name))
+ {
+ OutputTP.update(texts, names, n);
+ OutputTP.setState(IPS_OK);
+ OutputTP.apply();
+ saveConfig(OutputTP);
+ syncIndexes();
+ return true;
+ }
+ }
+
+ return INDI::Dome::ISNewText(dev, name, texts, names, n);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::updateProperties()
+{
+ INDI::Dome::updateProperties();
+
+ if (isConnected())
+ {
+ setupParms();
+
+ defineProperty(InputTP);
+ defineProperty(OutputTP);
+ }
+ else
+ {
+ deleteProperty(InputTP);
+ deleteProperty(OutputTP);
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+void UniversalROR::TimerHit()
+{
+ if (!isConnected())
+ return;
+
+ if (DomeMotionSP.getState() == IPS_BUSY)
+ {
+ // Roll off is opening
+ if (DomeMotionSP[DOME_CW].getState() == ISS_ON)
+ {
+ if (m_FullOpenLimitSwitch)
+ {
+ LOG_INFO("Roof is open.");
+ SetParked(false);
+ // Make sure the relays are off
+ if (m_Client)
+ m_Client->stop();
+ return;
+ }
+ }
+ // Roll Off is closing
+ else if (DomeMotionSP[DOME_CCW].getState() == ISS_ON)
+ {
+ if (m_FullClosedLimitSwitch)
+ {
+ LOG_INFO("Roof is closed.");
+ SetParked(true);
+ // Make sure the relays are off
+ if (m_Client)
+ m_Client->stop();
+ return;
+ }
+ }
+ }
+
+ SetTimer(getPollingPeriod());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::saveConfigItems(FILE *fp)
+{
+ INDI::Dome::saveConfigItems(fp);
+ InputTP.save(fp);
+ OutputTP.save(fp);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+IPState UniversalROR::Move(DomeDirection dir, DomeMotionCommand operation)
+{
+ if (operation == MOTION_START)
+ {
+ // 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.
+ if (dir == DOME_CW && m_FullOpenLimitSwitch)
+ {
+ LOG_WARN("Roof is already fully opened.");
+ return IPS_ALERT;
+ }
+ else if (dir == DOME_CCW && m_FullClosedLimitSwitch)
+ {
+ LOG_WARN("Roof is already fully closed.");
+ return IPS_ALERT;
+ }
+ else if (dir == DOME_CCW && INDI::Dome::isLocked())
+ {
+ DEBUG(INDI::Logger::DBG_WARNING, "Cannot close dome when mount is locking. See: Telescope parking policy, in options tab");
+ return IPS_ALERT;
+ }
+
+ if (dir == DOME_CW)
+ {
+ if (m_Client)
+ {
+ m_Client->openRoof();
+ }
+ else
+ {
+ LOG_ERROR("Failed to open roof. ROR Client is not connected!");
+ return IPS_ALERT;
+ }
+ }
+ else
+ {
+ if (m_Client)
+ {
+ m_Client->closeRoof();
+ }
+ else
+ {
+ LOG_ERROR("Failed to close roof. ROR Client is not connected!");
+ return IPS_ALERT;
+ }
+ }
+
+ return IPS_BUSY;
+ }
+
+ return (Dome::Abort() ? IPS_OK : IPS_ALERT);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+IPState UniversalROR::Park()
+{
+ IPState rc = INDI::Dome::Move(DOME_CCW, MOTION_START);
+ if (rc == IPS_BUSY)
+ {
+ LOG_INFO("Roll off is parking...");
+ return IPS_BUSY;
+ }
+ else
+ return IPS_ALERT;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+IPState UniversalROR::UnPark()
+{
+ IPState rc = INDI::Dome::Move(DOME_CW, MOTION_START);
+ if (rc == IPS_BUSY)
+ {
+ LOG_INFO("Roll off is unparking...");
+ return IPS_BUSY;
+ }
+ else
+ return IPS_ALERT;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::Abort()
+{
+ if (m_Client)
+ return m_Client->stop();
+
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+/// Drivers are assumed to be on localhost running at port 7624
+/// Clients connects to localhost:7624 server.
+////////////////////////////////////////////////////////////////////////////////
+void UniversalROR::ActiveDevicesUpdated()
+{
+ auto input = std::string(ActiveDeviceTP[ACTIVE_INPUT].getText());
+ auto output = std::string(ActiveDeviceTP[ACTIVE_OUTPUT].getText());
+
+ // If input OR output drivers are missing, do not initialize client.
+ if (input.empty() || output.empty())
+ return;
+
+ m_Client.reset(new UniversalRORClient(input, output));
+ m_Client->setFullyClosedCallback([&](bool on)
+ {
+ m_FullClosedLimitSwitch = on;
+ });
+ m_Client->setFullyOpenedCallback([&](bool on)
+ {
+ m_FullOpenLimitSwitch = on;
+ });
+ m_Client->watchDevice(input.c_str());
+ m_Client->watchDevice(output.c_str());
+ m_Client->connectServer();
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+bool UniversalROR::syncIndexes()
+{
+ if (!m_Client)
+ return false;
+
+ // Input --> Fully Opened Indexes
+ auto fullyOpenedList = extract(InputTP[FullyOpened].getText());
+ if (!fullyOpenedList.empty() && fullyOpenedList != m_InputFullyOpened)
+ {
+ m_InputFullyOpened = fullyOpenedList;
+ m_Client->setInputFullyOpened(m_InputFullyOpened);
+ }
+
+ // Input --> Fully Closed Indexes
+ auto fullyClosedList = extract(InputTP[FullyClosed].getText());
+ if (!fullyClosedList.empty() && fullyClosedList != m_InputFullyClosed)
+ {
+ m_InputFullyClosed = fullyClosedList;
+ m_Client->setInputFullyClosed(m_InputFullyClosed);
+ }
+
+ // Output --> Open Roof Indexes
+ auto openRoofList = extract(OutputTP[OpenRoof].getText());
+ if (!openRoofList.empty() && openRoofList != m_OutputOpenRoof)
+ {
+ m_OutputOpenRoof = openRoofList;
+ m_Client->setOutputOpenRoof(m_OutputOpenRoof);
+ }
+
+ // Output --> Close Roof Indexes
+ auto closeRoofList = extract(OutputTP[CloseRoof].getText());
+ if (!closeRoofList.empty() && closeRoofList != m_OutputCloseRoof)
+ {
+ m_OutputCloseRoof = closeRoofList;
+ m_Client->setOutputCloseRoof(m_OutputCloseRoof);
+ }
+
+ m_Client->syncFullyOpenedState();
+ m_Client->syncFullyClosedState();
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+////////////////////////////////////////////////////////////////////////////////
+std::vector UniversalROR::extract(const std::string &text)
+{
+ std::vector result;
+ std::regex pattern(R"(\d+)");
+ std::smatch matches;
+ std::string remaining = text;
+
+ while (std::regex_search(remaining, matches, pattern))
+ {
+ result.push_back(std::stoi(matches[0]));
+ remaining = matches.suffix().str();
+ }
+
+ return result;
+}
\ No newline at end of file
diff --git a/drivers/dome/universal_ror.h b/drivers/dome/universal_ror.h
new file mode 100644
index 0000000000..974b25ca41
--- /dev/null
+++ b/drivers/dome/universal_ror.h
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ Universal Roll Off Roof driver.
+ Copyright(c) 2024 Jasem Mutlaq. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+ .
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*******************************************************************************/
+
+#pragma once
+
+#include "indidome.h"
+#include "universal_ror_client.h"
+
+class UniversalROR : public INDI::Dome
+{
+ public:
+ UniversalROR();
+ virtual ~UniversalROR() = default;
+
+ virtual bool initProperties() override;
+ const char *getDefaultName() override;
+ bool updateProperties() override;
+
+ virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override;
+ virtual bool ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n) override;
+ virtual bool saveConfigItems(FILE *fp) override;
+ virtual bool ISSnoopDevice(XMLEle *root) override;
+
+ protected:
+ bool Connect() override;
+ bool Disconnect() override;
+
+ void TimerHit() override;
+
+ virtual IPState Move(DomeDirection dir, DomeMotionCommand operation) override;
+ virtual IPState Park() override;
+ virtual IPState UnPark() override;
+ virtual bool Abort() override;
+ virtual void ActiveDevicesUpdated() override;
+
+ private:
+ bool setupParms();
+ bool syncIndexes();
+ std::vector extract(const std::string &text);
+ bool m_FullOpenLimitSwitch {false}, m_FullClosedLimitSwitch {false};
+
+ INDI::PropertyText InputTP {2};
+ enum
+ {
+ FullyOpened,
+ FullyClosed
+ };
+
+ INDI::PropertyText OutputTP {2};
+ enum
+ {
+ OpenRoof,
+ CloseRoof
+ };
+
+ std::vector m_OutputOpenRoof, m_OutputCloseRoof;
+ std::vector m_InputFullyOpened, m_InputFullyClosed;
+ std::unique_ptr m_Client;
+};
diff --git a/drivers/dome/universal_ror_client.cpp b/drivers/dome/universal_ror_client.cpp
new file mode 100644
index 0000000000..1f0ed16320
--- /dev/null
+++ b/drivers/dome/universal_ror_client.cpp
@@ -0,0 +1,241 @@
+/*******************************************************************************
+ Copyright(c) 2024 Jasem Mutlaq. All rights reserved.
+
+ INDI Universal ROR Client. It connects to INDI server running locally at
+ localhost:7624. It checks for both input and output drivers.
+
+ Output driver is used to command Open, Close, and Stop ROR.
+ Input driver is used to query the fully closed and opened states.
+
+ The client does NOT stop the roof if the limit switches are activated. This
+ is the responsiblity of the external hardware.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ The full GNU General Public License is included in this distribution in the
+ file called LICENSE.
+*******************************************************************************/
+
+#include "universal_ror_client.h"
+
+#include
+#include
+
+///////////////////////////////////////////////////////////////////////////////////////////
+///
+///////////////////////////////////////////////////////////////////////////////////////////
+UniversalRORClient::UniversalRORClient(const std::string &input, const std::string &output) : m_Input(input),
+ m_Output(output)
+{
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+///
+///////////////////////////////////////////////////////////////////////////////////////////
+UniversalRORClient::~UniversalRORClient()
+{
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+///
+///////////////////////////////////////////////////////////////////////////////////////////
+void UniversalRORClient::newDevice(INDI::BaseDevice dp)
+{
+ if (dp.isDeviceNameMatch(m_Input))
+ m_InputReady = true;
+ if (dp.isDeviceNameMatch(m_Output))
+ m_OutputReady = true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+///
+///////////////////////////////////////////////////////////////////////////////////////////
+void UniversalRORClient::newProperty(INDI::Property property)
+{
+ updateProperty(property);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+/// Check if any of the relevant digital inputs are for the fully opened or closed states
+///////////////////////////////////////////////////////////////////////////////////////////
+void UniversalRORClient::updateProperty(INDI::Property property)
+{
+ auto fullyOpenedUpdated = false, fullyClosedUpdated = false;
+ for (const auto &value : m_InputFullyOpened)
+ {
+ const auto name = "DIGITAL_INPUT_" + std::to_string(value);
+ if (property.isNameMatch(name))
+ {
+ fullyOpenedUpdated = true;
+ }
+ }
+
+ if (fullyOpenedUpdated)
+ syncFullyOpenedState();
+
+ for (const auto &value : m_InputFullyClosed)
+ {
+ const auto name = "DIGITAL_INPUT_" + std::to_string(value);
+ if (property.isNameMatch(name))
+ {
+ fullyClosedUpdated = true;
+ }
+ }
+
+ if (fullyClosedUpdated)
+ syncFullyClosedState();
+
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+///
+///////////////////////////////////////////////////////////////////////////////////////////
+bool UniversalRORClient::openRoof()
+{
+ auto device = getDevice(m_Output.c_str());
+ if (!device || !device.isConnected())
+ return false;
+
+ for (const auto &value : m_OutputOpenRoof)
+ {
+ const auto name = "DIGITAL_OUTPUT_" + std::to_string(value);
+ auto property = device.getSwitch(name.c_str());
+ if (property)
+ {
+ property.reset();
+ property[1].setState(ISS_ON);
+ sendNewSwitch(property);
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+///
+///////////////////////////////////////////////////////////////////////////////////////////
+bool UniversalRORClient::closeRoof()
+{
+ auto device = getDevice(m_Output.c_str());
+ if (!device || !device.isConnected())
+ return false;
+
+ for (const auto &value : m_OutputCloseRoof)
+ {
+ const auto name = "DIGITAL_OUTPUT_" + std::to_string(value);
+ auto property = device.getSwitch(name.c_str());
+ if (property)
+ {
+ property.reset();
+ property[1].setState(ISS_ON);
+ sendNewSwitch(property);
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+/// Sets both Close & Open roof outputs to OFF
+///////////////////////////////////////////////////////////////////////////////////////////
+bool UniversalRORClient::stop()
+{
+ auto device = getDevice(m_Output.c_str());
+ if (!device || !device.isConnected())
+ return false;
+
+ // Find all close roof digital outputs and set them to OFF
+ // Only send OFF if required
+ for (const auto &value : m_OutputCloseRoof)
+ {
+ const auto name = "DIGITAL_OUTPUT_" + std::to_string(value);
+ auto property = device.getSwitch(name.c_str());
+ if (property && property[0].getState() != ISS_ON)
+ {
+ property.reset();
+ property[0].setState(ISS_ON);
+ sendNewSwitch(property);
+ }
+ }
+
+ // Same for Open roof digital outputs
+ for (const auto &value : m_OutputOpenRoof)
+ {
+ const auto name = "DIGITAL_OUTPUT_" + std::to_string(value);
+ auto property = device.getSwitch(name.c_str());
+ if (property && property[0].getState() != ISS_ON)
+ {
+ property.reset();
+ property[0].setState(ISS_ON);
+ sendNewSwitch(property);
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+/// Checks fully opened state properties.
+///////////////////////////////////////////////////////////////////////////////////////////
+void UniversalRORClient::syncFullyOpenedState()
+{
+ auto device = getDevice(m_Input.c_str());
+ if (!device || !device.isConnected())
+ return;
+
+ std::vector fullyOpenedStates;
+ for (const auto &value : m_InputFullyOpened)
+ {
+ const auto name = "DIGITAL_INPUT_" + std::to_string(value);
+ auto property = device.getSwitch(name.c_str());
+ if (property)
+ {
+ auto toggled = property[1].getState() == ISS_ON;
+ fullyOpenedStates.push_back(toggled);
+ }
+ }
+
+ auto on = std::all_of(fullyOpenedStates.begin(), fullyOpenedStates.end(), [](bool value)
+ {
+ return value;
+ });
+ m_FullyOpenedCallback(on);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+///
+///////////////////////////////////////////////////////////////////////////////////////////
+void UniversalRORClient::syncFullyClosedState()
+{
+ auto device = getDevice(m_Input.c_str());
+ if (!device || !device.isConnected())
+ return;
+
+ std::vector fullyClosedStates;
+ for (const auto &value : m_InputFullyClosed)
+ {
+ const auto name = "DIGITAL_INPUT_" + std::to_string(value);
+ auto property = device.getSwitch(name.c_str());
+ if (property)
+ {
+ auto toggled = property[1].getState() == ISS_ON;
+ fullyClosedStates.push_back(toggled);
+ }
+ }
+
+ auto on = std::all_of(fullyClosedStates.begin(), fullyClosedStates.end(), [](bool value)
+ {
+ return value;
+ });
+ m_FullyClosedCallback(on);
+}
\ No newline at end of file
diff --git a/drivers/dome/universal_ror_client.h b/drivers/dome/universal_ror_client.h
new file mode 100644
index 0000000000..210c94d5b2
--- /dev/null
+++ b/drivers/dome/universal_ror_client.h
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ Copyright(c) 2024 Jasem Mutlaq. All rights reserved.
+
+ INDI Universal ROR Client. It connects to INDI server running locally at
+ localhost:7624. It checks for both input and output drivers.
+
+ Output driver is used to command Open, Close, and Stop ROR.
+ Input driver is used to query the fully closed and opened states.
+
+ The client does NOT stop the roof if the limit switches are activated. This
+ is the responsiblity of the external hardware.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ The full GNU General Public License is included in this distribution in the
+ file called LICENSE.
+*******************************************************************************/
+
+#pragma once
+
+#include "baseclient.h"
+#include "basedevice.h"
+
+class UniversalRORClient : public INDI::BaseClient
+{
+ public:
+ UniversalRORClient(const std::string &input, const std::string &output);
+ ~UniversalRORClient();
+
+ bool isConnected()
+ {
+ return m_InputReady && m_OutputReady;
+ }
+
+ bool openRoof();
+ bool closeRoof();
+ bool stop();
+
+ void setOutputOpenRoof(const std::vector &value)
+ {
+ m_OutputOpenRoof = value;
+ }
+
+ void setOutputCloseRoof(const std::vector &value)
+ {
+ m_OutputCloseRoof = value;
+ }
+
+ void setInputFullyOpened(const std::vector &value)
+ {
+ m_InputFullyOpened = value;
+ }
+
+ void setInputFullyClosed(const std::vector &value)
+ {
+ m_InputFullyClosed = value;
+ }
+
+ void setFullyOpenedCallback(std::function callback)
+ {
+ m_FullyOpenedCallback = callback;
+ }
+
+ void setFullyClosedCallback(std::function callback)
+ {
+ m_FullyClosedCallback = callback;
+ }
+
+ void syncFullyOpenedState();
+ void syncFullyClosedState();
+
+ protected:
+ virtual void newDevice(INDI::BaseDevice dp) override;
+ virtual void newProperty(INDI::Property property) override;
+ virtual void updateProperty(INDI::Property property) override;
+
+ private:
+ std::string m_Input, m_Output;
+ bool m_InputReady {false}, m_OutputReady {false};
+ std::vector m_OutputOpenRoof, m_OutputCloseRoof;
+ std::vector m_InputFullyOpened, m_InputFullyClosed;
+ std::function m_FullyOpenedCallback, m_FullyClosedCallback;
+};
diff --git a/libs/indibase/indidome.cpp b/libs/indibase/indidome.cpp
index 7be9b156c1..938aad597e 100644
--- a/libs/indibase/indidome.cpp
+++ b/libs/indibase/indidome.cpp
@@ -119,10 +119,12 @@ bool Dome::initProperties()
PresetGotoSP.fill(getDeviceName(), "Goto", "", "Presets", IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
// Active Devices
- ActiveDeviceTP[0].fill("ACTIVE_TELESCOPE", "Telescope", "Telescope Simulator");
- //IUFillText(&ActiveDeviceT[1], "ACTIVE_WEATHER", "Weather", "WunderGround");
+ ActiveDeviceTP[ACTIVE_MOUNT].fill("ACTIVE_TELESCOPE", "Telescope", "Telescope Simulator");
+ ActiveDeviceTP[ACTIVE_INPUT].fill("ACTIVE_INPUT", "Input", "");
+ ActiveDeviceTP[ACTIVE_OUTPUT].fill("ACTIVE_OUTPUT", "Output", "");
ActiveDeviceTP.fill(getDeviceName(), "ACTIVE_DEVICES", "Snoop devices", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
- ActiveDeviceTP.load();
+ if (ActiveDeviceTP.load())
+ ActiveDevicesUpdated();
// Use locking if telescope is unparked
MountPolicySP[MOUNT_IGNORED].fill("MOUNT_IGNORED", "Mount ignored", ISS_ON);
@@ -793,14 +795,16 @@ bool Dome::ISNewText(const char * dev, const char * name, char * texts[], char *
ActiveDeviceTP.update(texts, names, n);
ActiveDeviceTP.apply();
- const auto scope = ActiveDeviceTP[0].getText();
- IDSnoopDevice(scope, "EQUATORIAL_EOD_COORD");
- IDSnoopDevice(scope, "TARGET_EOD_COORD");
- IDSnoopDevice(scope, "GEOGRAPHIC_COORD");
- IDSnoopDevice(scope, "TELESCOPE_PARK");
+ auto mount = ActiveDeviceTP[ACTIVE_MOUNT].getText();
+ IDSnoopDevice(mount, "EQUATORIAL_EOD_COORD");
+ IDSnoopDevice(mount, "TARGET_EOD_COORD");
+ IDSnoopDevice(mount, "GEOGRAPHIC_COORD");
+ IDSnoopDevice(mount, "TELESCOPE_PARK");
if (CanAbsMove())
- IDSnoopDevice(scope, "TELESCOPE_PIER_SIDE");
+ IDSnoopDevice(mount, "TELESCOPE_PIER_SIDE");
+
saveConfig(ActiveDeviceTP);
+ ActiveDevicesUpdated();
return true;
}
}
diff --git a/libs/indibase/indidome.h b/libs/indibase/indidome.h
index e3f94c43e0..9c55d3d60c 100644
--- a/libs/indibase/indidome.h
+++ b/libs/indibase/indidome.h
@@ -521,6 +521,11 @@ class Dome : public DefaultDevice
/** \brief perform handshake with device to check communication */
virtual bool Handshake();
+ /**
+ * Signal to concrete driver when Active Devices are updated.
+ */
+ virtual void ActiveDevicesUpdated() {};
+
double Csc(double x);
double Sec(double x);
@@ -546,7 +551,13 @@ class Dome : public DefaultDevice
INDI::PropertySwitch ParkOptionSP {3};
- INDI::PropertyText ActiveDeviceTP {1};
+ INDI::PropertyText ActiveDeviceTP {3};
+ enum
+ {
+ ACTIVE_MOUNT,
+ ACTIVE_INPUT,
+ ACTIVE_OUTPUT
+ };
// Switch to lock id mount is unparked
INDI::PropertySwitch MountPolicySP {2};