Instrument Neutral Distributed Interface INDI 2.2.5
wanderer_eclipse.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2024 Frank Wang/Jérémie Klein. All rights reserved.
3
4 Wanderer Eclipse
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
25#include "wanderer_eclipse.h"
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
37static std::unique_ptr<WandererEclipse> wanderereclipse(new WandererEclipse());
38
43
48{
49 return "Wanderer Eclipse";
50}
51
56{
58
60
63
64 //Data read
65
66 DataNP[torque_read].fill( "Motor_Torque", "Motor_Torque", "%4.2f", 0, 999, 100, 0);
67 DataNP[voltage_read].fill( "Voltage", "Voltage (V)", "%4.2f", 0, 999, 100, 0);
68 DataNP.fill(getDeviceName(), "STATUS", "Real Time Status", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
69
70 // Firmware information
71 FirmwareTP[FIRMWARE_VERSION].fill("FIRMWARE_VERSION", "Firmware Version", "Unknown");
72 FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
73
74
75 // Torque property
76 //TorqueSP[TORQUE_LOW].fill("TORQUE_LOW", "Low", ISS_OFF);
77 //TorqueSP[TORQUE_MEDIUM].fill("TORQUE_MEDIUM", "Medium", ISS_ON);
78 //TorqueSP[TORQUE_HIGH].fill("TORQUE_HIGH", "High", ISS_OFF);
79 //TorqueSP.fill(getDeviceName(), "TORQUE", "Motor Torque", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
80
82
83 serialConnection = new Connection::Serial(this);
85 serialConnection->registerHandshake([&]()
86 {
87 return getData();
88 });
89 registerConnection(serialConnection);
90
91
92
93 return true;
94}
95
100{
102
103 if (isConnected())
104 {
105 // Update firmware information
106 char firmwareStr[16];
107 snprintf(firmwareStr, sizeof(firmwareStr), "%d", firmware);
108 FirmwareTP[FIRMWARE_VERSION].setText(firmwareStr);
109
110 if(firmware >= 20240101)
111 {
112 LOGF_INFO("Firmware version: %d", firmware);
113 }
114 else
115 {
116 LOGF_INFO("Firmware version: %d", firmware);
117 LOG_INFO("New firmware available!");
118 }
119
120
121
122 defineProperty(DataNP);
123 defineProperty(FirmwareTP);
124defineProperty(TorqueSP);
125 }
126 else
127 {
128 deleteProperty(DataNP);
129 deleteProperty(FirmwareTP);
130 deleteProperty(TorqueSP);
131 }
132
134 return true;
135}
136
137
138bool WandererEclipse::getData()
139{
140 if (!serialPortMutex.try_lock_for(std::chrono::milliseconds(100)))
141 {
142 LOG_DEBUG("Serial port is busy, skipping status update");
143 return true;
144 }
145
146 std::lock_guard<std::timed_mutex> lock(serialPortMutex, std::adopt_lock);
147
148 try
149 {
150 PortFD = serialConnection->getPortFD();
151 tcflush(PortFD, TCIOFLUSH);
152
153
154 const char *command = "1500001\n";
155 int bytes_written = write(PortFD, command, strlen(command));
156 if (bytes_written < 0)
157 {
158 LOG_ERROR("Failed to write command to device");
159 return false;
160 }
161
162
163 char buffer[512] = {0};
164 int nbytes_read = 0, rc = -1;
165
166 if ((rc = tty_read_section(PortFD, buffer, '\n', 2, &nbytes_read)) != TTY_OK)
167 {
168 if (rc == TTY_TIME_OUT)
169 {
170 LOG_DEBUG("Timeout reading from device, will try again later");
171 return true;
172 }
173
174 char errorMessage[MAXRBUF];
175 tty_error_msg(rc, errorMessage, MAXRBUF);
176 LOGF_ERROR("Failed to read data from device. Error: %s", errorMessage);
177 return false;
178 }
179
180 return parseDeviceData(buffer);
181 }
182 catch (std::exception &e)
183 {
184 LOG_ERROR("Exception occurred while reading data from device");
185 return false;
186 }
187
188 return true;
189}
190
191
192bool WandererEclipse::parseDeviceData(const char *data)
193{
194 try
195 {
196
197 std::vector<std::string> tokens;
198 std::string token;
199 std::istringstream tokenStream(data);
200 LOGF_DEBUG("Data: %s", data);
201 // Split the data by 'A' separator
202 while (std::getline(tokenStream, token, 'A'))
203 {
204 if (!token.empty())
205 tokens.push_back(token);
206 }
207
208 // Check if we have enough tokens
209 if (tokens.size() < 3)
210 {
211 return false;
212 }
213
214 // Device Model
215 if (tokens[0] != "WandererEclipse")
216 {
217 if (tokens[0] == "ZXWBProV3" || tokens[0] == "ZXWBPlusV3" ||
218 tokens[0] == "UltimateV2" || tokens[0] == "PlusV2"|| tokens[0] == "WandererCoverV4"|| tokens[0] == "WandererCoverV4Pro")
219 {
220 LOG_WARN("The device is not Wanderer Eclipse!");
221 }
222 else
223 {
224 LOG_ERROR("Unknown device model");
225 }
226 return false;
227 }
228
229 // Firmware version
230 firmware = std::atoi(tokens[1].c_str());
231
232 // Update firmware information in the UI
233 char firmwareStr[16];
234 snprintf(firmwareStr, sizeof(firmwareStr), "%d", firmware);
235 FirmwareTP[FIRMWARE_VERSION].setText(firmwareStr);
236 FirmwareTP.setState(IPS_OK);
237 FirmwareTP.apply();
238
239 // Touque
240 torqueLevel = std::strtod(tokens[2].c_str(), NULL);
241
242
243 // Voltage
244 if (tokens.size() > 3)
245 {
246 voltageread = std::strtod(tokens[3].c_str(), NULL);
247
248 if (voltageread <= 7)
249 {
250 LOG_ERROR("No power input!");
251 }
252 }
253
254 // Update the UI with the parsed data
255 updateData(torqueLevel, voltageread);
256
257
258 return true;
259 }
260 catch(std::exception &e)
261 {
262 LOG_ERROR("Failed to parse device data");
263 return false;
264 }
265}
266
267void WandererEclipse::updateData(double torqueLevel, double voltageread)
268{
269 if (torqueLevel <= 0)
270 return;
271
272 DataNP[torque_read].setValue(torqueLevel);
273 DataNP[voltage_read].setValue(voltageread);
274 DataNP.setState(IPS_OK);
275 DataNP.apply();
276
277 int level = 0;
278 if (torqueLevel <= 110)
279 level = 0; // LOW
280 else if (torqueLevel <= 160)
281 level = 1; // MEDIUM
282 else
283 level = 2; // HIGH
284
285 for (int i = 0; i < 3; i++)
286 TorqueSP[i].setState(i == level ? ISS_ON : ISS_OFF);
287
288 TorqueSP.setState(IPS_OK);
289 TorqueSP.apply();
290
291 auto prevParked = ParkCapSP[CAP_PARK].getState() == ISS_ON;
292 auto prevState = ParkCapSP.getState();
294
295 auto currentParked = ParkCapSP[CAP_PARK].getState() == ISS_ON;
296 auto currentState = ParkCapSP.getState();
297
298 // Only update on state changes
299 if ((prevParked != currentParked) || (prevState != currentState))
301}
302
303
304
305
306
310bool WandererEclipse::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
311{
312 if (DI::processSwitch(dev, name, states, names, n))
313 return true;
314
315 if (dev && !strcmp(dev, getDeviceName()))
316{
317 // Torque
318 if (TorqueSP.isNameMatch(name))
319 {
320 int newTorque = -1;
321 const char *cmd = nullptr;
322
323 for (int i = 0; i < n; i++)
324 {
325 if (states[i] == ISS_ON)
326 {
327 if (strcmp(names[i], "TORQUE_LOW") == 0)
328 {
329 newTorque = 0;
330 cmd = "3110";
331 }
332 else if (strcmp(names[i], "TORQUE_MEDIUM") == 0)
333 {
334 newTorque = 1;
335 cmd = "3160";
336 }
337 else if (strcmp(names[i], "TORQUE_HIGH") == 0)
338 {
339 newTorque = 2;
340 cmd = "3190";
341 }
342 }
343 }
344
345 if (newTorque != -1 && newTorque != torqueLevel)
346 {
347 if (cmd && sendCommand(cmd))
348 {
349 torqueLevel = newTorque;
350 for (int i = 0; i < 3; i++)
351 TorqueSP[i].setState(i == newTorque ? ISS_ON : ISS_OFF);
352 TorqueSP.setState(IPS_OK);
353 }
354 else
355 {
356 TorqueSP.setState(IPS_ALERT);
357 }
358
359 TorqueSP.apply();
360 return true;
361 }
362 }
363}
364
365 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
366}
367
368
369
373bool WandererEclipse::toggleCover(bool open)
374{
375 char cmd[128] = {0};
376 snprintf(cmd, 128, "100%d", open ? 1 : 0);
377 if (sendCommand(cmd))
378 {
379 return true;
380 }
381
382 return false;
383}
384
385
390{
391 // Set park status to busy
394
395 if (toggleCover(false))
396 {
397 return IPS_BUSY;
398 }
399
400 // If toggleCover failed, set back to alert
403 return IPS_ALERT;
404}
405
410{
411 // Set park status to busy
414
415 if (toggleCover(true))
416 {
417 return IPS_BUSY;
418 }
419
420 // If toggleCover failed, set back to alert
423 return IPS_ALERT;
424}
425
426
430bool WandererEclipse::sendCommand(std::string command)
431{
432 // Lock the mutex for the duration of this function
433 std::lock_guard<std::timed_mutex> lock(serialPortMutex);
434
435 int nbytes_written = 0, rc = -1;
436 std::string command_termination = "\n";
437 LOGF_DEBUG("CMD: %s", command.c_str());
438 if ((rc = tty_write_string(PortFD, (command + command_termination).c_str(), &nbytes_written)) != TTY_OK)
439 {
440 char errorMessage[MAXRBUF];
441 tty_error_msg(rc, errorMessage, MAXRBUF);
442 LOGF_ERROR("Serial write error: %s", errorMessage);
443 return false;
444 }
445
446 return true;
447}
448
449
450
455{
456 if (!isConnected())
457 {
459 return;
460 }
461
462 getData();
464}
465
466
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 ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
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.
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 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.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
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 setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual IPState UnParkCap() override
unPark dust cap (open cover). Must be implemented by child.
const char * getDefaultName() override
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 ParkCap() override
Park dust cap (close cover). Must be implemented by child.
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_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_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define LOG_WARN(txt)
Definition indilogger.h:73
#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