From 5354d4a470f5d4cca6f9e8d204d6fd832da06cc9 Mon Sep 17 00:00:00 2001 From: Frank Wang <103447783+WandererAstro@users.noreply.github.com> Date: Sun, 18 Feb 2024 12:27:19 +0800 Subject: [PATCH] Driver for WandererCover V4-EC added (#1998) Co-authored-by: WandererAstro --- drivers.xml | 4 + drivers/auxiliary/CMakeLists.txt | 8 + drivers/auxiliary/wanderer_cover_v4_ec.cpp | 422 +++++++++++++++++++++ drivers/auxiliary/wanderer_cover_v4_ec.h | 118 ++++++ 4 files changed, 552 insertions(+) create mode 100644 drivers/auxiliary/wanderer_cover_v4_ec.cpp create mode 100644 drivers/auxiliary/wanderer_cover_v4_ec.h diff --git a/drivers.xml b/drivers.xml index 7a1f10f910..0914131b28 100644 --- a/drivers.xml +++ b/drivers.xml @@ -753,6 +753,10 @@ indi_wandererbox_plus_v3 1.0 + + indi_wanderercover_v4_ec + 1.0 + diff --git a/drivers/auxiliary/CMakeLists.txt b/drivers/auxiliary/CMakeLists.txt index 6bf1480ad9..c3be1ea67e 100644 --- a/drivers/auxiliary/CMakeLists.txt +++ b/drivers/auxiliary/CMakeLists.txt @@ -1,6 +1,14 @@ IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") INSTALL(FILES 99-indi_auxiliary.rules DESTINATION ${UDEVRULES_INSTALL_DIR}) ENDIF() +# ########## WandererCover V4-EC############### +SET(indi_wanderercover_v4_ec_SRC + wanderer_cover_v4_ec.cpp) + +add_executable(indi_wanderercover_v4_ec ${indi_wanderercover_v4_ec_SRC}) +target_link_libraries(indi_wanderercover_v4_ec indidriver) +install(TARGETS indi_wanderercover_v4_ec RUNTIME DESTINATION bin) + # ########## Wanderer Box Plus V3 ############### SET(indi_wandererbox_plus_v3_SRC wandererbox_plus_v3.cpp) diff --git a/drivers/auxiliary/wanderer_cover_v4_ec.cpp b/drivers/auxiliary/wanderer_cover_v4_ec.cpp new file mode 100644 index 0000000000..9d9da4f318 --- /dev/null +++ b/drivers/auxiliary/wanderer_cover_v4_ec.cpp @@ -0,0 +1,422 @@ +/******************************************************************************* + Copyright(c) 2024 Frank Wang. All rights reserved. + + WandererCover V4-EC + + 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_cover_v4_ec.h" +#include "indicom.h" +#include "connectionplugins/connectionserial.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// We declare an auto pointer to WandererCoverV4EC. +static std::unique_ptr wanderercoverv4ec(new WandererCoverV4EC()); + + + +WandererCoverV4EC::WandererCoverV4EC() +{ + setVersion(1, 0); +} + +bool WandererCoverV4EC::initProperties() +{ + + INDI::DefaultDevice::initProperties(); + setDriverInterface(AUX_INTERFACE); + addAuxControls(); + + //Data read + DataNP[closeset_read].fill( "Closed_Position", "Closed Position Set(°)", "%4.2f", 0, 999, 100, 0); + DataNP[openset_read].fill( "Open_Position", "Open Position Set(°)", "%4.2f", 0, 999, 100, 0); + DataNP[position_read].fill( "Current_Position", "Current Position(°)", "%4.2f", 0, 999, 100, 0); + DataNP[voltage_read].fill( "Voltage", "Voltage (V)", "%4.2f", 0, 999, 100, 0); + DataNP.fill(getDeviceName(), "STATUS", "Real Time Status",MAIN_CONTROL_TAB, IP_RO,60, IPS_IDLE); + + // Open&Close Control + OCcontrolSP[Open].fill( "Open", "Open", ISS_OFF); + OCcontrolSP[Close].fill( "Close", "Close", ISS_ON); + OCcontrolSP.fill(getDeviceName(), "Move Cover", "Move Cover", MAIN_CONTROL_TAB, IP_RW,ISR_ATMOST1, 60, IPS_IDLE); + + // Light + SetLightNP[Light].fill( "Flat_Light", "PWM", "%.2f", 0, 255, 5, 0); + SetLightNP.fill(getDeviceName(), "Flat_Light", "Flat Light", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); + + // Heater + SetHeaterNP[Heat].fill( "Heater", "PWM", "%.2f", 0, 150, 50, 0); + SetHeaterNP.fill(getDeviceName(), "Heater", "Dew Heater", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); + + // Close Set + CloseSetNP[CloseSet].fill( "CloseSet", "10-90", "%.2f", 10, 90, 0.01, 20); + CloseSetNP.fill(getDeviceName(), "CloseSet", "Close Position(°)", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); + + // Open Set + OpenSetNP[OpenSet].fill( "OpenSet", "100-300", "%.2f", 100, 300, 0.01, 150); + OpenSetNP.fill(getDeviceName(), "OpenSet", "Open Position(°)", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); + + serialConnection = new Connection::Serial(this); + serialConnection->setDefaultBaudRate(Connection::Serial::B_19200); + serialConnection->registerHandshake([&]() + { + return getData(); + }); + registerConnection(serialConnection); + + return true; +} + +bool WandererCoverV4EC::getData() +{ + try + { + PortFD = serialConnection->getPortFD(); + tcflush(PortFD, TCIOFLUSH); + int nbytes_read_name = 0,rc=-1; + char name[64] = {0}; + + //Device Model////////////////////////////////////////////////////////////////////////////////////////////////////// + if ((rc = tty_read_section(PortFD, name, 'A', 3, &nbytes_read_name)) != TTY_OK) + { + char errorMessage[MAXRBUF]; + tty_error_msg(rc, errorMessage, MAXRBUF); + if(Ismoving==false) + { + LOGF_INFO("No data received, the device may not be WandererCover V4-EC, please check the serial port!","Updated"); + LOGF_ERROR("Device read error: %s", errorMessage); + } + return false; + } + name[nbytes_read_name - 1] = '\0'; + if(strcmp(name, "ZXWBProV3")==0||strcmp(name, "ZXWBPlusV3")==0||strcmp(name, "UltimateV2")==0||strcmp(name, "PlusV2")==0) + { + LOGF_INFO("The device is not WandererCover V4-EC!","Updated"); + return false; + } + if(strcmp(name, "WandererCoverV4")!=0) + throw std::exception(); + // Frimware version///////////////////////////////////////////////////////////////////////////////////////////// + int nbytes_read_version = 0; + char version[64] = {0}; + tty_read_section(PortFD, version, 'A', 5, &nbytes_read_version); + + version[nbytes_read_version - 1] = '\0'; + firmware=std::atoi(version); + // Close position set////////////////////////////////////////////////////////////////////////////////////////// + char closeset[64] = {0}; + int nbytes_read_closeset= 0; + tty_read_section(PortFD, closeset, 'A', 5, &nbytes_read_closeset); + closeset[nbytes_read_closeset - 1] = '\0'; + closesetread = std::strtod(closeset,NULL); + + // Open position set////////////////////////////////////////////////////////////////////////////////////////// + char openset[64] = {0}; + int nbytes_read_openset= 0; + tty_read_section(PortFD, openset, 'A', 5, &nbytes_read_openset); + openset[nbytes_read_openset - 1] = '\0'; + opensetread = std::strtod(openset,NULL); + // Current Position////////////////////////////////////////////////////////////////////////////////////////// + char position[64] = {0}; + int nbytes_read_position= 0; + tty_read_section(PortFD, position, 'A', 5, &nbytes_read_position); + position[nbytes_read_position - 1] = '\0'; + positionread = std::strtod(position,NULL); + + // Voltage////////////////////////////////////////////////////////////////////////////////////////// + char voltage[64] = {0}; + int nbytes_read_voltage= 0; + tty_read_section(PortFD, voltage, 'A', 5, &nbytes_read_voltage); + voltage[nbytes_read_voltage - 1] = '\0'; + voltageread = std::strtod(voltage,NULL); + updateData(closesetread,opensetread,positionread,voltageread); + + if(voltageread<=7) + { + LOGF_ERROR("No power input!","failed"); + } + + Ismoving=false; + OCcontrolSP.setState( IPS_OK); + } + catch(std::exception& e) + { + //LOGF_INFO("Data read failed","failed"); + } + return true; +} + +void WandererCoverV4EC::updateData(double closesetread,double opensetread,double positionread,double voltageread) +{ + DataNP[closeset_read].setValue(closesetread); + DataNP[openset_read].setValue(opensetread); + DataNP[position_read].setValue(positionread); + DataNP[voltage_read].setValue(voltageread); + DataNP.setState(IPS_OK); + DataNP.apply(); + + OCcontrolSP[Open].setState( (positionread+10>=opensetread) ? ISS_ON : ISS_OFF); + OCcontrolSP[Close].setState( (positionread-10<=closesetread) ? ISS_ON : ISS_OFF); + OCcontrolSP.setState((OCcontrolSP[Open].getState()==ISS_ON||OCcontrolSP[Close].getState()==ISS_ON) ? IPS_OK : IPS_IDLE); + OCcontrolSP.apply(); +} + + + + +bool WandererCoverV4EC::updateProperties() +{ + INDI::DefaultDevice::updateProperties(); + + if (isConnected()) + { + + if(firmware>=20240101) + { + LOGF_INFO("Firmware version: %d", firmware); + } + else + { + LOGF_INFO("Firmware version: %d", firmware); + LOGF_INFO("New firmware available!","failed"); + } + + defineProperty(DataNP); + defineProperty(SetLightNP); + defineProperty(SetHeaterNP); + + defineProperty(CloseSetNP); + defineProperty(OpenSetNP); + + defineProperty(OCcontrolSP); + } + else + { + + deleteProperty(DataNP); + deleteProperty(SetLightNP); + deleteProperty(SetHeaterNP); + deleteProperty(OpenSetNP); + deleteProperty(CloseSetNP); + deleteProperty(OCcontrolSP); + + } + return true; +} + +bool WandererCoverV4EC::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) +{ + + // Open&Close + if (OCcontrolSP.isNameMatch(name)) + { + if(DataNP[voltage_read].value<=7) + { + LOGF_ERROR("No power input!","failed"); + OCcontrolSP.setState(IPS_ALERT); + return false; + } + OCcontrolSP.update(states, names, n); + OCcontrolSP.setState(IPS_ALERT); + char cmd[128] = {0}; + snprintf(cmd, 128, "100%d", (OCcontrolSP[Open].getState() == ISS_ON) ? 1 : 0); + sendCommand(cmd); + LOGF_INFO("Moving...", "NULL"); + OCcontrolSP.apply(); + Ismoving=true; + return true; + } + + return DefaultDevice::ISNewSwitch(dev, name, states, names, n); +} + +bool WandererCoverV4EC::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n) +{ + + if (dev && !strcmp(dev, getDeviceName())) + { + // Light + if (SetLightNP.isNameMatch(name)) + { + bool rc1 = false; + for (int i = 0; i < n; i++) + { + if(static_cast(values[i])>0) + rc1 = sendCommand(std::to_string(static_cast(values[i]))); + else + rc1 = sendCommand("9999"); + } + + SetLightNP.setState( (rc1) ? IPS_OK : IPS_ALERT); + if (SetLightNP.getState() == IPS_OK) + SetLightNP.update(values, names, n); + SetLightNP.apply(); + return true; + } + // Heater + if (SetHeaterNP.isNameMatch(name)) + { + bool rc1 = false; + for (int i = 0; i < n; i++) + { + rc1 = setDewPWM(2, static_cast(values[i])); + } + + SetHeaterNP.setState( (rc1) ? IPS_OK : IPS_ALERT); + if (SetHeaterNP.getState() == IPS_OK) + SetHeaterNP.update(values, names, n); + SetHeaterNP.apply(); + return true; + } + // Close Set + if (CloseSetNP.isNameMatch(name)) + { + bool rc1 = false; + + for (int i = 0; i < n; i++) + { + if(values[i]<10||values[i]>90) + { + CloseSetNP.setState(IPS_ALERT); + LOGF_ERROR("Out of range! Allowed closed angle: 10-90 degrees.","NULL"); + return false; + } + rc1 = setClose(values[i]); + } + + CloseSetNP.setState( (rc1) ? IPS_OK : IPS_ALERT); + if (CloseSetNP.getState() == IPS_OK) + CloseSetNP.update(values, names, n); + CloseSetNP.apply(); + return true; + } + // Open Set + if (OpenSetNP.isNameMatch(name)) + { + bool rc1 = false; + + for (int i = 0; i < n; i++) + { + if(values[i]<100||values[i]>300) + { + OpenSetNP.setState(IPS_ALERT); + LOGF_ERROR("Out of range! Allowed open angle: 100-300 degrees.","NULL"); + return false; + } + rc1 = setOpen(values[i]); + } + + OpenSetNP.setState( (rc1) ? IPS_OK : IPS_ALERT); + if (OpenSetNP.getState() == IPS_OK) + OpenSetNP.update(values, names, n); + OpenSetNP.apply(); + return true; + } + } + return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n); +} + + +const char *WandererCoverV4EC::getDefaultName() +{ + return "WandererCover V4-EC"; +} + + +bool WandererCoverV4EC::sendCommand(std::string command) +{ + int nbytes_written = 0, rc = -1; + std::string command_termination = "\n"; + LOGF_DEBUG("CMD: %s", command.c_str()); + 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 WandererCoverV4EC::setDewPWM(int id, int value) +{ + char cmd[64] = {0}; + snprintf(cmd, 64, "%d%03d", id, value); + if (sendCommand(cmd)) + { + return true; + } + + return false; +} + +bool WandererCoverV4EC::setClose(double value) +{ + char cmd[64] = {0}; + snprintf(cmd, 64, "%d", (int)(value*100+10000)); + if (sendCommand(cmd)) + { + return true; + } + + return false; +} + +bool WandererCoverV4EC::setOpen(double value) +{ + char cmd[64] = {0}; + snprintf(cmd, 64, "%d", (int)(value*100+40000)); + if (sendCommand(cmd)) + { + return true; + } + + return false; +} + +void WandererCoverV4EC::TimerHit() +{ + if (!isConnected()) + { + SetTimer(2000); + return; + } + + getData(); + SetTimer(2000); +} + +bool WandererCoverV4EC::saveConfigItems(FILE * fp) +{ + INDI::DefaultDevice::saveConfigItems(fp); + + SetHeaterNP.save(fp); + SetLightNP.save(fp); + CloseSetNP.save(fp); + OpenSetNP.save(fp); + return true; +} + diff --git a/drivers/auxiliary/wanderer_cover_v4_ec.h b/drivers/auxiliary/wanderer_cover_v4_ec.h new file mode 100644 index 0000000000..657b826ecb --- /dev/null +++ b/drivers/auxiliary/wanderer_cover_v4_ec.h @@ -0,0 +1,118 @@ +/******************************************************************************* + Copyright(c) 2024 Frank Wang. All rights reserved. + + WandererCover V4-EC + + 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 +#include + + +namespace Connection +{ +class Serial; +} + +class WandererCoverV4EC : public INDI::DefaultDevice +{ +public: + WandererCoverV4EC(); + virtual ~WandererCoverV4EC() = default; + + virtual bool initProperties() override; + virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; + virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override; + virtual bool updateProperties() override; + + + +protected: + const char *getDefaultName() override; + virtual bool saveConfigItems(FILE *fp) override; + virtual void TimerHit() override; + + + +private: + + int firmware=0; + bool sendCommand(std::string command); + //Current Calibrate + bool getData(); + double closesetread=0; + double opensetread=0; + double positionread=0; + double voltageread=0; + bool Ismoving=false; + bool setDewPWM(int id, int value); + bool setClose(double value); + bool setOpen(double value); + void updateData(double closesetread,double opensetread,double positionread,double voltageread); + + INDI::PropertySwitch OCcontrolSP{2}; + enum + { + Open, + Close, + }; + + INDI::PropertyNumber DataNP{4}; + enum + { + closeset_read, + openset_read, + position_read, + voltage_read, + }; + + //Flat light//////////////////////////////////////////////////////////////// + INDI::PropertyNumber SetLightNP{1}; + enum + { + Light, + }; + //Dew heater/////////////////////////////////////////////////////////////// + INDI::PropertyNumber SetHeaterNP{1}; + enum + { + Heat, + }; + //Close Set/////////////////////////////////////////////////////////////// + INDI::PropertyNumber CloseSetNP{1}; + enum + { + CloseSet, + }; + //Open Set/////////////////////////////////////////////////////////////// + INDI::PropertyNumber OpenSetNP{1}; + enum + { + OpenSet, + }; + + + int PortFD{ -1 }; + + Connection::Serial *serialConnection{ nullptr }; +};