Instrument Neutral Distributed Interface INDI 2.2.5
ups.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2025 Jérémie Klein. All rights reserved.
3
4 INDI UPS Driver using NUT (Network UPS Tools)
5 This driver monitors a UPS through NUT and exposes battery status via SAFETY_STATUS.
6
7 This program is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2 of the License, or (at your option)
10 any later version.
11
12 This program is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21
22 The full GNU General Public License is included in this distribution in the
23 file called LICENSE.
24*******************************************************************************/
25
26#include <memory>
27#include <chrono>
28#include <thread>
29#include <cstdio>
30#include <cstring>
31#include <unistd.h>
32#include <sstream>
33#include <termios.h>
34#include <fcntl.h>
35#include "ups.h"
36#include "indicom.h"
37
38// We declare an auto pointer to UPS
39std::unique_ptr<UPS> ups(new UPS());
40
42{
43 setVersion(1, 0);
44
45 // Create TCP Connection
46 tcpConnection = new Connection::TCP(this);
47 // Set default values for TCP connection
48 tcpConnection->setDefaultHost("localhost");
49 tcpConnection->setDefaultPort(3493); // Default NUT port
50 // Register the handshake function
51 tcpConnection->registerHandshake([&]() { return Handshake(); });
52
53 // Register connection
54 registerConnection(tcpConnection);
55}
56
58{
59 return "UPS";
60}
61
63{
65
66 // Setup UPS name property
67 UPSNameTP[0].fill("NAME", "UPS Name", "ups"); // Default UPS name
68 UPSNameTP.fill(getDeviceName(), "UPS_NAME", "UPS", CONNECTION_TAB, IP_RW, 60, IPS_IDLE);
69
70 // Setup connection settings properties
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);
73 ConnectionSettingsNP.fill(getDeviceName(), "CONNECTION_SETTINGS", "Connection", CONNECTION_TAB, IP_RW, 60, IPS_IDLE);
74
75 // Setup update period
76 UpdatePeriodNP[0].fill("PERIOD", "Period (s)", "%.1f", 1, 3600, 1, 10);
77 UpdatePeriodNP.fill(getDeviceName(), "UPDATE_PERIOD", "Update", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
78
79 // UPS parameters
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);
83 UPSParametersNP.fill(getDeviceName(), "UPS_PARAMETERS", "Parameters", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
84
85 // Battery thresholds
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);
88 BatteryThresholdsNP.fill(getDeviceName(), "BATTERY_THRESHOLDS", "Battery Thresholds", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
89
90 // Safety Status property
91 SafetyStatusLP[0].fill("SAFETY", "Safety", IPS_IDLE);
92 SafetyStatusLP.fill(getDeviceName(), "SAFETY_STATUS", "Status", MAIN_CONTROL_TAB, IPS_IDLE);
93
96
97 return true;
98}
99
100void UPS::ISGetProperties(const char *dev)
101{
103
104 // Define UPS name property
105 defineProperty(UPSNameTP);
106 defineProperty(ConnectionSettingsNP);
107 defineProperty(BatteryThresholdsNP);
108
109 loadConfig(true);
110}
111
112bool UPS::Handshake()
113{
114 LOG_INFO("Starting handshake with NUT server...");
115
116 PortFD = tcpConnection->getPortFD();
117 if (PortFD == -1)
118 {
119 LOG_ERROR("Invalid port file descriptor during handshake.");
120 return false;
121 }
122
123 LOGF_DEBUG("Handshake: Using file descriptor %d", PortFD);
124
125 // Check if socket is valid
126 int socket_test = 0;
127 if (fcntl(PortFD, F_GETFL, &socket_test) < 0) {
128 LOGF_ERROR("Socket test failed: %s", strerror(errno));
129 return false;
130 }
131
132 // Wait a bit for connection to be established
133 std::this_thread::sleep_for(std::chrono::milliseconds(100));
134
135 // Test communication with simple command
136 std::string response = makeNUTRequest("VER");
137 if (response.empty())
138 {
139 LOG_ERROR("Handshake failed: NUT server did not respond to VER command");
140 return false;
141 }
142
143 LOGF_DEBUG("Handshake: VER command response: %s", response.c_str());
144
145 // List available UPS
146 response = makeNUTRequest("LIST UPS");
147 if (response.empty())
148 {
149 LOG_ERROR("Handshake failed: NUT server did not respond to LIST UPS command");
150 return false;
151 }
152
153 LOGF_DEBUG("Handshake: LIST UPS command response: %s", response.c_str());
154
155 // Try to query the configured UPS
156 if (!queryUPSStatus())
157 {
158 LOG_ERROR("Handshake failed: could not query UPS status for the configured UPS name");
159 return false;
160 }
161
162 LOG_INFO("Handshake successful, connected to NUT server");
163 return true;
164}
165
167{
169 return false;
170
171 // Start timer for periodic updates
172 m_TimerID = SetTimer(static_cast<uint32_t>(UpdatePeriodNP[0].getValue() * 1000));
173
174 return true;
175}
176
178{
179 if (m_TimerID > 0)
180 {
181 RemoveTimer(m_TimerID);
182 m_TimerID = -1;
183 }
184
185 LOG_INFO("Disconnected from NUT server.");
187}
188
190{
192
193 if (isConnected())
194 {
195 defineProperty(UpdatePeriodNP);
196 defineProperty(UPSParametersNP);
197 defineProperty(SafetyStatusLP);
198
199 // Initial update
200 updateUPSStatus();
201 }
202 else
203 {
204 deleteProperty(UpdatePeriodNP);
205 deleteProperty(UPSParametersNP);
206 deleteProperty(SafetyStatusLP);
207 }
208
209 return true;
210}
211
213{
214 if (!isConnected())
215 return;
216
217 updateUPSStatus();
218
219 // Re-arm timer
220 m_TimerID = SetTimer(static_cast<uint32_t>(UpdatePeriodNP[0].getValue() * 1000));
221}
222
223void UPS::updateUPSStatus()
224{
225 // Get the file descriptor from the TCP connection
226 PortFD = tcpConnection->getPortFD();
227
228 if (PortFD == -1)
229 {
230 LOG_ERROR("Connection lost, invalid file descriptor");
231 SafetyStatusLP.setState(IPS_ALERT);
232 SafetyStatusLP.apply();
233 return;
234 }
235
236 if (!queryUPSStatus())
237 {
238 LastParseSuccess = false;
239 SafetyStatusLP.setState(IPS_ALERT);
240 SafetyStatusLP.apply();
241 return;
242 }
243
244 // Update UPS parameters
245 try
246 {
247 double batteryCharge = 0;
248 bool hasCharge = false;
249
250 // Battery charge
251 if (upsParameters.count("battery.charge"))
252 {
253 batteryCharge = std::stod(upsParameters["battery.charge"]);
254 UPSParametersNP[UPS_BATTERY_CHARGE].setValue(batteryCharge);
255 hasCharge = true;
256 }
257
258 // Battery voltage
259 if (upsParameters.count("battery.voltage"))
260 {
261 double voltage = std::stod(upsParameters["battery.voltage"]);
262 UPSParametersNP[UPS_BATTERY_VOLTAGE].setValue(voltage);
263 }
264
265 // Input voltage
266 if (upsParameters.count("input.voltage"))
267 {
268 double voltage = std::stod(upsParameters["input.voltage"]);
269 UPSParametersNP[UPS_INPUT_VOLTAGE].setValue(voltage);
270 }
271
272 UPSParametersNP.setState(IPS_OK);
273 UPSParametersNP.apply();
274
275 // Determine safety status based on battery charge
276 IPState safetyState = IPS_IDLE;
277
278 if (hasCharge)
279 {
280 double criticalThreshold = BatteryThresholdsNP[BATTERY_CRITICAL_THRESHOLD].getValue();
281 double warningThreshold = BatteryThresholdsNP[BATTERY_WARNING_THRESHOLD].getValue();
282
283 if (batteryCharge <= criticalThreshold)
284 {
285 safetyState = IPS_ALERT;
286 LOGF_WARN("Battery critically low: %.1f%% (<= %.0f%%)", batteryCharge, criticalThreshold);
287 }
288 else if (batteryCharge <= warningThreshold)
289 {
290 safetyState = IPS_BUSY;
291 LOGF_WARN("Battery low: %.1f%% (<= %.0f%%)", batteryCharge, warningThreshold);
292 }
293 else
294 {
295 safetyState = IPS_OK;
296 LOGF_DEBUG("Battery normal: %.1f%%", batteryCharge);
297 }
298 }
299
300 SafetyStatusLP.setState(safetyState);
301 SafetyStatusLP.apply();
302
303 LastParseSuccess = true;
304 }
305 catch (const std::exception &e)
306 {
307 LOGF_ERROR("Error parsing UPS status: %s", e.what());
308 LastParseSuccess = false;
309 SafetyStatusLP.setState(IPS_ALERT);
310 SafetyStatusLP.apply();
311 }
312}
313
314bool UPS::queryUPSStatus()
315{
316 // Clear previous parameters
317 upsParameters.clear();
318
319 LOGF_DEBUG("Querying UPS status for '%s'", UPSNameTP[0].getText());
320
321 // Get UPS variables
322 std::string response = makeNUTRequest("LIST VAR " + std::string(UPSNameTP[0].getText()));
323 if (response.empty())
324 {
325 LOG_ERROR("Failed to get UPS variables");
326
327 // Try a LIST UPS command to verify if the UPS exists
328 std::string upsListResponse = makeNUTRequest("LIST UPS");
329 if (!upsListResponse.empty()) {
330 LOGF_DEBUG("Available UPS units: %s", upsListResponse.c_str());
331 }
332
333 return false;
334 }
335
336 LOGF_DEBUG("Response from LIST VAR: %s", response.c_str());
337 return parseUPSResponse(response);
338}
339
340bool UPS::parseUPSResponse(const std::string& response)
341{
342 std::istringstream iss(response);
343 std::string line;
344 int lineCount = 0;
345
346 LOGF_DEBUG("Parsing response of %zu bytes", response.length());
347
348 // Check for error first
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);
355
356 LOGF_ERROR("NUT server returned error: %s", errorMsg.c_str());
357 return false;
358 }
359
360 while (std::getline(iss, line))
361 {
362 lineCount++;
363 // Ignore empty lines or end markers
364 if (line.empty() || line == "END") {
365 continue;
366 }
367
368 // Parse NUT response format: VAR ups variablename "value"
369 if (line.substr(0, 4) == "VAR ")
370 {
371 LOGF_DEBUG("Processing line: %s", line.c_str());
372
373 std::istringstream lineStream(line);
374 std::string var, ups, name;
375 lineStream >> var >> ups >> name;
376
377 // Extract value between quotes
378 size_t firstQuote = line.find('"');
379 size_t lastQuote = line.rfind('"');
380 if (firstQuote != std::string::npos && lastQuote != std::string::npos && firstQuote < lastQuote)
381 {
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());
385 }
386 else {
387 LOGF_DEBUG("Could not extract quoted value from line: %s", line.c_str());
388 }
389 }
390 else {
391 LOGF_DEBUG("Ignoring non-VAR line: %s", line.c_str());
392 }
393 }
394
395 LOGF_DEBUG("Parsed %d lines, found %zu parameters", lineCount, upsParameters.size());
396 return !upsParameters.empty();
397}
398
399std::string UPS::makeNUTRequest(const std::string& command)
400{
401 int retries = static_cast<int>(ConnectionSettingsNP[0].getValue());
402 int retryDelay = static_cast<int>(ConnectionSettingsNP[1].getValue());
403
404 LOGF_DEBUG("NUT Command: %s", command.c_str());
405
406 while (retries > 0)
407 {
408 // Check if port is valid
409 if (PortFD == -1)
410 {
411 LOG_ERROR("Invalid port file descriptor");
412 return "";
413 }
414
415 try
416 {
417 // Send command with newline
418 std::string request = command + "\n";
419 int nbytes_written = 0;
420
421 // Flush any lingering data before sending new command
422 tcflush(PortFD, TCIOFLUSH);
423
424 int rc = tty_write_string(PortFD, request.c_str(), &nbytes_written);
425
426 if (rc != TTY_OK)
427 {
428 char errorMsg[MAXRBUF];
429 tty_error_msg(rc, errorMsg, MAXRBUF);
430 LOGF_ERROR("Error sending command: %s", errorMsg);
431 retries--;
432 if (retries > 0)
433 {
434 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
435 continue;
436 }
437 return "";
438 }
439
440 LOGF_DEBUG("Sent %d bytes to NUT server", nbytes_written);
441
442 // Collect multiline response
443 std::string fullResponse;
444 char buffer[4096] = {0};
445
446 int readAttempts = 3;
447 int timeout = 2; // seconds
448
449 // Read data until timeout or receiving "END"
450 while(readAttempts > 0) {
451 int nbytes_read = 0;
452 memset(buffer, 0, sizeof(buffer));
453
454 // Read one line at a time
455 rc = tty_read_section(PortFD, buffer, '\n', timeout, &nbytes_read);
456
457 if (rc == TTY_OK && nbytes_read > 0) {
458 buffer[nbytes_read] = '\0';
459 LOGF_DEBUG("Read line: %s", buffer);
460
461 fullResponse += buffer;
462
463 // If line contains "END" or "ERR", it's the end of NUT command response
464 if (strstr(buffer, "END") != nullptr || strstr(buffer, "ERR") != nullptr) {
465 break;
466 }
467
468 continue;
469 }
470 else if (rc == TTY_TIME_OUT) {
471 // If we already have data and no more to read, we're done
472 if (!fullResponse.empty()) {
473 LOGF_DEBUG("Read timeout after receiving %zu bytes total", fullResponse.length());
474 break;
475 }
476 }
477 else {
478 char errorMsg[MAXRBUF];
479 tty_error_msg(rc, errorMsg, MAXRBUF);
480 LOGF_ERROR("Error reading response: %s (error code: %d)", errorMsg, rc);
481 readAttempts--;
482 continue;
483 }
484
485 readAttempts--;
486 }
487
488 if (fullResponse.empty()) {
489 LOGF_WARN("No response received from NUT server for command: %s", command.c_str());
490 retries--;
491 if (retries > 0) {
492 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
493 continue;
494 }
495 return "";
496 }
497
498 LOGF_DEBUG("Received full response (%zu bytes): %s", fullResponse.length(), fullResponse.c_str());
499 return fullResponse;
500 }
501 catch (const std::exception& e)
502 {
503 LOGF_ERROR("Request error: %s", e.what());
504 }
505
506 retries--;
507 if (retries > 0)
508 {
509 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
510 }
511 }
512
513 return "";
514}
515
516bool UPS::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
517{
518 if (isDeviceNameMatch(dev))
519 {
520 if (UPSNameTP.isNameMatch(name))
521 {
522 UPSNameTP.update(texts, names, n);
523 UPSNameTP.setState(IPS_OK);
524 UPSNameTP.apply();
525 saveConfig();
526 return true;
527 }
528 }
529
530 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
531}
532
533bool UPS::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
534{
535 if (isDeviceNameMatch(dev))
536 {
537 if (ConnectionSettingsNP.isNameMatch(name))
538 {
539 ConnectionSettingsNP.update(values, names, n);
540 ConnectionSettingsNP.setState(IPS_OK);
541 ConnectionSettingsNP.apply();
542 saveConfig();
543 return true;
544 }
545 else if (UpdatePeriodNP.isNameMatch(name))
546 {
547 UpdatePeriodNP.update(values, names, n);
548 UpdatePeriodNP.setState(IPS_OK);
549 UpdatePeriodNP.apply();
550
551 // Restart timer with new period
552 if (m_TimerID > 0)
553 {
554 RemoveTimer(m_TimerID);
555 m_TimerID = SetTimer(static_cast<uint32_t>(UpdatePeriodNP[0].getValue() * 1000));
556 }
557
558 saveConfig();
559 return true;
560 }
561 else if (BatteryThresholdsNP.isNameMatch(name))
562 {
563 BatteryThresholdsNP.update(values, names, n);
564 BatteryThresholdsNP.setState(IPS_OK);
565 BatteryThresholdsNP.apply();
566
567 // Re-evaluate safety status with new thresholds
568 if (isConnected())
569 updateUPSStatus();
570
571 saveConfig();
572 return true;
573 }
574 }
575
576 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
577}
578
580{
582
583 UPSNameTP.save(fp);
584 ConnectionSettingsNP.save(fp);
585 UpdatePeriodNP.save(fp);
586 BatteryThresholdsNP.save(fp);
587
588 return true;
589}
590
591bool UPS::loadConfig(bool silent, const char *property)
592{
593 bool result = INDI::DefaultDevice::loadConfig(silent, property);
594
595 if (property == nullptr)
596 {
597 UPSNameTP.load();
598 ConnectionSettingsNP.load();
599 UpdatePeriodNP.load();
600 BatteryThresholdsNP.load();
601 }
602
603 return result;
604}
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)
int getPortFD() const
bool isConnected() const
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)
Definition ups.h:42
bool Disconnect() override
Disconnect from device.
Definition ups.cpp:177
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition ups.cpp:212
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
Definition ups.cpp:579
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
Definition ups.cpp:166
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition ups.cpp:189
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition ups.cpp:516
virtual bool loadConfig(bool silent=false, const char *property=nullptr) override
Load the last saved configuration file.
Definition ups.cpp:591
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
Definition ups.cpp:100
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition ups.cpp:533
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition ups.cpp:62
const char * getDefaultName() override
Definition ups.cpp:57
UPS()
Definition ups.cpp:41
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 errno
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:653
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
@ TTY_TIME_OUT
Definition indicom.h:58
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
std::vector< uint8_t > buffer
std::unique_ptr< UPS > ups(new UPS())