From 9b00c50dce5f956ba3f9311cb8c943c17da0c8e5 Mon Sep 17 00:00:00 2001 From: Efstathios Chrysikos Date: Wed, 17 Jan 2024 09:49:22 +0200 Subject: [PATCH] PegasusAstro SaddlePowerBox (#1989) --- drivers.xml | 4 + drivers/auxiliary/CMakeLists.txt | 9 + drivers/auxiliary/pegasus_spb.cpp | 960 ++++++++++++++++++++++++++++++ drivers/auxiliary/pegasus_spb.h | 195 ++++++ 4 files changed, 1168 insertions(+) create mode 100644 drivers/auxiliary/pegasus_spb.cpp create mode 100644 drivers/auxiliary/pegasus_spb.h diff --git a/drivers.xml b/drivers.xml index 94808d8c32..98a2eefe07 100644 --- a/drivers.xml +++ b/drivers.xml @@ -649,6 +649,10 @@ indi_pegasus_flatmaster 1.2 + + indi_pegasus_spb + 1.2 + indi_planewave_deltat 1.1 diff --git a/drivers/auxiliary/CMakeLists.txt b/drivers/auxiliary/CMakeLists.txt index c72c3b52ae..3bfb2ca35a 100644 --- a/drivers/auxiliary/CMakeLists.txt +++ b/drivers/auxiliary/CMakeLists.txt @@ -101,6 +101,15 @@ add_executable(indi_pegasus_ppba ${pegasus_ppba_SRC}) target_link_libraries(indi_pegasus_ppba indidriver) install(TARGETS indi_pegasus_ppba RUNTIME DESTINATION bin) + +# ########## Pegasus Saddle Power Box Driver ############### +SET(pegasus_spb_SRC + pegasus_spb.cpp) + +add_executable(indi_pegasus_spb ${pegasus_spb_SRC}) +target_link_libraries(indi_pegasus_spb indidriver) +install(TARGETS indi_pegasus_spb RUNTIME DESTINATION bin) + # ########## RB Focus Excalibur ############### SET(Excalibur_SRC Excalibur.cpp) diff --git a/drivers/auxiliary/pegasus_spb.cpp b/drivers/auxiliary/pegasus_spb.cpp new file mode 100644 index 0000000000..791500e2f6 --- /dev/null +++ b/drivers/auxiliary/pegasus_spb.cpp @@ -0,0 +1,960 @@ +#include "pegasus_spb.h" +#include "indicom.h" +#include "connectionplugins/connectionserial.h" + +#include +#include +#include +#include + +static std::unique_ptr spb(new PegasusSPB()); + + + +//double result = map(100, 9, 255, 1, 100); +//double reverse = map(result, 1, 100, 9, 255); +//LOGF_DEBUG("%d", reverse); + +PegasusSPB::PegasusSPB() : WI(this) +{ + setVersion(1, 0); + lastSensorData.reserve(PA_N); + lastConsumptionData.reserve(PS_N); + lastMetricsData.reserve(PC_N); + +} + +double PegasusSPB::map(double value, double from1, double to1, double from2, double to2) +{ + // Make sure the value is within the source range [from1, to1] + value = std::max(from1, std::min(to1, value)); + + // Calculate the mapped value in the destination range [from2, to2] + double mappedValue = from2 + (value - from1) * (to2 - from2) / (to1 - from1); + + return mappedValue; +} + +bool PegasusSPB::initProperties() +{ + INDI::DefaultDevice::initProperties(); + setDriverInterface(AUX_INTERFACE | WEATHER_INTERFACE); + + WI::initProperties(ENVIRONMENT_TAB, ENVIRONMENT_TAB); + + addAuxControls(); + + + serialConnection = new Connection::Serial(this); + serialConnection->registerHandshake([&]() + { + return Handshake(); + }); + registerConnection(serialConnection); + + //////////////////////////////////////////////////////////////////////////// + /// Adjustable HUB Group + //////////////////////////////////////////////////////////////////////////// + + //Power-Dew Switch A + PowerDewSwitchASP[DEW].fill("DEW", "DEW", ISS_OFF); + PowerDewSwitchASP[POWER].fill("POWER", "POWER(12v)", ISS_OFF); + PowerDewSwitchASP.fill(getDeviceName(), "POWER-DEWA", "Port A Mode", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + //Power-Dew Switch B + PowerDewSwitchBSP[DEW].fill("DEW", "DEW", ISS_OFF); + PowerDewSwitchBSP[POWER].fill("POWER", "POWER(12v)", ISS_OFF); + PowerDewSwitchBSP.fill(getDeviceName(), "POWER-DEWB", "Port B Mode", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + + //////////////////////////////////////////////////////////////////////////// + /// Dew Group + //////////////////////////////////////////////////////////////////////////// + + //DewAdjA + DewAdjANP[0].fill("DEW_A", "Dew A (%)", "%.2f", 0, 100, 10, 0); + DewAdjANP.fill(getDeviceName(), "DEW-ADJA", "DEW A", DEW_TAB, IP_RW, 60, IPS_IDLE); + + //DewAdjB + DewAdjBNP[0].fill("DEW_B", "Dew B (%)", "%.2f", 0, 100, 10, 0); + DewAdjBNP.fill(getDeviceName(), "DEW-ADJB", "DEW B", DEW_TAB, IP_RW, 60, IPS_IDLE); + + //DewAuto + DewAutoSP[INDI_ENABLED].fill("DEWAUTO_ON", "Enabled", ISS_OFF); + DewAutoSP[INDI_DISABLED].fill("DEWAUTO_OFF", "Disabled", ISS_OFF); + DewAutoSP.fill(getDeviceName(), "DEWAUTO", "Auto Dew", DEW_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + //DewAggress + DewAggressNP[0].fill("DEW_AGGRESS", "Agg Level", "%.2f", 0, 100, 1, 0); + DewAggressNP.fill(getDeviceName(), "DEW-AGGESS", "Auto Dew", DEW_TAB, IP_RW, 60, IPS_IDLE); + + + //////////////////////////////////////////////////////////////////////////// + /// Power Group + //////////////////////////////////////////////////////////////////////////// + + // Quad 12v Power + QuadPowerSP[INDI_ENABLED].fill("QUAD_ON", "Enabled", ISS_OFF); + QuadPowerSP[INDI_DISABLED].fill("QUAD_OFF", "Disabled", ISS_OFF); + QuadPowerSP.fill(getDeviceName(), "QUAD_HUB", "Power Quad Hub", POWER_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + //PowerAdjA + PowerAdjASP[INDI_ENABLED].fill("POWERA_ON", "Enabled", ISS_OFF); + PowerAdjASP[INDI_DISABLED].fill("POWERA_OFF", "Disabled", ISS_ON); + PowerAdjASP.fill(getDeviceName(), "POWER-ADJA", "Power A", POWER_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + + //PowerAdjB + PowerAdjBSP[INDI_ENABLED].fill("POWERB_ON", "Enabled", ISS_OFF); + PowerAdjBSP[INDI_DISABLED].fill("POWERB_OFF", "Disabled", ISS_ON); + PowerAdjBSP.fill(getDeviceName(), "POWER-ADJB", "Power B", POWER_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + + // Power Sensors + PowerSensorsNP[SENSOR_VOLTAGE].fill("SENSOR_VOLTAGE", "Voltage (V)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP[SENSOR_CURRENT].fill("SENSOR_CURRENT", "Current (A)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP[SENSOR_AVG_AMPS].fill("SENSOR_AVG_AMPS", "Average Current (A)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP[SENSOR_AMP_HOURS].fill("SENSOR_AMP_HOURS", "Amp hours (Ah)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP[SENSOR_WATT_HOURS].fill("SENSOR_WATT_HOURS", "Watt hours (Wh)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP[SENSOR_TOTAL_CURRENT].fill("SENSOR_TOTAL_CURRENT", "Total current (A)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP[SENSOR_12V_CURRENT].fill("SENSOR_12V_CURRENT", "12V current (A)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP[SENSOR_DEWA_CURRENT].fill("SENSOR_DEWA_CURRENT", "DewA current (A)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP[SENSOR_DEWB_CURRENT].fill("SENSOR_DEWB_CURRENT", "DewB current (A)", "%4.2f", 0, 99, 100, 0); + PowerSensorsNP.fill(getDeviceName(), "POWER_SENSORS", "Sensors", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE); + + //////////////////////////////////////////////////////////////////////////// + /// Environment Group + //////////////////////////////////////////////////////////////////////////// + addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -15, 35, 15); + addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15); + addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15); + setCriticalParameter("WEATHER_TEMPERATURE"); + + + //////////////////////////////////////////////////////////////////////////////////// + /// Sensor Offset + //////////////////////////////////////////////////////////////////////////////////// + HumidityOffsetNP[0].fill("HUM_OFFSET", "Level", "%.0f", -50, 50, 1, 0); + HumidityOffsetNP.fill(getDeviceName(), "HUM-OFFSET", "Humidity Offset", ENVIRONMENT_TAB, IP_RW, 60, IPS_IDLE); + + TemperatureOffsetNP[0].fill("TEMP_OFFSET", "Level", "%.0f", -40, 40, 1, 0); + TemperatureOffsetNP.fill(getDeviceName(), "TEMP-OFFSET", "Temperature Offset", ENVIRONMENT_TAB, IP_RW, 60, IPS_IDLE); + + return true; + +} + +bool PegasusSPB::updateProperties() +{ + INDI::DefaultDevice::updateProperties(); + + if (isConnected()) + { + defineProperty(DewAutoSP); + defineProperty(DewAggressNP); + int aggressiveness = getDewAggressiveness(); + DewAggressNP[0].setValue(static_cast(aggressiveness)); + DewAggressNP.setState(IPS_OK); + DewAggressNP.apply(); + + + + + defineProperty(PowerDewSwitchASP); + int portNumber = 1; + int mode = getPowerDewPortMode(portNumber); + PowerDewSwitchASP.reset(); + PowerDewSwitchASP[DEW].setState(mode == PegasusSPB::PORT_MODE::DEW ? ISS_ON : ISS_OFF); + PowerDewSwitchASP[POWER].setState(mode == PegasusSPB::PORT_MODE::POWER ? ISS_ON : ISS_OFF); + PowerDewSwitchASP.setState(IPS_OK); + PowerDewSwitchASP.apply(); + updatePropertiesPowerDewMode(portNumber, mode); + + defineProperty(PowerDewSwitchBSP); + portNumber = 2; + mode = getPowerDewPortMode(portNumber); + PowerDewSwitchBSP.reset(); + PowerDewSwitchBSP[DEW].setState(mode == PegasusSPB::PORT_MODE::DEW ? ISS_ON : ISS_OFF); + PowerDewSwitchBSP[POWER].setState(mode == PegasusSPB::PORT_MODE::POWER ? ISS_ON : ISS_OFF); + PowerDewSwitchBSP.setState(IPS_OK); + PowerDewSwitchBSP.apply(); + updatePropertiesPowerDewMode(portNumber, mode); + + // Main Control + defineProperty(QuadPowerSP); + defineProperty(PowerSensorsNP); + + + //Sensor Offsets + defineProperty(HumidityOffsetNP); + int humidityOffset = getHumidityOffset(); + HumidityOffsetNP[0].setValue(static_cast(humidityOffset)); + HumidityOffsetNP.setState(IPS_OK); + HumidityOffsetNP.apply(); + + + defineProperty(TemperatureOffsetNP); + int temperatureOffset = getTemperatureOffset(); + TemperatureOffsetNP[0].setValue(static_cast(temperatureOffset)); + TemperatureOffsetNP.setState(IPS_OK); + TemperatureOffsetNP.apply(); + + + WI::updateProperties(); + setupComplete = true; + } + else + { + // Main Control + deleteProperty(DewAutoSP); + deleteProperty(DewAggressNP); + deleteProperty(QuadPowerSP); + deleteProperty(PowerSensorsNP); + deleteProperty(PowerDewSwitchASP); + deleteProperty(PowerDewSwitchBSP); + deleteProperty(PowerAdjASP); + deleteProperty(PowerAdjBSP); + deleteProperty(DewAdjANP); + deleteProperty(DewAdjBNP); + deleteProperty(HumidityOffsetNP); + deleteProperty(TemperatureOffsetNP); + WI::updateProperties(); + setupComplete = false; + } + + return true; +} + +const char * PegasusSPB::getDefaultName() +{ + return "Pegasus SPB"; +} + + +bool PegasusSPB::Handshake() +{ + int tty_rc = 0, nbytes_written = 0, nbytes_read = 0; + char command[PEGASUS_LEN] = {0}, response[PEGASUS_LEN] = {0}; + + PortFD = serialConnection->getPortFD(); + + LOG_DEBUG("CMD "); + + tcflush(PortFD, TCIOFLUSH); + strncpy(command, "P#\n", PEGASUS_LEN); + if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK) + { + char errorMessage[MAXRBUF]; + tty_error_msg(tty_rc, errorMessage, MAXRBUF); + LOGF_ERROR("Serial write error: %s", errorMessage); + return false; + } + + // Try first with stopChar as the stop character + if ( (tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read)) != TTY_OK) + { + // Try 0xA as the stop character + if (tty_rc == TTY_OVERFLOW || tty_rc == TTY_TIME_OUT) + { + tcflush(PortFD, TCIOFLUSH); + tty_write_string(PortFD, command, &nbytes_written); + stopChar = 0xA; + tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read); + } + + if (tty_rc != TTY_OK) + { + char errorMessage[MAXRBUF]; + tty_error_msg(tty_rc, errorMessage, MAXRBUF); + LOGF_ERROR("Serial read error: %s", errorMessage); + return false; + } + } + + tcflush(PortFD, TCIOFLUSH); + response[nbytes_read - 1] = '\0'; + LOGF_DEBUG("RES <%s>", response); + + setupComplete = false; + + return (strstr(response, "SPB") != nullptr); +} + +void PegasusSPB::TimerHit() +{ + if (!isConnected() || setupComplete == false) + { + SetTimer(getCurrentPollingPeriod()); + return; + } + + getSensorData(); + getConsumptionData(); + getMetricsData(); + + SetTimer(getCurrentPollingPeriod()); +} + +bool PegasusSPB::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n) +{ + if (dev != nullptr && !strcmp(dev, getDeviceName())) + { + // Quad 12V Power + if (QuadPowerSP.isNameMatch(name)) + { + QuadPowerSP.update(states, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + auto newState = QuadPowerSP[INDI_ENABLED].getState() == ISS_ON; + result = setQuadPowerState(newState) ? IPS_OK : IPS_ALERT; + } + QuadPowerSP.setState(result); + QuadPowerSP.apply(); + return true; + } + //Power-Dew SwitchA + else if(PowerDewSwitchASP.isNameMatch(name)) + { + PowerDewSwitchASP.update(states, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + auto newState = PowerDewSwitchASP[DEW].getState() == ISS_ON ? 0 : 1; + result = setPowerDewPortMode(1, newState) ? IPS_OK : IPS_ALERT; + updatePropertiesPowerDewMode(1, newState); + } + PowerDewSwitchASP.setState(result); + PowerDewSwitchASP.apply(); + return true; + } + //Power-Dew SwitchB + else if(PowerDewSwitchBSP.isNameMatch(name)) + { + PowerDewSwitchBSP.update(states, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + auto newState = PowerDewSwitchBSP[DEW].getState() == ISS_ON ? 0 : 1; + result = setPowerDewPortMode(2, newState) ? IPS_OK : IPS_ALERT; + updatePropertiesPowerDewMode(2, newState); + } + PowerDewSwitchBSP.setState(result); + PowerDewSwitchBSP.apply(); + return true; + } + else if(PowerAdjASP.isNameMatch(name)) + { + PowerAdjASP.update(states, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + bool state = PowerAdjASP[INDI_ENABLED].getState() == ISS_ON; + if(!setPowerPortState(1, state )) + { + result = IPS_ALERT; + } + } + + PowerAdjASP.setState(result); + PowerAdjASP.apply(); + + } + else if(PowerAdjBSP.isNameMatch(name)) + { + PowerAdjBSP.update(states, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + bool state = PowerAdjBSP[INDI_ENABLED].getState() == ISS_ON; + if(!setPowerPortState(2, state)) + { + result = IPS_ALERT; + } + } + PowerAdjBSP.setState(result); + PowerAdjBSP.apply(); + } + else if(DewAutoSP.isNameMatch(name)) + { + DewAutoSP.update(states, names, n); + IPState result = IPS_OK; + if(isConnected()) + { + bool state = DewAutoSP[INDI_ENABLED].getState() == ISS_ON; + if(!setDewAutoState(state)) + { + result = IPS_ALERT; + } + } + DewAutoSP.setState(result); + DewAutoSP.apply(); + + } + } + + return DefaultDevice::ISNewSwitch(dev, name, states, names, n); +} + +bool PegasusSPB::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n) +{ + if (dev != nullptr && !strcmp(dev, getDeviceName())) + { + if(DewAdjANP.isNameMatch(name)) + { + DewAdjANP.update(values, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + if(!setDewPortPower(1, values[0])) + { + result = IPS_ALERT; + } + } + DewAdjANP.setState(result); + DewAdjANP.apply(); + } + else if(DewAdjBNP.isNameMatch(name)) + { + DewAdjBNP.update(values, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + if(!setDewPortPower(2, values[0])) + { + result = IPS_ALERT; + } + } + DewAdjBNP.setState(result); + DewAdjBNP.apply(); + } + else if(DewAggressNP.isNameMatch(name)) + { + DewAggressNP.update(values, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + if(!setDewAggressiveness(static_cast(values[0]))) + { + result = IPS_ALERT; + } + } + DewAggressNP.setState(result); + DewAggressNP.apply(); + } + else if(HumidityOffsetNP.isNameMatch(name)) + { + HumidityOffsetNP.update(values, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + if(!setHumdityOffset(static_cast(values[0]))) + { + result = IPS_ALERT; + } + } + HumidityOffsetNP.setState(result); + HumidityOffsetNP.apply(); + } + else if(TemperatureOffsetNP.isNameMatch(name)) + { + TemperatureOffsetNP.update(values, names, n); + IPState result = IPS_OK; + if (isConnected()) + { + if(!setTemperatureOffset(static_cast(values[0]))) + { + result = IPS_ALERT; + } + } + TemperatureOffsetNP.setState(result); + TemperatureOffsetNP.apply(); + } + } + return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n); +} + +bool PegasusSPB::setPowerPortState(int portNumber, bool enabled) +{ + int power = 0; + + if(enabled) + power = 100; + + return setDewPortPower(portNumber, power); +} + +bool PegasusSPB::getPowerPortState(int portNumber) +{ + int power = getDewPortPower(portNumber); + + if(power == 100) + return true; + + + return false; +} + +int PegasusSPB::getDewPortPower(int portNumber) +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "PA"); + int power = -1; + if(sendCommand(cmd, res)) + { + std::vector result = split(res, ":"); + + if(portNumber == 1) + power = std::stod(result[PA_DEW_1]); + else if(portNumber == 2) + power = std::stod(result[PA_DEW_2]); + } + else + { + LOGF_ERROR("Error on set power-dew port mode [Port=%d]", portNumber); + } + return power / 255.0 * 100.0; +} + + + +bool PegasusSPB::setDewPortPower(int portNumber, int power) +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "P%d:%d", portNumber + 2, static_cast(power / 100.0 * 255.0)); + if(sendCommand(cmd, res)) + { + if(!strstr(cmd, res)) + { + LOGF_ERROR("Error on set dew port power [Cmd=%s Res=%s]", cmd, res); + return false; + } + } + else + { + LOGF_ERROR("Error on set dew port power [Port=%d Power=%d]", portNumber, power); + return false; + } + return true; +} + +int PegasusSPB::getPowerDewPortMode(int portNumber) +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "D%d:99", portNumber + 2); + if(sendCommand(cmd, res)) + { + std::vector result = split(res, ":"); + return std::stod(result[1]); + } + else + { + LOGF_ERROR("Error on set power-dew port mode [Port=%d]", portNumber); + } + return -1; +} + +bool PegasusSPB::setPowerDewPortMode(int portNumber, int mode) +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "D%d:%d", portNumber + 2, mode); + if(sendCommand(cmd, res)) + { + if(!strstr(cmd, res)) + { + LOGF_ERROR("Error on set power-dew port mode [Cmd=%s Res=%s]", cmd, res); + return false; + } + } + else + { + LOGF_ERROR("Error on set power-dew port mode [Port=%d Mode=%d]", portNumber, mode); + return false; + } + return true; +} + +bool PegasusSPB::setDewAutoState(bool enabled) +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "PD:%d", enabled ? 1 : 0); + if(sendCommand(cmd, res)) + { + if(!strstr(cmd, res)) + { + LOGF_ERROR("Error on set dew auto state [Cmd=%s Res=%s]", cmd, res); + return false; + } + } + else + { + LOG_ERROR("Error on set set dew auto state"); + return false; + } + return true; +} + +bool PegasusSPB::setDewAggressiveness(uint8_t level) +{ + int mappedLevel = map(level, 0, 100, 10, 255); + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "PD:%d", mappedLevel); + if(sendCommand(cmd, res)) + { + if(!strstr(cmd, res)) + { + LOGF_ERROR("Error on set dew aggresiveness [Cmd=%s Res=%s]", cmd, res); + return false; + } + } + else + { + LOG_ERROR("Error on set dew aggresiveness"); + return false; + } + return true; +} + +int PegasusSPB::getDewAggressiveness() +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "DA"); + if(sendCommand(cmd, res)) + { + std::vector result = split(res, ":"); + int value = map(std::stoi(result[1]), 10, 255, 0, 100); + return value; + } + else + { + LOGF_ERROR("Error on get dew aggresiveness [Cmd=%s Res=%s]", cmd, res); + } + return -1; +} + +bool PegasusSPB::setHumdityOffset(int level) +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "CH:%d", level); + if(sendCommand(cmd, res)) + { + if(!strstr(cmd, res)) + { + LOGF_ERROR("Error on set humidity offset [Cmd=%s Res=%s]", cmd, res); + return false; + } + } + else + { + LOG_ERROR("Error on set humidity offset"); + return false; + } + return true; +} + +int PegasusSPB::getHumidityOffset() +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "CR"); + if(sendCommand(cmd, res)) + { + std::vector result = split(res, ":"); + int value = std::stoi(result[2]); + return value; + } + else + { + LOGF_ERROR("Error on get humidity offset [Cmd=%s Res=%s]", cmd, res); + } + return -1; +} + +bool PegasusSPB::setTemperatureOffset(int level) +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "CT:%d", (level * 100)); + if(sendCommand(cmd, res)) + { + if(!strstr(cmd, res)) + { + LOGF_ERROR("Error on set temperature offset [Cmd=%s Res=%s]", cmd, res); + return false; + } + } + else + { + LOG_ERROR("Error on set temperature offset"); + return false; + } + return true; +} + +int PegasusSPB::getTemperatureOffset() +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "CR"); + if(sendCommand(cmd, res)) + { + std::vector result = split(res, ":"); + int value = std::stoi(result[1]); + return (value / 100); + } + else + { + LOGF_ERROR("Error on get temperature offset [Cmd=%s Res=%s]", cmd, res); + } + return -1; +} + +void PegasusSPB::updatePropertiesPowerDewMode(int portNumber, int mode) +{ + if(portNumber == 1) + { + switch(mode) + { + case PegasusSPB::PORT_MODE::DEW: + deleteProperty(PowerAdjASP); + defineProperty(DewAdjANP); + break; + case PegasusSPB::PORT_MODE::POWER: + deleteProperty(DewAdjANP); + bool enabled = getPowerPortState(1); + PowerAdjASP.reset(); + PowerAdjASP[INDI_ENABLED].setState(enabled ? ISS_ON : ISS_OFF); + PowerAdjASP[INDI_DISABLED].setState(enabled ? ISS_OFF : ISS_ON); + PowerAdjASP.setState(IPS_OK); + PowerAdjASP.apply(); + defineProperty(PowerAdjASP); + break; + } + } + else if(portNumber == 2) + { + switch(mode) + { + case PegasusSPB::PORT_MODE::DEW: + deleteProperty(PowerAdjBSP); + defineProperty(DewAdjBNP); + break; + case PegasusSPB::PORT_MODE::POWER: + deleteProperty(DewAdjBNP); + bool enabled = getPowerPortState(2); + PowerAdjBSP.reset(); + PowerAdjBSP[INDI_ENABLED].setState(enabled ? ISS_ON : ISS_OFF); + PowerAdjBSP[INDI_DISABLED].setState(enabled ? ISS_OFF : ISS_ON); + PowerAdjBSP.setState(IPS_OK); + PowerAdjBSP.apply(); + defineProperty(PowerAdjBSP); + break; + } + } +} + +bool PegasusSPB::setQuadPowerState(bool enabled) +{ + char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}; + snprintf(cmd, PEGASUS_LEN, "P1:%d", enabled ? 1 : 0); + return sendCommand(cmd, res); +} + +bool PegasusSPB::getSensorData() +{ + char res[PEGASUS_LEN] = {0}; + if (sendCommand("PA", res)) + { + std::vector result = split(res, ":"); + if (result.size() < PA_N) + { + LOG_WARN("Received wrong number of detailed sensor data. Retrying..."); + return false; + } + + if (result == lastSensorData) + return true; + + // Power Sensors + PowerSensorsNP[SENSOR_VOLTAGE].setValue(std::stod(result[PA_VOLTAGE])); + PowerSensorsNP[SENSOR_CURRENT].setValue(std::stod(result[PA_CURRENT]) / 65.0); + PowerSensorsNP.setState(IPS_OK); + if (lastSensorData[PA_VOLTAGE] != result[PA_VOLTAGE] || lastSensorData[PA_CURRENT] != result[PA_CURRENT]) + PowerSensorsNP.apply(); + + + // Environment Sensors + setParameterValue("WEATHER_TEMPERATURE", std::stod(result[PA_TEMPERATURE])); + setParameterValue("WEATHER_HUMIDITY", std::stod(result[PA_HUMIDITY])); + setParameterValue("WEATHER_DEWPOINT", std::stod(result[PA_DEW_POINT])); + if (lastSensorData[PA_TEMPERATURE] != result[PA_TEMPERATURE] || + lastSensorData[PA_HUMIDITY] != result[PA_HUMIDITY] || + lastSensorData[PA_DEW_POINT] != result[PA_DEW_POINT]) + { + if (WI::syncCriticalParameters()) + IDSetLight(&critialParametersLP, nullptr); + ParametersNP.s = IPS_OK; + IDSetNumber(&ParametersNP, nullptr); + } + + // Power Quad Status + QuadPowerSP[INDI_ENABLED].setState((std::stoi(result[PA_PORT_STATUS]) == 1) ? ISS_ON : ISS_OFF); + QuadPowerSP[INDI_DISABLED].setState((std::stoi(result[PA_PORT_STATUS]) == 1) ? ISS_OFF : ISS_ON); + QuadPowerSP.setState((std::stoi(result[6]) == 1) ? IPS_OK : IPS_IDLE); + if (lastSensorData[PA_PORT_STATUS] != result[PA_PORT_STATUS]) + QuadPowerSP.apply(); + + // // Power Warn + // PowerWarnL[0].s = (std::stoi(result[PA_PWR_WARN]) == 1) ? IPS_ALERT : IPS_OK; + // PowerWarnLP.s = (std::stoi(result[PA_PWR_WARN]) == 1) ? IPS_ALERT : IPS_OK; + // if (lastSensorData[PA_PWR_WARN] != result[PA_PWR_WARN]) + // IDSetLight(&PowerWarnLP, nullptr); + + // Dew PWM + double dewA = std::stod(result[PA_DEW_1]) / 255.0 * 100.0; + double dewB = std::stod(result[PA_DEW_2]) / 255.0 * 100.0; + DewAdjANP[0].setValue(dewA); + DewAdjANP.setState(IPS_OK); + DewAdjBNP[0].setValue(dewB); + DewAdjBNP.setState(IPS_OK); + if (lastSensorData[PA_DEW_1] != result[PA_DEW_1] || lastSensorData[PA_DEW_2] != result[PA_DEW_2]) + { + DewAdjANP.apply(); + DewAdjBNP.apply(); + } + + + // Auto Dew + DewAutoSP[INDI_ENABLED].setState((std::stoi(result[PA_AUTO_DEW]) == 1) ? ISS_ON : ISS_OFF); + DewAutoSP[INDI_DISABLED].setState((std::stoi(result[PA_AUTO_DEW]) == 1) ? ISS_OFF : ISS_ON); + DewAutoSP.setState((std::stoi(result[6]) == 1) ? IPS_OK : IPS_IDLE); + if (lastSensorData[PA_AUTO_DEW] != result[PA_AUTO_DEW]) + DewAutoSP.apply(); + + lastSensorData = result; + return true; + } + + return false; +} + +bool PegasusSPB::getConsumptionData() +{ + char res[PEGASUS_LEN] = {0}; + if (sendCommand("PS", res)) + { + std::vector result = split(res, ":"); + if (result.size() < PS_N) + { + LOG_WARN("Received wrong number of detailed consumption data. Retrying..."); + return false; + } + + if (result == lastConsumptionData) + return true; + + // Power Sensors + PowerSensorsNP[SENSOR_AVG_AMPS].setValue(std::stod(result[PS_AVG_AMPS])); + PowerSensorsNP[SENSOR_AMP_HOURS].setValue(std::stod(result[PS_AMP_HOURS])); + PowerSensorsNP[SENSOR_WATT_HOURS].setValue(std::stod(result[PS_WATT_HOURS])); + PowerSensorsNP.setState(IPS_OK); + if (lastConsumptionData[PS_AVG_AMPS] != result[PS_AVG_AMPS] || lastConsumptionData[PS_AMP_HOURS] != result[PS_AMP_HOURS] + || lastConsumptionData[PS_WATT_HOURS] != result[PS_WATT_HOURS]) + PowerSensorsNP.apply(); + + lastConsumptionData = result; + + return true; + } + + return false; +} + +bool PegasusSPB::getMetricsData() +{ + char res[PEGASUS_LEN] = {0}; + if (sendCommand("PC", res)) + { + std::vector result = split(res, ":"); + if (result.size() < PC_N) + { + LOG_WARN("Received wrong number of detailed metrics data. Retrying..."); + return false; + } + + if (result == lastMetricsData) + return true; + + // Power Sensors + PowerSensorsNP[SENSOR_TOTAL_CURRENT].setValue(std::stod(result[PC_TOTAL_CURRENT])); + PowerSensorsNP[SENSOR_12V_CURRENT].setValue(std::stod(result[PC_12V_CURRENT])); + PowerSensorsNP[SENSOR_DEWA_CURRENT].setValue(std::stod(result[PC_DEWA_CURRENT])); + PowerSensorsNP[SENSOR_DEWB_CURRENT].setValue(std::stod(result[PC_DEWB_CURRENT])); + PowerSensorsNP.setState(IPS_OK); + if (lastMetricsData[PC_TOTAL_CURRENT] != result[PC_TOTAL_CURRENT] || + lastMetricsData[PC_12V_CURRENT] != result[PC_12V_CURRENT] || + lastMetricsData[PC_DEWA_CURRENT] != result[PC_DEWA_CURRENT] || + lastMetricsData[PC_DEWB_CURRENT] != result[PC_DEWB_CURRENT]) + PowerSensorsNP.apply(); + + // std::chrono::milliseconds uptime(std::stol(result[PC_UPTIME])); + // using dhours = std::chrono::duration>; + // std::stringstream ss; + // ss << std::fixed << std::setprecision(3) << dhours(uptime).count(); + // IUSaveText(&FirmwareT[FIRMWARE_UPTIME], ss.str().c_str()); + // IDSetText(&FirmwareTP, nullptr); + + lastMetricsData = result; + + return true; + } + + return false; +} + + +bool PegasusSPB::sendCommand(const char * cmd, char * res) +{ + int nbytes_read = 0, nbytes_written = 0, tty_rc = 0; + char command[PEGASUS_LEN] = {0}; + LOGF_DEBUG("CMD <%s>", cmd); + + for (int i = 0; i < 2; i++) + { + tcflush(PortFD, TCIOFLUSH); + snprintf(command, PEGASUS_LEN, "%s\n", cmd); + if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK) + continue; + + if (!res) + { + tcflush(PortFD, TCIOFLUSH); + return true; + } + + if ( (tty_rc = tty_nread_section(PortFD, res, PEGASUS_LEN, stopChar, PEGASUS_TIMEOUT, &nbytes_read)) != TTY_OK + || nbytes_read == 1) + continue; + + tcflush(PortFD, TCIOFLUSH); + res[nbytes_read - 1] = '\0'; + LOGF_DEBUG("RES <%s>", res); + return true; + } + + if (tty_rc != TTY_OK) + { + char errorMessage[MAXRBUF]; + tty_error_msg(tty_rc, errorMessage, MAXRBUF); + LOGF_ERROR("Serial error: %s", errorMessage); + } + + return false; +} + +std::vector PegasusSPB::split(const std::string &input, const std::string ®ex) +{ + // passing -1 as the submatch index parameter performs splitting + std::regex re(regex); + std::sregex_token_iterator + first{input.begin(), input.end(), re, -1}, + last; + return {first, last}; +} diff --git a/drivers/auxiliary/pegasus_spb.h b/drivers/auxiliary/pegasus_spb.h new file mode 100644 index 0000000000..4ef2409891 --- /dev/null +++ b/drivers/auxiliary/pegasus_spb.h @@ -0,0 +1,195 @@ +/******************************************************************************* + Copyright(c) 2023 Efstathios Chrysikos. All rights reserved. + + Pegasus Pocket Power Box Advance + + 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 "indiweatherinterface.h" + +#include +#include + +namespace Connection +{ +class Serial; +} +const char *ADJ_HUB_TAB = "Adjustable HUB"; +const char *POWER_TAB = "Power"; +const char *DEW_TAB = "Dew"; +class PegasusSPB : public INDI::DefaultDevice, public INDI::WeatherInterface +{ + + public: + PegasusSPB(); + + virtual bool initProperties() override; + virtual bool updateProperties() 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; + + enum PORT_MODE + { + DEW = 0, + POWER = 1 + }; + enum + { + PA_NAME, + PA_VOLTAGE, + PA_CURRENT, + PA_TEMPERATURE, + PA_HUMIDITY, + PA_DEW_POINT, + PA_PORT_STATUS, + PA_ADJ_STATUS, + PA_DEW_1, + PA_DEW_2, + PA_AUTO_DEW, + PA_PWR_WARN, + PA_PWRADJ, + PA_N, + }; + enum + { + PS_NAME, + PS_AVG_AMPS, + PS_AMP_HOURS, + PS_WATT_HOURS, + PS_UPTIME, + PS_N, + }; + enum + { + PC_NAME, + PC_TOTAL_CURRENT, + PC_12V_CURRENT, + PC_DEWA_CURRENT, + PC_DEWB_CURRENT, + PC_UPTIME, + PC_N, + }; + enum + { + SENSOR_VOLTAGE, + SENSOR_CURRENT, + SENSOR_AVG_AMPS, + SENSOR_AMP_HOURS, + SENSOR_WATT_HOURS, + SENSOR_TOTAL_CURRENT, + SENSOR_12V_CURRENT, + SENSOR_DEWA_CURRENT, + SENSOR_DEWB_CURRENT + }; + protected: + const char *getDefaultName() override; + //virtual bool saveConfigItems(FILE *fp) override; + + virtual void TimerHit() override; + + // Weather Overrides + virtual IPState updateWeather() override + { + return IPS_OK; + } + + + + private: + bool Handshake(); + + bool sendCommand(const char *cmd, char *res); + bool setQuadPowerState(bool enabled); + bool setPowerDewPortMode(int portNumber, int mode); + int getPowerDewPortMode(int portNumber); + bool setPowerPortState(int portNumber, bool enabled); + bool getPowerPortState(int portNumber); + int getDewPortPower(int portNumber); + bool setDewPortPower(int portNumber, int power); + bool setDewAutoState(bool enabled); + bool setDewAggressiveness(uint8_t level); + int getDewAggressiveness(); + bool setHumdityOffset(int level); + int getHumidityOffset(); + bool setTemperatureOffset(int level); + int getTemperatureOffset(); + bool getSensorData(); + bool getConsumptionData(); + bool getMetricsData(); + void updatePropertiesPowerDewMode(int portNumber, int mode); + + + + int PortFD { -1 }; + bool setupComplete { false }; + Connection::Serial *serialConnection { nullptr }; + + std::vector split(const std::string &input, const std::string ®ex); + std::vector lastSensorData; + std::vector lastConsumptionData; + std::vector lastMetricsData; + char stopChar { 0xD }; + static constexpr const uint8_t PEGASUS_TIMEOUT {3}; + static constexpr const uint8_t PEGASUS_LEN {128}; + static constexpr const char *ENVIRONMENT_TAB {"Environment"}; + double map(double value, double from1, double to1, double from2, double to2); + + + + + //////////////////////////////////////////////////////////////////////////////////// + /// Main Control + //////////////////////////////////////////////////////////////////////////////////// + + // Power Sensors + INDI::PropertyNumber PowerSensorsNP {9}; + + //////////////////////////////////////////////////////////////////////////////////// + /// Adjustable Hub + //////////////////////////////////////////////////////////////////////////////////// + INDI::PropertySwitch PowerDewSwitchASP {2}; + INDI::PropertySwitch PowerAdjASP {2}; + INDI::PropertyNumber DewAdjANP {1}; + + INDI::PropertySwitch PowerDewSwitchBSP {2}; + INDI::PropertySwitch PowerAdjBSP {2}; + INDI::PropertyNumber DewAdjBNP {1}; + + INDI::PropertySwitch DewAutoSP {2}; + INDI::PropertyNumber DewAggressNP {1}; + + //////////////////////////////////////////////////////////////////////////////////// + /// Power Group + //////////////////////////////////////////////////////////////////////////////////// + + + INDI::PropertySwitch QuadPowerSP {2}; + + //////////////////////////////////////////////////////////////////////////////////// + /// Sensor Offset + //////////////////////////////////////////////////////////////////////////////////// + INDI::PropertyNumber HumidityOffsetNP {1}; + INDI::PropertyNumber TemperatureOffsetNP {1}; + +};