Instrument Neutral Distributed Interface INDI 2.2.5
wanderer_cover_v4_ec.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2024 Frank Wang/Jérémie Klein. All rights reserved.
3
4 WandererCover V4-EC
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 The full GNU General Public License is included in this distribution in the
22 file called LICENSE.
23*******************************************************************************/
24
26#include "indicom.h"
28#include <cstring>
29#include <string>
30#include <memory>
31#include <termios.h>
32#include <unistd.h>
33#include <sys/ioctl.h>
34#include <mutex>
35#include <chrono>
36#include <sstream>
37#include <algorithm>
38
39static std::unique_ptr<WandererCoverV4EC> wanderercoverv4ec(new WandererCoverV4EC());
40
41// Protocol implementations
42bool WandererCoverLegacyProtocol::supportsFeature(const std::string& feature) const
43{
44 // Legacy protocol supports basic features only
45 static const std::vector<std::string> supportedFeatures = {
46 "cover_control", "light_control", "heater_control", "position_setting"
47 };
48 return std::find(supportedFeatures.begin(), supportedFeatures.end(), feature) != supportedFeatures.end();
49}
50
52{
53 // Parse firmware version from data
54 std::string dataStr(data);
55 std::vector<std::string> tokens;
56 std::string token;
57 std::istringstream tokenStream(dataStr);
58
59 while (std::getline(tokenStream, token, 'A'))
60 {
61 if (!token.empty())
62 tokens.push_back(token);
63 }
64
65 // Check if we have enough tokens and the first one is correct
66 if (tokens.size() < 2)
67 return false;
68
69 // Enhanced device detection for legacy protocol
70 std::string deviceName = tokens[0];
71
72 // Check for exact match first
73 if (deviceName == "WandererCoverV4")
74 {
75 // Parse firmware version
76 int firmwareVersion = std::atoi(tokens[1].c_str());
77
78 // Legacy protocol for firmware < 20250404
79 return firmwareVersion < 20250404 && firmwareVersion > 0;
80 }
81
82 return false;
83}
84
86{
87 try
88 {
89 // For legacy protocol, we need to parse the data differently
90 // The data comes as a single string but we need to extract individual values
91 std::string dataStr(data);
92
93 // Remove any trailing newlines or carriage returns
94 dataStr.erase(std::remove(dataStr.begin(), dataStr.end(), '\n'), dataStr.end());
95 dataStr.erase(std::remove(dataStr.begin(), dataStr.end(), '\r'), dataStr.end());
96
97 // Split by 'A' separator
98 std::vector<std::string> tokens;
99 std::string token;
100 std::istringstream tokenStream(dataStr);
101
102 while (std::getline(tokenStream, token, 'A'))
103 {
104 if (!token.empty())
105 tokens.push_back(token);
106 }
107
108 // Debug: log all tokens
109 for (size_t i = 0; i < tokens.size(); i++)
110 {
111 // Note: Can't use LOGF_DEBUG here as we don't have access to getDeviceName()
112 }
113
114 // Check if we have enough tokens for legacy protocol
115 if (tokens.size() < 4)
116 {
117 return false;
118 }
119
120 // Device Model - check if it's WandererCoverV4
121 std::string deviceName = tokens[0];
122 if (deviceName != "WandererCoverV4")
123 {
124 // Check for similar devices that might be compatible
125 if (deviceName == "ZXWBProV3" || deviceName == "ZXWBPlusV3" ||
126 deviceName == "UltimateV2" || deviceName == "PlusV2")
127 {
128 // Note: Can't use LOG_WARN here as we don't have access to getDeviceName()
129 }
130 else
131 {
132 // Note: Can't use LOG_ERROR here as we don't have access to getDeviceName()
133 }
134 return false;
135 }
136
137 // Firmware version
138 device->firmware = std::atoi(tokens[1].c_str());
139
140 // Update firmware information in the UI
141 char firmwareStr[16];
142 snprintf(firmwareStr, sizeof(firmwareStr), "%d", device->firmware);
143 device->FirmwareTP[device->FIRMWARE_VERSION].setText(firmwareStr);
144 device->FirmwareTP.setState(IPS_OK);
145 device->FirmwareTP.apply();
146
147 // For legacy protocol, try to parse based on the actual data structure
148 // Based on the old driver, the structure is: Device, Firmware, CloseSet, OpenSet, CurrentPos, Voltage
149 if (tokens.size() >= 5)
150 {
151 // Standard legacy format: Device, Firmware, CloseSet, OpenSet, CurrentPos, Voltage
152 device->closesetread = std::strtod(tokens[2].c_str(), NULL);
153 device->opensetread = std::strtod(tokens[3].c_str(), NULL);
154 device->positionread = std::strtod(tokens[4].c_str(), NULL);
155
156 if (tokens.size() > 5)
157 {
158 device->voltageread = std::strtod(tokens[5].c_str(), NULL);
159 }
160 else
161 {
162 device->voltageread = 0.0;
163 }
164 }
165 else
166 {
167 return false;
168 }
169
170 // Update status data
171 device->statusData.firmware = device->firmware;
172 device->statusData.closePositionSet = device->closesetread;
173 device->statusData.openPositionSet = device->opensetread;
174 device->statusData.currentPosition = device->positionread;
175 device->statusData.voltage = device->voltageread;
176
177 // Update the UI with the parsed data
178 device->updateData(device->closesetread, device->opensetread, device->positionread, device->voltageread,device->flatpanelbrightnessread, device->dewheaterpowerread,device->asiaircontrolenabledread);
179
180 // Update the Close and Open position settings with the values from the device
181 device->CloseSetNP[device->CloseSet].setValue(device->closesetread);
182 device->CloseSetNP.setState(IPS_OK);
183 device->CloseSetNP.apply();
184
185 device->OpenSetNP[device->OpenSet].setValue(device->opensetread);
186 device->OpenSetNP.setState(IPS_OK);
187 device->OpenSetNP.apply();
188
189 return true;
190 }
191 catch(std::exception &e)
192 {
193 // Note: Can't use logging here as we don't have access to getDeviceName()
194 return false;
195 }
196}
197
198std::string WandererCoverLegacyProtocol::generateOpenCommand() const { return "1001"; }
199std::string WandererCoverLegacyProtocol::generateCloseCommand() const { return "1000"; }
200std::string WandererCoverLegacyProtocol::generateSetBrightnessCommand(uint16_t value) const { return std::to_string(value); }
202std::string WandererCoverLegacyProtocol::generateSetOpenPositionCommand(double value) const { return std::to_string((int)(value * 100 + 40000)); }
203std::string WandererCoverLegacyProtocol::generateSetClosePositionCommand(double value) const { return std::to_string((int)(value * 100 + 10000)); }
206std::string WandererCoverLegacyProtocol::generateDewHeaterCommand(int value) const { return std::to_string(2000 + value); }
207std::string WandererCoverLegacyProtocol::generateASIAIRControlCommand(bool enable) const { return enable ? "1500003" : "1500004"; }
208std::string WandererCoverLegacyProtocol::generateCustomBrightnessCommand(int brightness, int customNumber) const { return std::to_string(customNumber * 1000000 + brightness); }
209
210// Modern protocol implementation
211bool WandererCoverModernProtocol::supportsFeature(const std::string& feature) const
212{
213 // Modern protocol supports all features
214 static const std::vector<std::string> supportedFeatures = {
215 "cover_control", "light_control", "heater_control", "position_setting",
216 "asiair_control", "custom_brightness", "auto_detect", "extended_status"
217 };
218 return std::find(supportedFeatures.begin(), supportedFeatures.end(), feature) != supportedFeatures.end();
219}
220
222{
223 // Parse firmware version from data
224 std::string dataStr(data);
225 std::vector<std::string> tokens;
226 std::string token;
227 std::istringstream tokenStream(dataStr);
228
229 while (std::getline(tokenStream, token, 'A'))
230 {
231 if (!token.empty())
232 tokens.push_back(token);
233 }
234
235 // Check if we have enough tokens and the first one is correct
236 if (tokens.size() < 2)
237 return false;
238
239 // Enhanced device detection for modern protocol
240 std::string deviceName = tokens[0];
241
242 // Check for exact match first
243 if (deviceName == "WandererCoverV4")
244 {
245 // Parse firmware version
246 int firmwareVersion = std::atoi(tokens[1].c_str());
247
248 // Modern protocol for firmware >= 20250404
249 return firmwareVersion >= 20250404;
250 }
251
252 return false;
253}
254
256{
257 try
258 {
259 std::vector<std::string> tokens;
260 std::string token;
261 std::istringstream tokenStream(data);
262
263 while (std::getline(tokenStream, token, 'A'))
264 {
265 if (!token.empty())
266 tokens.push_back(token);
267 }
268
269 if (tokens.size() < 8)
270 return false;
271
272 // Enhanced device detection for modern protocol
273 std::string deviceName = tokens[0];
274 if (deviceName != "WandererCoverV4")
275 {
276 // Check for similar devices that might be compatible
277 if (deviceName == "ZXWBProV3" || deviceName == "ZXWBPlusV3" ||
278 deviceName == "UltimateV2" || deviceName == "PlusV2")
279 {
280 // Note: Can't use LOG_WARN here as we don't have access to getDeviceName()
281 }
282 else
283 {
284 // Note: Can't use LOG_ERROR here as we don't have access to getDeviceName()
285 }
286 return false;
287 }
288
289 // Parse firmware version
290 device->firmware = std::atoi(tokens[1].c_str());
291
292 // Update firmware information in the UI
293 char firmwareStr[16];
294 snprintf(firmwareStr, sizeof(firmwareStr), "%d", device->firmware);
295 device->FirmwareTP[device->FIRMWARE_VERSION].setText(firmwareStr);
296 device->FirmwareTP.setState(IPS_OK);
297 device->FirmwareTP.apply();
298
299 // Parse position data
300 device->closesetread = std::strtod(tokens[2].c_str(), NULL);
301 device->opensetread = std::strtod(tokens[3].c_str(), NULL);
302 device->positionread = std::strtod(tokens[4].c_str(), NULL);
303 device->voltageread = std::strtod(tokens[5].c_str(), NULL);
304
305 // Parse extended data for modern protocol
306 if (tokens.size() >= 8)
307 {
308 device->flatpanelbrightnessread = std::atoi(tokens[6].c_str());
309 device->statusData.flatPanelBrightness = device->flatpanelbrightnessread;
310 device->dewheaterpowerread = std::atoi(tokens[7].c_str());
311 device->statusData.dewHeaterPower = device->dewheaterpowerread;
312 device->asiaircontrolenabledread = (tokens.size() > 8) ? (std::atoi(tokens[8].c_str()) == 1) : false;
313 device->statusData.asiairControlEnabled = device->asiaircontrolenabledread;
314 }
315
316 // Update status data
317 device->statusData.firmware = device->firmware;
318 device->statusData.closePositionSet = device->closesetread;
319 device->statusData.openPositionSet = device->opensetread;
320 device->statusData.currentPosition = device->positionread;
321 device->statusData.voltage = device->voltageread;
322
323 // Update the UI with the parsed data
324 device->updateData(device->closesetread, device->opensetread, device->positionread, device->voltageread,device->flatpanelbrightnessread, device->dewheaterpowerread,device->asiaircontrolenabledread);
325
326 // Update the Close and Open position settings with the values from the device
327 device->CloseSetNP[device->CloseSet].setValue(device->closesetread);
328 device->CloseSetNP.setState(IPS_OK);
329 device->CloseSetNP.apply();
330
331 device->OpenSetNP[device->OpenSet].setValue(device->opensetread);
332 device->OpenSetNP.setState(IPS_OK);
333 device->OpenSetNP.apply();
334
335 return true;
336 }
337 catch(std::exception &e)
338 {
339 // Note: Can't use LOG_ERROR here as we don't have access to getDeviceName()
340 return false;
341 }
342}
343
344std::string WandererCoverModernProtocol::generateOpenCommand() const { return "1001"; }
345std::string WandererCoverModernProtocol::generateCloseCommand() const { return "1000"; }
346std::string WandererCoverModernProtocol::generateSetBrightnessCommand(uint16_t value) const { return std::to_string(value); }
348std::string WandererCoverModernProtocol::generateSetOpenPositionCommand(double value) const { return std::to_string((int)(value * 100 + 40000)); }
349std::string WandererCoverModernProtocol::generateSetClosePositionCommand(double value) const { return std::to_string((int)(value * 100 + 10000)); }
352std::string WandererCoverModernProtocol::generateDewHeaterCommand(int value) const { return std::to_string(2000 + value); }
353std::string WandererCoverModernProtocol::generateASIAIRControlCommand(bool enable) const { return enable ? "1500003" : "1500004"; }
354std::string WandererCoverModernProtocol::generateCustomBrightnessCommand(int brightness, int customNumber) const { return std::to_string(customNumber * 1000000 + brightness); }
355
360
365{
366 return "WandererCover V4-EC";
367}
368
373{
375
378
381
382 //Data read - extended for new protocol
383 DataNP[closeset_read].fill( "Closed_Position", "Closed Position Set(°)", "%4.2f", 0, 999, 100, 0);
384 DataNP[openset_read].fill( "Open_Position", "Open Position Set(°)", "%4.2f", 0, 999, 100, 0);
385 DataNP[position_read].fill( "Current_Position", "Current Position(°)", "%4.2f", 0, 999, 100, 0);
386 DataNP[voltage_read].fill( "Voltage", "Voltage (V)", "%4.2f", 0, 999, 100, 0);
387 DataNP[flat_panel_brightness_read].fill( "Flat_Panel_Brightness", "Flat Panel Brightness", "%4.2f", 0, 255, 1, 0);
388 DataNP[dew_heater_power_read].fill( "Dew_Heater_Power", "Dew Heater Power", "%4.2f", 0, 150, 1, 0);
389 DataNP[asiair_control_enabled_read].fill( "ASIAIR_Control_Enabled", "ASIAIR Control Enabled", "%4.2f", 0, 1, 1, 0);
390 DataNP.fill(getDeviceName(), "STATUS", "Real Time Status", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
391
392 // Firmware information
393 FirmwareTP[FIRMWARE_VERSION].fill("FIRMWARE_VERSION", "Firmware Version", "Unknown");
394 FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
395
396 LightIntensityNP[0].setMax(255);
397 LightIntensityNP[0].setValue(100);
398
399 // Heater
400 SetHeaterNP[Heat].fill( "Heater", "PWM", "%.2f", 0, 150, 50, 0);
401 SetHeaterNP.fill(getDeviceName(), "Heater", "Dew Heater", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
402
403 // Close Set
404 CloseSetNP[CloseSet].fill( "CloseSet", "10-90", "%.2f", 10, 90, 0.01, 20);
405 CloseSetNP.fill(getDeviceName(), "CloseSet", "Close Position(°)", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
406
407 // Open Set
408 OpenSetNP[OpenSet].fill( "OpenSet", "100-300", "%.2f", 100, 300, 0.01, 150);
409 OpenSetNP.fill(getDeviceName(), "OpenSet", "Open Position(°)", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
410
411 // New protocol features
412 ASIAIRControlSP[ASIAIR_ENABLE].fill("ASIAIR_ENABLE", "Enable ASIAIR Control", ISS_OFF);
413 ASIAIRControlSP[ASIAIR_DISABLE].fill("ASIAIR_DISABLE", "Disable ASIAIR Control", ISS_ON);
414 ASIAIRControlSP.fill(getDeviceName(), "ASIAIR_CONTROL", "ASIAIR Control", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
415
416 CustomBrightnessNP[CUSTOM_BRIGHTNESS_1].fill("CUSTOM_BRIGHTNESS_1", "Custom Brightness 1", "%1.0f", 0, 255, 1, 1);
417 CustomBrightnessNP[CUSTOM_BRIGHTNESS_2].fill("CUSTOM_BRIGHTNESS_2", "Custom Brightness 2", "%1.0f", 0, 255, 1, 50);
418 CustomBrightnessNP[CUSTOM_BRIGHTNESS_3].fill("CUSTOM_BRIGHTNESS_3", "Custom Brightness 3", "%1.0f", 0, 255, 1, 255);
419 CustomBrightnessNP.fill(getDeviceName(), "CUSTOM_BRIGHTNESS", "Custom Brightness", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
420
421 AutoDetectSP[AUTO_DETECT_OPEN].fill("AUTO_DETECT_OPEN", "Auto Detect Open Position", ISS_OFF);
422 AutoDetectSP[AUTO_DETECT_CLOSE].fill("AUTO_DETECT_CLOSE", "Auto Detect Close Position", ISS_OFF);
423 AutoDetectSP.fill(getDeviceName(), "AUTO_DETECT", "Auto Detection", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
424
426
427 serialConnection = new Connection::Serial(this);
429 serialConnection->registerHandshake([&]()
430 {
431 return detectProtocol();
432 });
433 registerConnection(serialConnection);
434
435 return true;
436}
437
442{
444
445 if (isConnected())
446 {
447 // Update firmware information
448 char firmwareStr[16];
449 snprintf(firmwareStr, sizeof(firmwareStr), "%d", firmware);
450 FirmwareTP[FIRMWARE_VERSION].setText(firmwareStr);
451
452
453 // Update the Close and Open position settings with the values from the device
454 CloseSetNP[CloseSet].setValue(closesetread);
455 OpenSetNP[OpenSet].setValue(opensetread);
456
457 defineProperty(DataNP);
458 defineProperty(FirmwareTP);
459 defineProperty(SetHeaterNP);
460 defineProperty(CloseSetNP);
461 defineProperty(OpenSetNP);
462
463 // Define new protocol features if supported
464 if (currentProtocol && currentProtocol->supportsFeature("asiair_control"))
465 {
466 defineProperty(ASIAIRControlSP);
467 }
468 if (currentProtocol && currentProtocol->supportsFeature("custom_brightness"))
469 {
470 defineProperty(CustomBrightnessNP);
471 }
472 if (currentProtocol && currentProtocol->supportsFeature("auto_detect"))
473 {
474 defineProperty(AutoDetectSP);
475 }
476 }
477 else
478 {
479 deleteProperty(DataNP);
480 deleteProperty(FirmwareTP);
481 deleteProperty(SetHeaterNP);
482 deleteProperty(OpenSetNP);
483 deleteProperty(CloseSetNP);
484 deleteProperty(ASIAIRControlSP);
485 deleteProperty(CustomBrightnessNP);
486 deleteProperty(AutoDetectSP);
487 }
488
491 return true;
492}
493
502
511
515bool WandererCoverV4EC::detectProtocol()
516{
517 try
518 {
519 LOG_DEBUG("Starting protocol detection...");
520
521 // Try to lock the mutex with a short timeout
522 if (!serialPortMutex.try_lock_for(std::chrono::milliseconds(100)))
523 {
524 LOG_DEBUG("Serial port is busy during protocol detection");
525 return false;
526 }
527
528 std::lock_guard<std::timed_mutex> lock(serialPortMutex, std::adopt_lock);
529
530 PortFD = serialConnection->getPortFD();
531 tcflush(PortFD, TCIOFLUSH);
532
533 // Read data from the device
534 char buffer[512] = {0};
535 int nbytes_read = 0, rc = -1;
536
537 // First, try to read any available data immediately
538 int available = 0;
539 if (ioctl(PortFD, FIONREAD, &available) == 0)
540 {
541 LOGF_DEBUG("Bytes available for reading: %d", available);
542 }
543
544 if ((rc = tty_read_section(PortFD, buffer, '\n', 2, &nbytes_read)) != TTY_OK)
545 {
546 if (rc == TTY_TIME_OUT)
547 {
548 LOG_DEBUG("Timeout reading from device during protocol detection");
549 LOG_DEBUG("Trying to read any available data without timeout...");
550
551 // Try to read without timeout to see if there's any data
552 if (tty_read(PortFD, buffer, 1, 0, &nbytes_read) == TTY_OK)
553 {
554 LOGF_DEBUG("Found %d bytes without timeout: '%s'", nbytes_read, buffer);
555 }
556 else
557 {
558 LOG_DEBUG("No data available without timeout either");
559 }
560
561 LOG_ERROR("Protocol detection failed: No data received from device");
562 return false;
563 }
564
565 char errorMessage[MAXRBUF];
566 tty_error_msg(rc, errorMessage, MAXRBUF);
567 LOGF_ERROR("Failed to read data from device during protocol detection. Error: %s", errorMessage);
568 return false;
569 }
570
571 LOGF_DEBUG("Raw data received from device: '%s' (length: %d)", buffer, nbytes_read);
572
573 // Parse firmware version from the data
574 std::string dataStr(buffer);
575 std::vector<std::string> tokens;
576 std::string token;
577 std::istringstream tokenStream(dataStr);
578
579 while (std::getline(tokenStream, token, 'A'))
580 {
581 if (!token.empty())
582 tokens.push_back(token);
583 }
584
585 LOGF_DEBUG("Parsed %zu tokens from device data", tokens.size());
586 for (size_t i = 0; i < tokens.size(); i++)
587 {
588 LOGF_DEBUG("Token[%zu]: '%s'", i, tokens[i].c_str());
589 }
590
591 // Check if we have enough tokens and the first one is correct
592 if (tokens.size() < 2)
593 {
594 LOGF_ERROR("Invalid data format: Not enough tokens. Expected at least 2, got %zu", tokens.size());
595 return false;
596 }
597
598 // Enhanced device detection with better error reporting
599 std::string deviceName = tokens[0];
600 LOGF_DEBUG("Device identification: '%s'", deviceName.c_str());
601
602 // Check for exact match first
603 if (deviceName == "WandererCoverV4")
604 {
605 LOG_INFO("WandererCover V4-EC device detected");
606 }
607 // Check for similar devices that might be compatible
608 else if (deviceName == "ZXWBProV3" || deviceName == "ZXWBPlusV3" ||
609 deviceName == "UltimateV2" || deviceName == "PlusV2"|| deviceName == "WandererEclipse"|| deviceName == "WandererDewTerminator"|| deviceName == "WandererCoverV4Pro")
610 {
611 LOGF_ERROR("WandererAstro products detected, but the model does not match: '%s'. This driver is designed for WandererCover V4-EC only, please choose the right driver or try another serial port!", deviceName.c_str());
612 return false;
613 }
614 else
615 {
616 LOGF_ERROR("Unsupported device detected: '%s'. Expected 'WandererCoverV4'", deviceName.c_str());
617 LOG_ERROR("This driver is specifically designed for WandererCover V4-EC devices only.");
618 return false;
619 }
620
621 // Parse firmware version
622 int firmwareVersion = std::atoi(tokens[1].c_str());
623 LOGF_INFO("Detected firmware version: %d", firmwareVersion);
624
625 // Validate firmware version
626 if (firmwareVersion <= 0)
627 {
628 LOGF_ERROR("Invalid firmware version: %d. Cannot determine protocol.", firmwareVersion);
629 return false;
630 }
631
632 // Choose protocol based on firmware version
633 if (firmwareVersion >= 20250404)
634 {
635 std::unique_ptr<WandererCoverModernProtocol> modernProtocol = std::make_unique<WandererCoverModernProtocol>();
636 setProtocol(std::move(modernProtocol));
637 LOG_INFO("Using modern protocol (firmware >= 20250404) Please note that in the newer firmware, to protect dark conditions, the flat light will remain off whenever the Cover is open.");
638 return true;
639 }
640 else if (firmwareVersion > 0)
641 {
642 std::unique_ptr<WandererCoverLegacyProtocol> legacyProtocol = std::make_unique<WandererCoverLegacyProtocol>();
643 setProtocol(std::move(legacyProtocol));
644 LOG_INFO("Using legacy protocol (firmware < 20250404) Firmware update recommended.");
645 return true;
646 }
647 else
648 {
649 LOGF_ERROR("Invalid firmware version: %d. Cannot determine protocol.", firmwareVersion);
650 return false;
651 }
652 }
653 catch(std::exception &e)
654 {
655 LOGF_ERROR("Exception occurred during protocol detection: %s", e.what());
656 return false;
657 }
658}
659
660void WandererCoverV4EC::setProtocol(std::unique_ptr<IWandererCoverProtocol> protocol)
661{
662 currentProtocol = std::move(protocol);
663
664}
665
669bool WandererCoverV4EC::getData()
670{
671 // Try to lock the mutex with a short timeout
672 if (!serialPortMutex.try_lock_for(std::chrono::milliseconds(100)))
673 {
674 LOG_DEBUG("Serial port is busy, skipping status update");
675 return true;
676 }
677
678 std::lock_guard<std::timed_mutex> lock(serialPortMutex, std::adopt_lock);
679
680 try
681 {
682 LOG_DEBUG("Reading data from device...");
683
684 PortFD = serialConnection->getPortFD();
685 tcflush(PortFD, TCIOFLUSH);
686
687 // Read all data from the device as a single line with 'A' separators
688 char buffer[512] = {0};
689 int nbytes_read = 0, rc = -1;
690
691 // Use a shorter timeout for reading to prevent blocking too long
692 if ((rc = tty_read_section(PortFD, buffer, '\n', 2, &nbytes_read)) != TTY_OK)
693 {
694 // If we get a timeout, it's not necessarily an error - the device might just be busy
695 if (rc == TTY_TIME_OUT)
696 {
697 LOG_DEBUG("Timeout reading from device, will try again later");
698 return true;
699 }
700
701 char errorMessage[MAXRBUF];
702 tty_error_msg(rc, errorMessage, MAXRBUF);
703 LOGF_ERROR("Failed to read data from device. Error: %s", errorMessage);
704 return false;
705 }
706
707 LOGF_DEBUG("Data received from device: '%s' (length: %d)", buffer, nbytes_read);
708
709 // Parse the received data using the current protocol
710 bool parseResult = parseDeviceData(buffer);
711 LOGF_DEBUG("Data parsing result: %s", parseResult ? "success" : "failed");
712 return parseResult;
713 }
714 catch(std::exception &e)
715 {
716 LOGF_ERROR("Exception occurred while reading data from device: %s", e.what());
717 return false;
718 }
719
720 return true;
721}
722
723bool WandererCoverV4EC::parseDeviceData(const char *data)
724{
725 if (!currentProtocol)
726 {
727 LOG_ERROR("No protocol handler available");
728 return false;
729 }
730
731 LOGF_DEBUG("Parsing data with protocol: %s", currentProtocol->getProtocolName().c_str());
732 return currentProtocol->parseDeviceData(data, this);
733}
734
738void WandererCoverV4EC::updateData(double closesetread, double opensetread, double positionread, double voltageread,double flatpanelbrightnessread,double dewheaterpowerread, double asiaircontrolenabledread)
739{
740 // Update basic data
741 DataNP[closeset_read].setValue(closesetread);
742 DataNP[openset_read].setValue(opensetread);
743 DataNP[position_read].setValue(positionread);
744 DataNP[voltage_read].setValue(voltageread);
745
746 // Update extended data only if supported by current protocol
747 if (currentProtocol && currentProtocol->supportsFeature("extended_status"))
748 {
749 DataNP[flat_panel_brightness_read].setValue(flatpanelbrightnessread);
750 DataNP[dew_heater_power_read].setValue(dewheaterpowerread);
751 DataNP[asiair_control_enabled_read].setValue(asiaircontrolenabledread);
752 }
753 else
754 {
755 // For legacy protocol, set default values for unsupported features
756 DataNP[flat_panel_brightness_read].setValue(-1);
757 DataNP[dew_heater_power_read].setValue(-1);
758 DataNP[asiair_control_enabled_read].setValue(-1);
759 }
760
761 DataNP.setState(IPS_OK);
762 DataNP.apply();
763
764 auto prevParked = ParkCapSP[CAP_PARK].getState() == ISS_ON;
765 auto prevState = ParkCapSP.getState();
766
767 ParkCapSP[CAP_PARK].setState((positionread - 10 <= closesetread) ? ISS_ON : ISS_OFF);
768 ParkCapSP[CAP_UNPARK].setState((positionread + 10 >= opensetread) ? ISS_ON : ISS_OFF);
770 || ParkCapSP[CAP_UNPARK].getState() == ISS_ON) ? IPS_OK : IPS_IDLE);
771
772 auto currentParked = ParkCapSP[CAP_PARK].getState() == ISS_ON;
773 auto currentState = ParkCapSP.getState();
774
775 // Only update on state changes
776 if ((prevParked != currentParked) || (prevState != currentState))
778}
779
783bool WandererCoverV4EC::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
784{
785 if (LI::processText(dev, name, texts, names, n))
786 return true;
787
788 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
789}
790
794bool WandererCoverV4EC::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
795{
796 if (LI::processSwitch(dev, name, states, names, n))
797 return true;
798
799 if (DI::processSwitch(dev, name, states, names, n))
800 return true;
801
802 if (dev && !strcmp(dev, getDeviceName()))
803 {
804 // ASIAIR Control
805 if (ASIAIRControlSP.isNameMatch(name))
806 {
807 if (!currentProtocol || !currentProtocol->supportsFeature("asiair_control"))
808 {
809 LOG_ERROR("ASIAIR control not supported by current protocol");
810 ASIAIRControlSP.setState(IPS_ALERT);
811 ASIAIRControlSP.apply();
812 return true;
813 }
814
815 bool rc = false;
816 for (int i = 0; i < n; i++)
817 {
818 if (strcmp(names[i], "ASIAIR_ENABLE") == 0 && states[i] == ISS_ON)
819 {
820 rc = sendCommand(currentProtocol->generateASIAIRControlCommand(true));
821 }
822 else if (strcmp(names[i], "ASIAIR_DISABLE") == 0 && states[i] == ISS_ON)
823 {
824 rc = sendCommand(currentProtocol->generateASIAIRControlCommand(false));
825 }
826 }
827
828 ASIAIRControlSP.setState(rc ? IPS_OK : IPS_ALERT);
829 if (ASIAIRControlSP.getState() == IPS_OK)
830 ASIAIRControlSP.update(states, names, n);
831 ASIAIRControlSP.apply();
832 return true;
833 }
834
835 // Auto Detection
836 if (AutoDetectSP.isNameMatch(name))
837 {
838 if (!currentProtocol || !currentProtocol->supportsFeature("auto_detect"))
839 {
840 LOG_ERROR("Auto detection not supported by current protocol");
841 AutoDetectSP.setState(IPS_ALERT);
842 AutoDetectSP.apply();
843 return true;
844 }
845
846 bool rc = false;
847 for (int i = 0; i < n; i++)
848 {
849 if (strcmp(names[i], "AUTO_DETECT_OPEN") == 0 && states[i] == ISS_ON)
850 {
851 rc = sendCommand(currentProtocol->generateAutoDetectOpenPositionCommand());
852 }
853 else if (strcmp(names[i], "AUTO_DETECT_CLOSE") == 0 && states[i] == ISS_ON)
854 {
855 rc = sendCommand(currentProtocol->generateAutoDetectClosePositionCommand());
856 }
857 }
858
859 AutoDetectSP.setState(rc ? IPS_OK : IPS_ALERT);
860 if (AutoDetectSP.getState() == IPS_OK)
861 AutoDetectSP.update(states, names, n);
862 AutoDetectSP.apply();
863 return true;
864 }
865 }
866
867 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
868}
869
873bool WandererCoverV4EC::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
874{
875 if (LI::processNumber(dev, name, values, names, n))
876 return true;
877
878 if (dev && !strcmp(dev, getDeviceName()))
879 {
880 // Heater
881 if (SetHeaterNP.isNameMatch(name))
882 {
883 if (!currentProtocol)
884 {
885 LOG_ERROR("No protocol handler available");
886 SetHeaterNP.setState(IPS_ALERT);
887 SetHeaterNP.apply();
888 return true;
889 }
890
891 bool rc1 = false;
892 for (int i = 0; i < n; i++)
893 {
894 rc1 = setDewPWM(2, static_cast<int>(values[i]));
895 }
896
897 SetHeaterNP.setState( (rc1) ? IPS_OK : IPS_ALERT);
898 if (SetHeaterNP.getState() == IPS_OK)
899 SetHeaterNP.update(values, names, n);
900 SetHeaterNP.apply();
901 return true;
902 }
903 // Close Set
904 if (CloseSetNP.isNameMatch(name))
905 {
906 if (!currentProtocol)
907 {
908 LOG_ERROR("No protocol handler available");
909 CloseSetNP.setState(IPS_ALERT);
910 CloseSetNP.apply();
911 return true;
912 }
913
914 bool rc1 = false;
915
916 for (int i = 0; i < n; i++)
917 {
918 if(values[i] < 10 || values[i] > 90)
919 {
920 CloseSetNP.setState(IPS_ALERT);
921 LOG_ERROR("Out of range! Allowed closed angle: 10-90 degrees.");
922 return false;
923 }
924 rc1 = sendCommand(currentProtocol->generateSetClosePositionCommand(values[i]));
925 }
926
927 CloseSetNP.setState( (rc1) ? IPS_OK : IPS_ALERT);
928 if (CloseSetNP.getState() == IPS_OK)
929 {
930 CloseSetNP.update(values, names, n);
931 saveConfig(CloseSetNP);
932 }
933 CloseSetNP.apply();
934 return true;
935 }
936 // Open Set
937 if (OpenSetNP.isNameMatch(name))
938 {
939 if (!currentProtocol)
940 {
941 LOG_ERROR("No protocol handler available");
942 OpenSetNP.setState(IPS_ALERT);
943 OpenSetNP.apply();
944 return true;
945 }
946
947 bool rc1 = false;
948
949 for (int i = 0; i < n; i++)
950 {
951 if(values[i] < 100 || values[i] > 300)
952 {
953 OpenSetNP.setState(IPS_ALERT);
954 LOG_ERROR("Out of range! Allowed open angle: 100-300 degrees.");
955 return false;
956 }
957 rc1 = sendCommand(currentProtocol->generateSetOpenPositionCommand(values[i]));
958 }
959
960 OpenSetNP.setState( (rc1) ? IPS_OK : IPS_ALERT);
961 if (OpenSetNP.getState() == IPS_OK)
962 {
963 OpenSetNP.update(values, names, n);
964 saveConfig(OpenSetNP);
965 }
966 OpenSetNP.apply();
967 return true;
968 }
969
970 // Custom Brightness
971 if (CustomBrightnessNP.isNameMatch(name))
972 {
973 if (!currentProtocol || !currentProtocol->supportsFeature("custom_brightness"))
974 {
975 LOG_ERROR("Custom brightness not supported by current protocol");
976 CustomBrightnessNP.setState(IPS_ALERT);
977 CustomBrightnessNP.apply();
978 return true;
979 }
980
981 bool rc = false;
982 for (int i = 0; i < n; i++)
983 {
984 if (strcmp(names[i], "CUSTOM_BRIGHTNESS_1") == 0)
985 {
986 rc = sendCommand(currentProtocol->generateCustomBrightnessCommand(static_cast<int>(values[i]), 1));
987 }
988 else if (strcmp(names[i], "CUSTOM_BRIGHTNESS_2") == 0)
989 {
990 rc = sendCommand(currentProtocol->generateCustomBrightnessCommand(static_cast<int>(values[i]), 2));
991 }
992 else if (strcmp(names[i], "CUSTOM_BRIGHTNESS_3") == 0)
993 {
994 rc = sendCommand(currentProtocol->generateCustomBrightnessCommand(static_cast<int>(values[i]), 3));
995 }
996 }
997
998 CustomBrightnessNP.setState(rc ? IPS_OK : IPS_ALERT);
999 if (CustomBrightnessNP.getState() == IPS_OK)
1000 CustomBrightnessNP.update(values, names, n);
1001 CustomBrightnessNP.apply();
1002 return true;
1003 }
1004 }
1005 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
1006}
1007
1011bool WandererCoverV4EC::toggleCover(bool open)
1012{
1013 if (!currentProtocol)
1014 {
1015 LOG_ERROR("No protocol handler available");
1016 return false;
1017 }
1018
1019 std::string command = open ? currentProtocol->generateOpenCommand() : currentProtocol->generateCloseCommand();
1020 return sendCommand(command);
1021}
1022
1027{
1028 // Set park status to busy
1030 ParkCapSP.apply();
1031
1032 if (toggleCover(false))
1033 {
1034 return IPS_BUSY;
1035 }
1036
1037 // If toggleCover failed, set back to alert
1039 ParkCapSP.apply();
1040 return IPS_ALERT;
1041}
1042
1047{
1048 // Set park status to busy
1050 ParkCapSP.apply();
1051
1052 if (toggleCover(true))
1053 {
1054 return IPS_BUSY;
1055 }
1056
1057 // If toggleCover failed, set back to alert
1059 ParkCapSP.apply();
1060 return IPS_ALERT;
1061}
1062
1067{
1068 if (!currentProtocol)
1069 {
1070 LOG_ERROR("No protocol handler available");
1071 return false;
1072 }
1073
1074 auto rc = true;
1075 if(value > 0)
1076 {
1077 // Only change if already enabled.
1078 if (LightSP[INDI_ENABLED].getState() == ISS_ON)
1079 rc = sendCommand(currentProtocol->generateSetBrightnessCommand(value));
1080 }
1081 else
1082 {
1083 EnableLightBox(false);
1087 LightSP.apply();
1088 }
1089
1090 return rc;
1091}
1092
1097{
1098 if (!currentProtocol)
1099 {
1100 LOG_ERROR("No protocol handler available");
1101 return false;
1102 }
1103
1104 auto rc = false;
1105 if(!enable)
1106 rc = sendCommand(currentProtocol->generateTurnOffLightCommand());
1107 else
1108 rc = sendCommand(currentProtocol->generateSetBrightnessCommand(static_cast<int>(LightIntensityNP[0].getValue())));
1109
1110 return rc;
1111}
1112
1116bool WandererCoverV4EC::sendCommand(std::string command)
1117{
1118 // Lock the mutex for the duration of this function
1119 std::lock_guard<std::timed_mutex> lock(serialPortMutex);
1120
1121 int nbytes_written = 0, rc = -1;
1122 std::string command_termination = "\n";
1123 LOGF_DEBUG("CMD: %s", command.c_str());
1124 if ((rc = tty_write_string(PortFD, (command + command_termination).c_str(), &nbytes_written)) != TTY_OK)
1125 {
1126 char errorMessage[MAXRBUF];
1127 tty_error_msg(rc, errorMessage, MAXRBUF);
1128 LOGF_ERROR("Serial write error: %s", errorMessage);
1129 return false;
1130 }
1131
1132 return true;
1133}
1134
1138bool WandererCoverV4EC::setDewPWM(int id, int value)
1139{
1140 if (!currentProtocol)
1141 {
1142 LOG_ERROR("No protocol handler available");
1143 return false;
1144 }
1145
1146 char cmd[64] = {0};
1147 snprintf(cmd, 64, "%d%03d", id, value);
1148 return sendCommand(cmd);
1149}
1150
1154bool WandererCoverV4EC::setClose(double value)
1155{
1156 if (!currentProtocol)
1157 {
1158 LOG_ERROR("No protocol handler available");
1159 return false;
1160 }
1161
1162 return sendCommand(currentProtocol->generateSetClosePositionCommand(value));
1163}
1164
1168bool WandererCoverV4EC::setOpen(double value)
1169{
1170 if (!currentProtocol)
1171 {
1172 LOG_ERROR("No protocol handler available");
1173 return false;
1174 }
1175
1176 return sendCommand(currentProtocol->generateSetOpenPositionCommand(value));
1177}
1178
1183{
1184 if (!isConnected())
1185 {
1187 return;
1188 }
1189
1190 getData();
1192}
1193
1198{
1200
1201 SetHeaterNP.save(fp);
1202 CloseSetNP.save(fp);
1203 OpenSetNP.save(fp);
1204 CustomBrightnessNP.save(fp);
1205
1206 return LI::saveConfigItems(fp);
1207}
hid_device * device
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
Provides interface to implement remotely controlled dust cover.
bool isConnected() const
const char * getDeviceName() const
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.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
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 deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
uint32_t getPollingPeriod() const
getPollingPeriod Return the polling period.
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.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
INDI::PropertySwitch ParkCapSP
bool updateProperties()
updateProperties Defines or Delete properties based on default device connection status
void initProperties(const char *group, uint32_t capabilities=0)
Initialize dust cap properties. It is recommended to call this function within initProperties() of yo...
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process dust cap switch properties.
void initProperties(const char *group, uint32_t capabilities)
Initialize light box properties. It is recommended to call this function within initProperties() of y...
void ISGetProperties(const char *deviceName)
isGetLightBoxProperties Get light box properties
INDI::PropertyNumber LightIntensityNP
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process light box switch properties.
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process light box text properties.
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process light box number properties.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
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 ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
Provides interface to implement controllable light box/switch device.
bool detectProtocol(const char *data) override
std::string generateSetBrightnessCommand(uint16_t value) const override
std::string generateCustomBrightnessCommand(int brightness, int customNumber) const override
std::string generateCloseCommand() const override
std::string generateSetClosePositionCommand(double value) const override
std::string generateAutoDetectClosePositionCommand() const override
bool supportsFeature(const std::string &feature) const override
std::string generateSetOpenPositionCommand(double value) const override
std::string generateOpenCommand() const override
std::string generateDewHeaterCommand(int value) const override
std::string generateTurnOffLightCommand() const override
bool parseDeviceData(const char *data, WandererCoverV4EC *device) override
std::string generateAutoDetectOpenPositionCommand() const override
std::string generateASIAIRControlCommand(bool enable) const override
std::string generateTurnOffLightCommand() const override
std::string generateAutoDetectOpenPositionCommand() const override
bool supportsFeature(const std::string &feature) const override
std::string generateASIAIRControlCommand(bool enable) const override
bool parseDeviceData(const char *data, WandererCoverV4EC *device) override
bool detectProtocol(const char *data) override
std::string generateCloseCommand() const override
std::string generateSetBrightnessCommand(uint16_t value) const override
std::string generateDewHeaterCommand(int value) const override
std::string generateAutoDetectClosePositionCommand() const override
std::string generateSetOpenPositionCommand(double value) const override
std::string generateCustomBrightnessCommand(int brightness, int customNumber) const override
std::string generateOpenCommand() const override
std::string generateSetClosePositionCommand(double value) const override
virtual IPState ParkCap() override
Park dust cap (close cover). Must be implemented by child.
const char * getDefaultName() override
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual IPState UnParkCap() override
unPark dust cap (open cover). Must be implemented by child.
virtual bool EnableLightBox(bool enable) override
EnableLightBox Turn on/off on a light box. Must be implemented in the child class.
virtual bool SetLightBoxBrightness(uint16_t value) override
setBrightness Set light level. Must be implemented in the child class, if supported.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ 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
@ ISR_1OFMANY
Definition indiapi.h:171
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_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
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_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#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
uint8_t cmd[4]
Definition pwc-ioctl.h:2