Instrument Neutral Distributed Interface INDI 2.2.5
snapcap.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2017-2024 Jarno Paananen. All right reserved.
3
4 Based on Flip Flat driver by:
5
6 Copyright(c) 2015 Jasem Mutlaq. All rights reserved.
7
8 This program is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2 of the License, or (at your option)
11 any later version.
12
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22
23 The full GNU General Public License is included in this distribution in the
24 file called LICENSE.
25*******************************************************************************/
26
27#include "snapcap.h"
28
29#include "indicom.h"
32
33#include <cerrno>
34#include <cstring>
35#include <memory>
36#include <termios.h>
37#include <sys/ioctl.h>
38
39// We declare an auto pointer to SnapCap.
40std::unique_ptr<SnapCap> snapcap(new SnapCap());
41
42#define SNAP_CMD 7
43#define SNAP_RES 8 // Includes terminating null
44#define SNAP_TIMEOUT 3
45
50
52{
53
54 delete serialConnection;
55 delete tcpConnection;
56}
57
59{
61
62 // Status
63 StatusTP[0].fill("COVER", "Cover", nullptr);
64 StatusTP[1].fill("LIGHT", "Light", nullptr);
65 StatusTP[2].fill("MOTOR", "Motor", nullptr);
66 StatusTP.fill(getDeviceName(), "STATUS", "Status", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
67
68 // Firmware version
69 FirmwareTP[0].fill("VERSION", "Version", nullptr);
70 FirmwareTP.fill(getDeviceName(), "FIRMWARE", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
71
72 ForceSP[0].fill("OFF", "Off", ISS_ON);
73 ForceSP[1].fill("ON", "On", ISS_OFF);
74 ForceSP.fill(getDeviceName(), "FORCE", "Force movement", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
75
78
79 LightIntensityNP[0].setMin(0);
80 LightIntensityNP[0].setMax(255);
81 LightIntensityNP[0].setStep(10);
82
83 hasLight = true;
84
86
88
89 if (dustcapConnection & CONNECTION_SERIAL)
90 {
91 serialConnection = new Connection::Serial(this);
92 serialConnection->registerHandshake([&]()
93 {
94 return callHandshake();
95 });
96 registerConnection(serialConnection);
97 }
98
99 if (dustcapConnection & CONNECTION_TCP)
100 {
101 tcpConnection = new Connection::TCP(this);
102 tcpConnection->registerHandshake([&]()
103 {
104 return callHandshake();
105 });
106 registerConnection(tcpConnection);
107 }
108
109
110 return true;
111}
112
113void SnapCap::ISGetProperties(const char *dev)
114{
116
117 // Get Light box properties
119}
120
122{
124
126
127 if (isConnected())
128 {
129 if (hasLight)
130 {
132 }
133 defineProperty(StatusTP);
134 defineProperty(FirmwareTP);
135 defineProperty(ForceSP);
136
137 getStartupData();
138 }
139 else
140 {
141 if (hasLight)
142 {
144 }
145 deleteProperty(StatusTP);
146 deleteProperty(FirmwareTP);
147 deleteProperty(ForceSP);
148 }
149
150 return true;
151}
152
154{
155 return "SnapCap";
156}
157
158bool SnapCap::Handshake()
159{
160 if (isSimulation())
161 {
162 LOGF_INFO("Connected successfully to simulated %s. Retrieving startup data...", getDeviceName());
163
165 return true;
166 }
167
168 if (!ping())
169 {
170 LOG_ERROR("Device ping failed.");
171 return false;
172 }
173
174 return true;
175}
176
177bool SnapCap::callHandshake()
178{
179 if (dustcapConnection > 0)
180 {
181 if (getActiveConnection() == serialConnection)
182 PortFD = serialConnection->getPortFD();
183 else if (getActiveConnection() == tcpConnection)
184 PortFD = tcpConnection->getPortFD();
185 }
186
187 return Handshake();
188}
189
190bool SnapCap::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
191{
192 if (!dev || strcmp(dev, getDeviceName()))
193 return false;
194
195 if (LI::processNumber(dev, name, values, names, n))
196 return true;
197
198 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
199}
200
201bool SnapCap::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
202{
203 if (!dev || strcmp(dev, getDeviceName()))
204 return false;
205
206 if (LI::processText(dev, name, texts, names, n))
207 return true;
208
209 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
210}
211
212bool SnapCap::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
213{
214 if (!dev || strcmp(dev, getDeviceName()))
215 return false;
216
217 if (ForceSP.isNameMatch(name))
218 {
219 ForceSP.update(states, names, n);
220 ForceSP.apply();
221 return true;
222 }
223
224 if (DI::processSwitch(dev, name, states, names, n))
225 return true;
226
227 if (LI::processSwitch(dev, name, states, names, n))
228 return true;
229
230 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
231}
232
234{
235 LI::snoop(root);
236
238}
239
241{
243
244 return LI::saveConfigItems(fp);
245}
246
247bool SnapCap::ping()
248{
249 bool found = getFirmwareVersion();
250 // Sometimes the controller does a corrupt reply at first connect
251 // so retry once just in case
252 if (!found)
253 found = getFirmwareVersion();
254 return found;
255}
256
257bool SnapCap::sendCommand(const char *command, char *response)
258{
259 int nbytes_written = 0, nbytes_read = 0, rc = -1;
260 char errstr[MAXRBUF];
261
262 tcflush(PortFD, TCIOFLUSH);
263
264 LOGF_DEBUG("CMD (%s)", command);
265
266 char buffer[SNAP_CMD + 1]; // space for terminating null
267 snprintf(buffer, SNAP_CMD + 1, "%s\r\n", command);
268
269 if ((rc = tty_write(PortFD, buffer, SNAP_CMD, &nbytes_written)) != TTY_OK)
270 {
271 tty_error_msg(rc, errstr, MAXRBUF);
272 LOGF_ERROR("%s error: %s.", command, errstr);
273 return false;
274 }
275
276 if ((rc = tty_nread_section(PortFD, response, SNAP_RES, '\n', SNAP_TIMEOUT, &nbytes_read)) != TTY_OK)
277 {
278 tty_error_msg(rc, errstr, MAXRBUF);
279 LOGF_ERROR("%s: %s.", command, errstr);
280 return false;
281 }
282
283 response[nbytes_read - 2] = 0; // strip \r\n
284
285 LOGF_DEBUG("RES (%s)", response);
286 return true;
287}
288
289bool SnapCap::getStartupData()
290{
291 bool rc1 = getFirmwareVersion();
292 bool rc2 = getStatus();
293 bool rc3 = getBrightness();
294
295 return (rc1 && rc2 && rc3);
296}
297
299{
300 if (isSimulation())
301 {
302 simulationWorkCounter = 3;
303 return IPS_BUSY;
304 }
305
306 char command[SNAP_CMD];
307 char response[SNAP_RES];
308
309 if (ForceSP[1].getState() == ISS_ON)
310 strncpy(command, ">c000", SNAP_CMD); // Force close command
311 else
312 strncpy(command, ">C000", SNAP_CMD);
313
314 if (!sendCommand(command, response))
315 return IPS_ALERT;
316
317 if (strcmp(response, "*C000") == 0 || strcmp(response, "*c000") == 0)
318 {
319 // Set cover status to random value outside of range to force it to refresh
320 prevCoverStatus = 10;
321 targetCoverStatus = 2;
322 return IPS_BUSY;
323 }
324 else
325 return IPS_ALERT;
326}
327
329{
330 if (isSimulation())
331 {
332 simulationWorkCounter = 3;
333 return IPS_BUSY;
334 }
335
336 char command[SNAP_CMD];
337 char response[SNAP_RES];
338
339 if (ForceSP[1].getState() == ISS_ON)
340 strncpy(command, ">o000", SNAP_CMD); // Force open command
341 else
342 strncpy(command, ">O000", SNAP_CMD);
343
344 if (!sendCommand(command, response))
345 return IPS_ALERT;
346
347 if (strcmp(response, "*O000") == 0 || strcmp(response, "*o000") == 0)
348 {
349 // Set cover status to random value outside of range to force it to refresh
350 prevCoverStatus = 10;
351 targetCoverStatus = 1;
352 return IPS_BUSY;
353 }
354 else
355 return IPS_ALERT;
356}
357
359{
360 if (isSimulation())
361 {
362 simulationWorkCounter = 0;
363 return IPS_OK;
364 }
365
366 char response[SNAP_RES];
367
368 if (!sendCommand(">A000", response))
369 return IPS_ALERT;
370
371 if (strcmp(response, "*A000") == 0)
372 {
373 // Set cover status to random value outside of range to force it to refresh
374 prevCoverStatus = 10;
375 return IPS_OK;
376 }
377 else
378 return IPS_ALERT;
379}
380
381bool SnapCap::EnableLightBox(bool enable)
382{
383 char command[SNAP_CMD];
384 char response[SNAP_RES];
385
386 if (isSimulation())
387 return true;
388
389 if (enable)
390 strncpy(command, ">L000", SNAP_CMD);
391 else
392 strncpy(command, ">D000", SNAP_CMD);
393
394 if (!sendCommand(command, response))
395 return false;
396
397 char expectedResponse[SNAP_RES];
398 if (enable)
399 snprintf(expectedResponse, SNAP_RES, "*L000");
400 else
401 snprintf(expectedResponse, SNAP_RES, "*D000");
402
403 if (strcmp(response, expectedResponse) == 0)
404 return true;
405
406 return false;
407}
408
409bool SnapCap::getStatus()
410{
411 char response[SNAP_RES];
412
413 if (isSimulation())
414 {
415 if (ParkCapSP.getState() == IPS_BUSY && --simulationWorkCounter <= 0)
416 {
419 simulationWorkCounter = 0;
420 }
421
422 if (ParkCapSP.getState() == IPS_BUSY)
423 {
424 response[2] = '1';
425 response[4] = '0';
426 }
427 else
428 {
429 response[2] = '0';
430 // Parked/Closed
431 if (ParkCapSP[CAP_PARK].getState() == ISS_ON)
432 response[4] = '2';
433 else
434 response[4] = '1';
435 }
436
437 response[3] = (LightSP[FLAT_LIGHT_ON].getState() == ISS_ON) ? '1' : '0';
438 }
439 else
440 {
441 if (!sendCommand(">S000", response))
442 return false;
443 }
444
445 char motorStatus = response[2] - '0';
446 char lightStatus = response[3] - '0';
447 char coverStatus = response[4] - '0';
448
449 // Force cover status as it doesn't reflect moving state otherwise...
450 if (motorStatus)
451 {
452 coverStatus = 0;
453 }
454 bool statusUpdated = false;
455
456 if (coverStatus != prevCoverStatus)
457 {
458 prevCoverStatus = coverStatus;
459
460 statusUpdated = true;
461
462 switch (coverStatus)
463 {
464 case 0:
465 StatusTP[0].setText("Opening/closing");
466 break;
467
468 case 1:
469 if ((targetCoverStatus == 1 && ParkCapSP.getState() == IPS_BUSY) || ParkCapSP.getState() == IPS_IDLE)
470 {
471 StatusTP[0].setText("Open");
475 LOG_INFO("Cover open.");
477 }
478 break;
479
480 case 2:
481 if ((targetCoverStatus == 2 && ParkCapSP.getState() == IPS_BUSY) || ParkCapSP.getState() == IPS_IDLE)
482 {
483 StatusTP[0].setText("Closed");
487 LOG_INFO("Cover closed.");
489 }
490 break;
491
492 case 3:
493 StatusTP[0].setText("Timed out");
494 break;
495
496 case 4:
497 StatusTP[0].setText("Open circuit");
498 break;
499
500 case 5:
501 StatusTP[0].setText("Overcurrent");
502 break;
503
504 case 6:
505 StatusTP[0].setText("User abort");
506 break;
507 }
508 }
509
510 if (lightStatus != prevLightStatus)
511 {
512 prevLightStatus = lightStatus;
513
514 statusUpdated = true;
515
516 switch (lightStatus)
517 {
518 case 0:
519 StatusTP[1].setText("Off");
520 if (LightSP[0].getState() == ISS_ON)
521 {
524 LightSP.apply();
525 }
526 break;
527
528 case 1:
529 StatusTP[1].setText("On");
530 if (LightSP[1].getState() == ISS_ON)
531 {
534 LightSP.apply();
535 }
536 break;
537 }
538 }
539
540 if (motorStatus != prevMotorStatus)
541 {
542 prevMotorStatus = motorStatus;
543
544 statusUpdated = true;
545
546 switch (motorStatus)
547 {
548 case 0:
549 StatusTP[2].setText("Stopped");
550 break;
551
552 case 1:
553 StatusTP[2].setText("Running");
554 break;
555 }
556 }
557
558 if (statusUpdated)
559 StatusTP.apply();
560
561 return true;
562}
563
564bool SnapCap::getFirmwareVersion()
565{
566 if (isSimulation())
567 {
568 FirmwareTP[0].setText("Ssimulation");
569 FirmwareTP.apply();
570 return true;
571 }
572
573 char response[SNAP_RES];
574
575 if (!sendCommand(">V000", response))
576 return false;
577
578 char versionString[4] = { 0 };
579 snprintf(versionString, 4, "%s", response + 2);
580 FirmwareTP[0].setText(versionString);
581 FirmwareTP.apply();
582
583 return true;
584}
585
587{
588 if (!isConnected())
589 return;
590
591 getStatus();
592
594}
595
596bool SnapCap::getBrightness()
597{
598 if (isSimulation())
599 {
600 return true;
601 }
602
603 char response[SNAP_RES];
604
605 if (!sendCommand(">J000", response))
606 return false;
607
608 int brightnessValue = 0;
609 int rc = sscanf(response, "*J%d", &brightnessValue);
610
611 if (rc <= 0)
612 {
613 LOGF_ERROR("Unable to parse brightness value (%s)", response);
614 return false;
615 }
616
617 if (brightnessValue != prevBrightness)
618 {
619 prevBrightness = brightnessValue;
620 LightIntensityNP[0].setValue(brightnessValue);
622 }
623
624 return true;
625}
626
628{
629 if (isSimulation())
630 {
631 LightIntensityNP[0].setValue(value);
633 return true;
634 }
635
636 char command[SNAP_CMD];
637 char response[SNAP_RES];
638
639 snprintf(command, SNAP_CMD, ">B%03d", value);
640
641 if (!sendCommand(command, response))
642 return false;
643
644 int brightnessValue = 0;
645 int rc = sscanf(response, "*B%d", &brightnessValue);
646
647 if (rc <= 0)
648 {
649 LOGF_ERROR("Unable to parse brightness value (%s)", response);
650 return false;
651 }
652
653 if (brightnessValue != prevBrightness)
654 {
655 prevBrightness = brightnessValue;
656 LightIntensityNP[0].setValue(brightnessValue);
658 }
659
660 return true;
661}
662
664{
665 return dustcapConnection;
666}
667
668void SnapCap::setDustcapConnection(const uint8_t &value)
669{
671
672 if (value == 0 || (mask & value) == 0)
673 {
674 LOGF_ERROR( "Invalid connection mode %d", value);
675 return;
676 }
677
678 dustcapConnection = value;
679}
680
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 ...
The TCP class manages connection with devices over the network via TCP/IP. Upon successful connection...
int getPortFD() const
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...
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.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
bool isSimulation() const
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.
Connection::Interface * getActiveConnection()
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
bool isNameMatch(const char *otherName) const
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.
virtual ~SnapCap()
Definition snapcap.cpp:51
virtual IPState UnParkCap() override
unPark dust cap (open cover). Must be implemented by child.
Definition snapcap.cpp:328
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition snapcap.cpp:58
virtual IPState AbortCap() override
Abort motion. Must be implemented by child.
Definition snapcap.cpp:358
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
Definition snapcap.cpp:233
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...
Definition snapcap.cpp:113
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition snapcap.cpp:190
void setDustcapConnection(const uint8_t &value)
setDustcapConnection Set Dustcap connection mode. Child class should call this in the constructor bef...
Definition snapcap.cpp:668
virtual bool SetLightBoxBrightness(uint16_t value) override
setBrightness Set light level. Must be implemented in the child class, if supported.
Definition snapcap.cpp:627
virtual IPState ParkCap() override
Park dust cap (close cover). Must be implemented by child.
Definition snapcap.cpp:298
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition snapcap.cpp:586
@ CONNECTION_TCP
Definition snapcap.h:63
@ CONNECTION_SERIAL
Definition snapcap.h:62
@ CONNECTION_NONE
Definition snapcap.h:61
virtual bool EnableLightBox(bool enable) override
EnableLightBox Turn on/off on a light box. Must be implemented in the child class.
Definition snapcap.cpp:381
SnapCap()
Definition snapcap.cpp:46
uint8_t getDustcapConnection() const
Definition snapcap.cpp:663
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition snapcap.cpp:212
const char * getDefaultName() override
Definition snapcap.cpp:153
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition snapcap.cpp:121
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition snapcap.cpp:201
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
Definition snapcap.cpp:240
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_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#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< SnapCap > snapcap(new SnapCap())
#define SNAP_TIMEOUT
Definition snapcap.cpp:44
#define SNAP_CMD
Definition snapcap.cpp:42
#define SNAP_RES
Definition snapcap.cpp:43