diff --git a/drivers.xml b/drivers.xml
index a3b81593c8..2c83a021e4 100644
--- a/drivers.xml
+++ b/drivers.xml
@@ -839,6 +839,10 @@
indi_wanderercover_v4_ec
1.2
+
+ indi_wanderer_eclipse
+ 1.2
+
indi_dragon_light
1.0
diff --git a/drivers/auxiliary/CMakeLists.txt b/drivers/auxiliary/CMakeLists.txt
index 833a59e5a8..1138122bfd 100644
--- a/drivers/auxiliary/CMakeLists.txt
+++ b/drivers/auxiliary/CMakeLists.txt
@@ -373,3 +373,11 @@ SET(ups_SRC
add_executable(indi_ups_safety ${ups_SRC})
target_link_libraries(indi_ups_safety indidriver)
install(TARGETS indi_ups_safety RUNTIME DESTINATION bin)
+
+# ########## WandererCover V4-EC###############
+SET(indi_wanderer_eclipse_SRC
+ wanderer_eclipse.cpp)
+
+add_executable(indi_wanderer_eclipse ${indi_wanderer_eclipse_SRC})
+target_link_libraries(indi_wanderer_eclipse indidriver)
+install(TARGETS indi_wanderer_eclipse RUNTIME DESTINATION bin)
\ No newline at end of file
diff --git a/drivers/auxiliary/wanderer_eclipse.cpp b/drivers/auxiliary/wanderer_eclipse.cpp
new file mode 100644
index 0000000000..853ea62358
--- /dev/null
+++ b/drivers/auxiliary/wanderer_eclipse.cpp
@@ -0,0 +1,265 @@
+/*******************************************************************************
+ Copyright(c) 2025 Jérémie Klein. All rights reserved.
+
+ Wanderer Eclipse
+
+ 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 "wanderer_eclipse.h"
+#include "indicom.h"
+#include "connectionplugins/connectionserial.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static std::unique_ptr wanderereclipse(new WandererEclipse());
+
+WandererEclipse::WandererEclipse() : DustCapInterface(this)
+{
+ setVersion(1, 0);
+}
+
+const char *WandererEclipse::getDefaultName()
+{
+ return "WandererEclipse";
+}
+
+bool WandererEclipse::initProperties()
+{
+ INDI::DefaultDevice::initProperties();
+
+ // Dust cap interface
+ DI::initProperties(MAIN_CONTROL_TAB);
+ setDriverInterface(AUX_INTERFACE | DUSTCAP_INTERFACE);
+ addAuxControls();
+
+ // Torque property
+ TorqueSP[TORQUE_LOW].fill("TORQUE_LOW", "Low", ISS_OFF);
+ TorqueSP[TORQUE_MEDIUM].fill("TORQUE_MEDIUM", "Medium", ISS_ON);
+ TorqueSP[TORQUE_HIGH].fill("TORQUE_HIGH", "High", ISS_OFF);
+ TorqueSP.fill(getDeviceName(), "TORQUE", "Motor Torque", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
+
+ // Firmware info
+ FirmwareTP[FIRMWARE_VERSION].fill("FIRMWARE_VERSION", "Firmware Version", "Unknown");
+ FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
+
+ setDefaultPollingPeriod(2000);
+
+ serialConnection = new Connection::Serial(this);
+ serialConnection->setDefaultBaudRate(Connection::Serial::B_19200);
+ serialConnection->registerHandshake([&]() {
+ return requestStatus();
+ });
+ registerConnection(serialConnection);
+
+ return true;
+}
+
+bool WandererEclipse::updateProperties()
+{
+ INDI::DefaultDevice::updateProperties();
+
+ if (isConnected())
+ {
+ // Update firmware info
+ char firmwareStr[16];
+ snprintf(firmwareStr, sizeof(firmwareStr), "%d", firmware);
+ FirmwareTP[FIRMWARE_VERSION].setText(firmwareStr);
+ defineProperty(FirmwareTP);
+ defineProperty(TorqueSP);
+ }
+ else
+ {
+ deleteProperty(FirmwareTP);
+ deleteProperty(TorqueSP);
+ }
+
+ DI::updateProperties();
+ return true;
+}
+
+void WandererEclipse::ISGetProperties(const char *dev)
+{
+ INDI::DefaultDevice::ISGetProperties(dev);
+}
+
+bool WandererEclipse::ISSnoopDevice(XMLEle *root)
+{
+ return INDI::DefaultDevice::ISSnoopDevice(root);
+}
+
+bool WandererEclipse::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
+{
+ if (dev && !strcmp(dev, getDeviceName()))
+ {
+ // Torque
+ if (TorqueSP.isNameMatch(name))
+ {
+ int newTorque = -1;
+ for (int i = 0; i < n; i++)
+ {
+ if (states[i] == ISS_ON)
+ {
+ if (strcmp(names[i], "TORQUE_LOW") == 0)
+ newTorque = 0;
+ else if (strcmp(names[i], "TORQUE_MEDIUM") == 0)
+ newTorque = 1;
+ else if (strcmp(names[i], "TORQUE_HIGH") == 0)
+ newTorque = 2;
+ }
+ }
+ if (newTorque != -1 && newTorque != torqueLevel)
+ {
+ // Send torque command: 2000 (low), 2001 (medium), 2002 (high)
+ char cmd[8];
+ snprintf(cmd, sizeof(cmd), "20%d", newTorque);
+ if (sendCommand(cmd))
+ {
+ torqueLevel = newTorque;
+ for (int i = 0; i < 3; i++)
+ TorqueSP[i].setState(i == newTorque ? ISS_ON : ISS_OFF);
+ TorqueSP.setState(IPS_OK);
+ }
+ else
+ {
+ TorqueSP.setState(IPS_ALERT);
+ }
+ TorqueSP.apply();
+ return true;
+ }
+ }
+ }
+ return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
+}
+
+bool WandererEclipse::toggleCover(bool open)
+{
+ char cmd[8] = {0};
+ // 1001 = open, 1000 = close
+ snprintf(cmd, sizeof(cmd), "100%d", open ? 1 : 0);
+ return sendCommand(cmd);
+}
+
+IPState WandererEclipse::ParkCap()
+{
+ ParkCapSP.setState(IPS_BUSY);
+ ParkCapSP.apply();
+ if (toggleCover(false))
+ return IPS_BUSY;
+ ParkCapSP.setState(IPS_ALERT);
+ ParkCapSP.apply();
+ return IPS_ALERT;
+}
+
+IPState WandererEclipse::UnParkCap()
+{
+ ParkCapSP.setState(IPS_BUSY);
+ ParkCapSP.apply();
+ if (toggleCover(true))
+ return IPS_BUSY;
+ ParkCapSP.setState(IPS_ALERT);
+ ParkCapSP.apply();
+ return IPS_ALERT;
+}
+
+bool WandererEclipse::sendCommand(const std::string &command)
+{
+ std::lock_guard lock(serialPortMutex);
+ int nbytes_written = 0, rc = -1;
+ std::string command_termination = "\n";
+ if ((rc = tty_write_string(PortFD, (command + command_termination).c_str(), &nbytes_written)) != TTY_OK)
+ {
+ char errorMessage[MAXRBUF];
+ tty_error_msg(rc, errorMessage, MAXRBUF);
+ LOGF_ERROR("Serial write error: %s", errorMessage);
+ return false;
+ }
+ return true;
+}
+
+bool WandererEclipse::requestStatus()
+{
+ std::lock_guard lock(serialPortMutex);
+ PortFD = serialConnection->getPortFD();
+ tcflush(PortFD, TCIOFLUSH);
+ char buffer[512] = {0};
+ int nbytes_read = 0, rc = -1;
+ // Send status request command: 1500001\n
+ if (!sendCommand("1500001"))
+ return false;
+
+ // Read response (should be a single line)
+ if ((rc = tty_read_section(PortFD, buffer, '\n', 2, &nbytes_read)) != TTY_OK)
+ {
+ if (rc == TTY_TIME_OUT)
+ {
+ LOG_DEBUG("Timeout reading from device, will try again later");
+ return true;
+ }
+ char errorMessage[MAXRBUF];
+ tty_error_msg(rc, errorMessage, MAXRBUF);
+ LOGF_ERROR("Failed to read data from device. Error: %s", errorMessage);
+ return false;
+ }
+ return parseDeviceStatus(buffer);
+}
+
+bool WandererEclipse::parseDeviceStatus(const char *data)
+{
+ // Example: WandererTilterM54A***A***A***A***A\n
+ // TODO: Parse the status string according to the protocol in the image
+ // Set firmware, isOpen, etc.
+ // For now, just log the data
+ LOGF_DEBUG("Status Data: %s", data);
+ // Example parsing (to be implemented as per actual protocol)
+ return true;
+}
+
+void WandererEclipse::updateStatus(bool isOpen, int torqueLevel)
+{
+ // Update internal state and UI if needed
+ this->isOpen = isOpen;
+ this->torqueLevel = torqueLevel;
+ // Update UI properties if needed
+}
+
+void WandererEclipse::TimerHit()
+{
+ if (!isConnected())
+ {
+ SetTimer(getPollingPeriod());
+ return;
+ }
+ // Only request status on timer if needed (not real-time)
+ requestStatus();
+ SetTimer(getPollingPeriod());
+}
+
+bool WandererEclipse::saveConfigItems(FILE *fp)
+{
+ INDI::DefaultDevice::saveConfigItems(fp);
+ TorqueSP.save(fp);
+ return true;
+}
\ No newline at end of file
diff --git a/drivers/auxiliary/wanderer_eclipse.h b/drivers/auxiliary/wanderer_eclipse.h
new file mode 100644
index 0000000000..5b6ecbf4ee
--- /dev/null
+++ b/drivers/auxiliary/wanderer_eclipse.h
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ Copyright(c) 2025 Jérémie Klein. All rights reserved.
+
+ Wanderer Eclipse
+
+ 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 "defaultdevice.h"
+#include "indidustcapinterface.h"
+#include
+
+namespace Connection
+{
+class Serial;
+}
+
+class WandererEclipse : public INDI::DefaultDevice, public INDI::DustCapInterface
+{
+public:
+ WandererEclipse();
+ virtual ~WandererEclipse() = default;
+
+ virtual bool initProperties() override;
+ virtual void ISGetProperties(const char *dev) override;
+ virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override;
+ virtual bool updateProperties() override;
+ virtual bool ISSnoopDevice(XMLEle *root) override;
+
+protected:
+ // From Dust Cap
+ virtual IPState ParkCap() override;
+ virtual IPState UnParkCap() override;
+ const char *getDefaultName() override;
+ virtual bool saveConfigItems(FILE *fp) override;
+ virtual void TimerHit() override;
+
+private:
+ int firmware = 0;
+ bool toggleCover(bool open);
+ bool sendCommand(const std::string &command);
+ bool requestStatus();
+ bool parseDeviceStatus(const char *data);
+ void updateStatus(bool isOpen, int torqueLevel);
+
+ // Torque property (0: low, 1: medium, 2: high)
+ INDI::PropertySwitch TorqueSP{3};
+ enum
+ {
+ TORQUE_LOW,
+ TORQUE_MEDIUM,
+ TORQUE_HIGH,
+ };
+
+ // Cap state
+ bool isOpen = false;
+ int torqueLevel = 1; // Default to medium
+
+ // Firmware information
+ INDI::PropertyText FirmwareTP{1};
+ enum
+ {
+ FIRMWARE_VERSION,
+ };
+
+ int PortFD{ -1 };
+ Connection::Serial *serialConnection{ nullptr };
+ std::timed_mutex serialPortMutex;
+};
\ No newline at end of file
diff --git a/drivers/weather/weather_safety_alpaca.cpp b/drivers/weather/weather_safety_alpaca.cpp
index 4a3058ed4d..f0ca048518 100644
--- a/drivers/weather/weather_safety_alpaca.cpp
+++ b/drivers/weather/weather_safety_alpaca.cpp
@@ -172,14 +172,14 @@ bool WeatherSafetyAlpaca::makeAlpacaRequest(const std::string& path, nlohmann::j
try
{
// Log connection details
- LOGF_INFO("Creating HTTP client for host: %s, port: %s",
+ LOGF_DEBUG("Creating HTTP client for host: %s, port: %s",
ServerAddressTP[0].getText(),
ServerAddressTP[1].getText());
httplib::Client cli(ServerAddressTP[0].getText(), std::stoi(ServerAddressTP[1].getText()));
// Log timeout settings
- LOGF_INFO("Setting timeouts - Connection: %d sec, Read: %d sec",
+ LOGF_DEBUG("Setting timeouts - Connection: %d sec, Read: %d sec",
static_cast(ConnectionSettingsNP[0].getValue()),
static_cast(ConnectionSettingsNP[0].getValue()));
@@ -187,7 +187,7 @@ bool WeatherSafetyAlpaca::makeAlpacaRequest(const std::string& path, nlohmann::j
cli.set_read_timeout(static_cast(ConnectionSettingsNP[0].getValue()));
// Log request details
- LOGF_INFO("Making %s request to path: %s",
+ LOGF_DEBUG("Making %s request to path: %s",
isPut ? "PUT" : "GET",
path.c_str());
@@ -201,16 +201,16 @@ bool WeatherSafetyAlpaca::makeAlpacaRequest(const std::string& path, nlohmann::j
}
// Log response status and headers
- LOGF_INFO("HTTP Status: %d", result->status);
+ LOGF_DEBUG("HTTP Status: %d", result->status);
for (const auto& header : result->headers)
{
- LOGF_INFO("Response Header - %s: %s",
+ LOGF_DEBUG("Response Header - %s: %s",
header.first.c_str(),
header.second.c_str());
}
// Log response body
- LOGF_INFO("Response Body: %s", result->body.c_str());
+ LOGF_DEBUG("Response Body: %s", result->body.c_str());
if (result->status != 200)
{
@@ -221,7 +221,7 @@ bool WeatherSafetyAlpaca::makeAlpacaRequest(const std::string& path, nlohmann::j
response = nlohmann::json::parse(result->body);
// Log parsed JSON response
- LOGF_INFO("Parsed JSON response: %s", response.dump(2).c_str());
+ LOGF_DEBUG("Parsed JSON response: %s", response.dump(2).c_str());
if (response["ErrorNumber"].get() != 0)
{