67 UPSNameTP[0].
fill(
"NAME",
"UPS Name",
"ups");
71 ConnectionSettingsNP[0].
fill(
"RETRIES",
"Max Retries",
"%.0f", 1, 10, 1, 3);
72 ConnectionSettingsNP[1].
fill(
"RETRY_DELAY",
"Retry Delay (ms)",
"%.0f", 100, 5000, 100, 1000);
76 UpdatePeriodNP[0].
fill(
"PERIOD",
"Period (s)",
"%.1f", 1, 3600, 1, 10);
80 UPSParametersNP[UPS_BATTERY_CHARGE].
fill(
"BATTERY_CHARGE",
"Battery Charge (%)",
"%.1f", 0, 100, 0, 0);
81 UPSParametersNP[UPS_BATTERY_VOLTAGE].
fill(
"BATTERY_VOLTAGE",
"Battery Voltage (V)",
"%.2f", 0, 100, 0, 0);
82 UPSParametersNP[UPS_INPUT_VOLTAGE].
fill(
"INPUT_VOLTAGE",
"Input Voltage (V)",
"%.2f", 0, 300, 0, 0);
86 BatteryThresholdsNP[BATTERY_WARNING_THRESHOLD].
fill(
"WARNING",
"Warning Level (%)",
"%.0f", 0, 100, 5, 25);
87 BatteryThresholdsNP[BATTERY_CRITICAL_THRESHOLD].
fill(
"CRITICAL",
"Critical Level (%)",
"%.0f", 0, 100, 5, 15);
114 LOG_INFO(
"Starting handshake with NUT server...");
119 LOG_ERROR(
"Invalid port file descriptor during handshake.");
123 LOGF_DEBUG(
"Handshake: Using file descriptor %d", PortFD);
127 if (fcntl(PortFD, F_GETFL, &socket_test) < 0) {
133 std::this_thread::sleep_for(std::chrono::milliseconds(100));
136 std::string response = makeNUTRequest(
"VER");
137 if (response.empty())
139 LOG_ERROR(
"Handshake failed: NUT server did not respond to VER command");
143 LOGF_DEBUG(
"Handshake: VER command response: %s", response.c_str());
146 response = makeNUTRequest(
"LIST UPS");
147 if (response.empty())
149 LOG_ERROR(
"Handshake failed: NUT server did not respond to LIST UPS command");
153 LOGF_DEBUG(
"Handshake: LIST UPS command response: %s", response.c_str());
156 if (!queryUPSStatus())
158 LOG_ERROR(
"Handshake failed: could not query UPS status for the configured UPS name");
162 LOG_INFO(
"Handshake successful, connected to NUT server");
172 m_TimerID =
SetTimer(
static_cast<uint32_t
>(UpdatePeriodNP[0].getValue() * 1000));
185 LOG_INFO(
"Disconnected from NUT server.");
220 m_TimerID =
SetTimer(
static_cast<uint32_t
>(UpdatePeriodNP[0].getValue() * 1000));
223void UPS::updateUPSStatus()
230 LOG_ERROR(
"Connection lost, invalid file descriptor");
232 SafetyStatusLP.
apply();
236 if (!queryUPSStatus())
238 LastParseSuccess =
false;
240 SafetyStatusLP.
apply();
247 double batteryCharge = 0;
248 bool hasCharge =
false;
251 if (upsParameters.count(
"battery.charge"))
253 batteryCharge = std::stod(upsParameters[
"battery.charge"]);
254 UPSParametersNP[UPS_BATTERY_CHARGE].setValue(batteryCharge);
259 if (upsParameters.count(
"battery.voltage"))
261 double voltage = std::stod(upsParameters[
"battery.voltage"]);
262 UPSParametersNP[UPS_BATTERY_VOLTAGE].setValue(voltage);
266 if (upsParameters.count(
"input.voltage"))
268 double voltage = std::stod(upsParameters[
"input.voltage"]);
269 UPSParametersNP[UPS_INPUT_VOLTAGE].setValue(voltage);
273 UPSParametersNP.
apply();
280 double criticalThreshold = BatteryThresholdsNP[BATTERY_CRITICAL_THRESHOLD].getValue();
281 double warningThreshold = BatteryThresholdsNP[BATTERY_WARNING_THRESHOLD].getValue();
283 if (batteryCharge <= criticalThreshold)
286 LOGF_WARN(
"Battery critically low: %.1f%% (<= %.0f%%)", batteryCharge, criticalThreshold);
288 else if (batteryCharge <= warningThreshold)
291 LOGF_WARN(
"Battery low: %.1f%% (<= %.0f%%)", batteryCharge, warningThreshold);
296 LOGF_DEBUG(
"Battery normal: %.1f%%", batteryCharge);
300 SafetyStatusLP.
setState(safetyState);
301 SafetyStatusLP.
apply();
303 LastParseSuccess =
true;
305 catch (
const std::exception &e)
307 LOGF_ERROR(
"Error parsing UPS status: %s", e.what());
308 LastParseSuccess =
false;
310 SafetyStatusLP.
apply();
314bool UPS::queryUPSStatus()
317 upsParameters.clear();
322 std::string response = makeNUTRequest(
"LIST VAR " + std::string(UPSNameTP[0].
getText()));
323 if (response.empty())
325 LOG_ERROR(
"Failed to get UPS variables");
328 std::string upsListResponse = makeNUTRequest(
"LIST UPS");
329 if (!upsListResponse.empty()) {
330 LOGF_DEBUG(
"Available UPS units: %s", upsListResponse.c_str());
336 LOGF_DEBUG(
"Response from LIST VAR: %s", response.c_str());
337 return parseUPSResponse(response);
340bool UPS::parseUPSResponse(
const std::string& response)
342 std::istringstream iss(response);
346 LOGF_DEBUG(
"Parsing response of %zu bytes", response.length());
349 if (response.find(
"ERR ") != std::string::npos) {
350 size_t pos = response.find(
"ERR ");
351 size_t end = response.find(
'\n', pos);
352 std::string errorMsg = (end != std::string::npos) ?
353 response.substr(pos, end - pos) :
354 response.substr(pos);
356 LOGF_ERROR(
"NUT server returned error: %s", errorMsg.c_str());
360 while (std::getline(iss, line))
364 if (line.empty() || line ==
"END") {
369 if (line.substr(0, 4) ==
"VAR ")
371 LOGF_DEBUG(
"Processing line: %s", line.c_str());
373 std::istringstream lineStream(line);
374 std::string var,
ups, name;
375 lineStream >> var >>
ups >> name;
378 size_t firstQuote = line.find(
'"');
379 size_t lastQuote = line.rfind(
'"');
380 if (firstQuote != std::string::npos && lastQuote != std::string::npos && firstQuote < lastQuote)
382 std::string value = line.substr(firstQuote + 1, lastQuote - firstQuote - 1);
383 upsParameters[name] = value;
384 LOGF_DEBUG(
"UPS Parameter: %s = %s", name.c_str(), value.c_str());
387 LOGF_DEBUG(
"Could not extract quoted value from line: %s", line.c_str());
391 LOGF_DEBUG(
"Ignoring non-VAR line: %s", line.c_str());
395 LOGF_DEBUG(
"Parsed %d lines, found %zu parameters", lineCount, upsParameters.size());
396 return !upsParameters.empty();
399std::string UPS::makeNUTRequest(
const std::string& command)
401 int retries =
static_cast<int>(ConnectionSettingsNP[0].getValue());
402 int retryDelay =
static_cast<int>(ConnectionSettingsNP[1].getValue());
404 LOGF_DEBUG(
"NUT Command: %s", command.c_str());
411 LOG_ERROR(
"Invalid port file descriptor");
418 std::string request = command +
"\n";
419 int nbytes_written = 0;
422 tcflush(PortFD, TCIOFLUSH);
430 LOGF_ERROR(
"Error sending command: %s", errorMsg);
434 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
440 LOGF_DEBUG(
"Sent %d bytes to NUT server", nbytes_written);
443 std::string fullResponse;
446 int readAttempts = 3;
450 while(readAttempts > 0) {
452 memset(buffer, 0,
sizeof(buffer));
457 if (rc ==
TTY_OK && nbytes_read > 0) {
458 buffer[nbytes_read] =
'\0';
464 if (strstr(buffer,
"END") !=
nullptr || strstr(buffer,
"ERR") !=
nullptr) {
472 if (!fullResponse.empty()) {
473 LOGF_DEBUG(
"Read timeout after receiving %zu bytes total", fullResponse.length());
480 LOGF_ERROR(
"Error reading response: %s (error code: %d)", errorMsg, rc);
488 if (fullResponse.empty()) {
489 LOGF_WARN(
"No response received from NUT server for command: %s", command.c_str());
492 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
498 LOGF_DEBUG(
"Received full response (%zu bytes): %s", fullResponse.length(), fullResponse.c_str());
501 catch (
const std::exception& e)
509 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
516bool UPS::ISNewText(
const char *dev,
const char *name,
char *texts[],
char *names[],
int n)
522 UPSNameTP.
update(texts, names, n);
533bool UPS::ISNewNumber(
const char *dev,
const char *name,
double values[],
char *names[],
int n)
539 ConnectionSettingsNP.
update(values, names, n);
541 ConnectionSettingsNP.
apply();
547 UpdatePeriodNP.
update(values, names, n);
549 UpdatePeriodNP.
apply();
555 m_TimerID =
SetTimer(
static_cast<uint32_t
>(UpdatePeriodNP[0].getValue() * 1000));
563 BatteryThresholdsNP.
update(values, names, n);
565 BatteryThresholdsNP.
apply();
584 ConnectionSettingsNP.
save(fp);
585 UpdatePeriodNP.
save(fp);
586 BatteryThresholdsNP.
save(fp);
595 if (property ==
nullptr)
598 ConnectionSettingsNP.
load();
599 UpdatePeriodNP.
load();
600 BatteryThresholdsNP.
load();
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The TCP class manages connection with devices over the network via TCP/IP. Upon successful connection...
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
bool isDeviceNameMatch(const char *otherName) const
Check that the device name matches the argument.
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool Disconnect()
Disconnect from device.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
void RemoveTimer(int id)
Remove timer added with SetTimer.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
void addDebugControl()
Add Debug control to the driver.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPState state)
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool Disconnect() override
Disconnect from device.
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool loadConfig(bool silent=false, const char *property=nullptr) override
Load the last saved configuration file.
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
const char * getDefaultName() override
const char * CONNECTION_TAB
CONNECTION_TAB Where all device connection settings (serial, usb, ethernet) are defined and controlle...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Implementations for common driver routines.
#define LOGF_WARN(fmt,...)
#define LOGF_DEBUG(fmt,...)
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)
std::vector< uint8_t > buffer
std::unique_ptr< UPS > ups(new UPS())