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) {