Instrument Neutral Distributed Interface INDI 2.2.5
flip_flat.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2015 Jasem Mutlaq. All rights reserved.
3
4 Simple GPS Simulator
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 "flip_flat.h"
26
27#include "indicom.h"
29
30#include <cerrno>
31#include <cstring>
32#include <memory>
33#include <termios.h>
34#include <unistd.h>
35#include <inttypes.h>
36#include <sys/ioctl.h>
37
38// We declare an auto pointer to FlipFlat.
39static std::unique_ptr<FlipFlat> flipflat(new FlipFlat());
40
41#define FLAT_CMD 6
42#define FLAT_RES 8
43#define FLAT_TIMEOUT 3
44
49
51{
53
54 // Status
55 IUFillText(&StatusT[0], "Cover", "Cover", nullptr);
56 IUFillText(&StatusT[1], "Light", "Light", nullptr);
57 IUFillText(&StatusT[2], "Motor", "Motor", nullptr);
58 IUFillTextVector(&StatusTP, StatusT, 3, getDeviceName(), "Status", "Status", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
59
60 // Firmware version
61 IUFillText(&FirmwareT[0], "Version", "Version", nullptr);
62 IUFillTextVector(&FirmwareTP, FirmwareT, 1, getDeviceName(), "Firmware", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
63
66
67 LightIntensityNP[0].setMin(0);
68 LightIntensityNP[0].setMax(255);
69 LightIntensityNP[0].setStep(10);
70
71 // Set DUSTCAP_INTEFACE later on connect after we verify whether it's flip-flat (dust cover + light) or just flip-man (light only)
73
75
76 serialConnection = new Connection::Serial(this);
77 serialConnection->registerHandshake([&]()
78 {
79 return Handshake();
80 });
81 registerConnection(serialConnection);
82
83 return true;
84}
85
86void FlipFlat::ISGetProperties(const char *dev)
87{
89
90 // Get Light box properties
92}
93
95{
97
98 if (isConnected())
99 {
100 if (m_Type == FLIP_FLAT || m_Type == ALNITAK_DUST_COVER)
102 defineProperty(&StatusTP);
103 defineProperty(&FirmwareTP);
104
106
107 getStartupData();
108 }
109 else
110 {
111 if (m_Type == FLIP_FLAT || m_Type == ALNITAK_DUST_COVER)
113
115 deleteProperty(StatusTP.name);
116 deleteProperty(FirmwareTP.name);
117
118 }
119
120 return true;
121}
122
124{
125 return "Flip Flat";
126}
127
128bool FlipFlat::Handshake()
129{
130 if (isSimulation())
131 {
132 LOGF_INFO("Connected successfully to simulated %s. Retrieving startup data...", getDeviceName());
133
135
138 m_Type = FLIP_FLAT;
139
140 return true;
141 }
142
143 PortFD = serialConnection->getPortFD();
144
145 /* Drop RTS */
146 int i = 0;
147 i |= TIOCM_RTS;
148 if (ioctl(PortFD, TIOCMBIC, &i) != 0)
149 {
150 /* Try ping anyway, to allow flip-flat implementations using virtual serial ports to proceed */
151 if(ping())
152 {
153 LOG_DEBUG("Successfully connected to flip-flat without hardware IOCTL");
154 return true;
155 }
156 LOGF_ERROR("IOCTL error %s.", strerror(errno));
157 return false;
158 }
159
160 i |= TIOCM_RTS;
161 if (ioctl(PortFD, TIOCMGET, &i) != 0)
162 {
163 LOGF_ERROR("IOCTL error %s.", strerror(errno));
164 return false;
165 }
166
167 if (!ping())
168 {
169 LOG_ERROR("Device ping failed.");
170 return false;
171 }
172
173 return true;
174}
175
176bool FlipFlat::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
177{
178 if (LI::processNumber(dev, name, values, names, n))
179 return true;
180
181 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
182}
183
184bool FlipFlat::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
185{
186 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
187 {
188 if (LI::processText(dev, name, texts, names, n))
189 return true;
190 }
191
192 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
193}
194
195bool FlipFlat::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
196{
197 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
198 {
199 if (DI::processSwitch(dev, name, states, names, n))
200 return true;
201
202 if (LI::processSwitch(dev, name, states, names, n))
203 return true;
204 }
205
206 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
207}
208
210{
211 LI::snoop(root);
212
214}
215
217{
219
220 return LI::saveConfigItems(fp);
221}
222
223bool FlipFlat::ping()
224{
225 char response[FLAT_RES] = {0};
226
227 if (!sendCommand(">P000", response))
228 return false;
229
230 char productString[3] = { 0 };
231 snprintf(productString, 3, "%s", response + 2);
232
233 int rc = sscanf(productString, "%" SCNu16, &productID);
234 if (rc <= 0)
235 {
236 LOGF_ERROR("Unable to parse input (%s)", response);
237 return false;
238 }
239
240 // Flip Flat
241 if (productID == 99)
242 {
245 m_Type = FLIP_FLAT;
246 }
247 // AlNitak Dust Cover
248 else if (productID == 98)
249 {
252 }
253 // Flip Man
254 else
255 m_Type = FLIP_MAN;
256
257 return true;
258}
259
260bool FlipFlat::getStartupData()
261{
262 bool rc1 = getFirmwareVersion();
263 bool rc2 = getStatus();
264 bool rc3 = getBrightness();
265
266 return (rc1 && rc2 && rc3);
267}
268
270{
271 if (isSimulation())
272 {
273 simulationWorkCounter = 3;
274 return IPS_BUSY;
275 }
276
277 char response[FLAT_RES];
278 if (!sendCommand(">C000", response))
279 return IPS_ALERT;
280
281 char expectedResponse[FLAT_RES];
282 snprintf(expectedResponse, FLAT_RES, "*C%02d", productID);
283
284 if (strstr(response, expectedResponse))
285 {
286 // Set cover status to random value outside of range to force it to refresh
287 prevCoverStatus = 10;
288
289 IERmTimer(parkTimeoutID);
290 parkTimeoutID = IEAddTimer(30000, parkTimeoutHelper, this);
291 return IPS_BUSY;
292 }
293 else
294 return IPS_ALERT;
295}
296
298{
299 if (isSimulation())
300 {
301 simulationWorkCounter = 3;
302 return IPS_BUSY;
303 }
304
305 char response[FLAT_RES];
306 if (!sendCommand(">O000", response))
307 return IPS_ALERT;
308
309 char expectedResponse[FLAT_RES];
310 snprintf(expectedResponse, FLAT_RES, "*O%02d", productID);
311
312 if (strstr(response, expectedResponse))
313 {
314 // Set cover status to random value outside of range to force it to refresh
315 prevCoverStatus = 10;
316
317 IERmTimer(unparkTimeoutID);
318 unparkTimeoutID = IEAddTimer(30000, unparkTimeoutHelper, this);
319
320 return IPS_BUSY;
321 }
322 else
323 return IPS_ALERT;
324}
325
327{
328 char command[FLAT_CMD];
329 char response[FLAT_RES];
330
331 if (m_Type == FLIP_FLAT && ParkCapSP[1].getState() == ISS_ON)
332 {
333 LOG_ERROR("Cannot control light while cap is unparked.");
334 return false;
335 }
336
337 if (isSimulation())
338 return true;
339
340 if (enable)
341 strncpy(command, ">L000", FLAT_CMD);
342 else
343 strncpy(command, ">D000", FLAT_CMD);
344
345 if (!sendCommand(command, response))
346 return false;
347
348 char expectedResponse[FLAT_RES];
349 if (enable)
350 snprintf(expectedResponse, FLAT_RES, "*L%02d", productID);
351 else
352 snprintf(expectedResponse, FLAT_RES, "*D%02d", productID);
353
354 // JM 2022.11.06 find if the response contains expected response.
355 // We no longer check for zero-fillers since some implementations wrong interpreted as 0.
356 return (strstr(response, expectedResponse));
357}
358
359bool FlipFlat::getStatus()
360{
361 char response[FLAT_RES];
362
363 if (isSimulation())
364 {
365 if (ParkCapSP.getState() == IPS_BUSY && --simulationWorkCounter <= 0)
366 {
369 simulationWorkCounter = 0;
370 }
371
372 if (ParkCapSP.getState() == IPS_BUSY)
373 {
374 response[4] = '1';
375 response[6] = '0';
376 }
377 else
378 {
379 response[4] = '0';
380 // Parked/Closed
381 if (ParkCapSP[CAP_PARK].getState() == ISS_ON)
382 response[6] = '1';
383 else
384 response[6] = '2';
385 }
386
387 response[5] = (LightSP[FLAT_LIGHT_ON].getState() == ISS_ON) ? '1' : '0';
388 }
389 else
390 {
391 if (!sendCommand(">S000", response))
392 return false;
393 }
394
395 char motorStatus = *(response + 4) - '0';
396 char lightStatus = *(response + 5) - '0';
397 char coverStatus = *(response + 6) - '0';
398
399 bool statusUpdated = false;
400
401 if (coverStatus != prevCoverStatus)
402 {
403 prevCoverStatus = coverStatus;
404
405 statusUpdated = true;
406
407 switch (coverStatus)
408 {
409 case 0:
410 IUSaveText(&StatusT[0], "Not Open/Closed");
411 break;
412
413 case 1:
414 IUSaveText(&StatusT[0], "Closed");
416 {
420 LOG_INFO("Cover closed.");
422 }
423 break;
424
425 case 2:
426 IUSaveText(&StatusT[0], "Open");
428 {
432 LOG_INFO("Cover open.");
434 }
435 break;
436
437 case 3:
438 IUSaveText(&StatusT[0], "Timed out");
439 break;
440 }
441 }
442
443 if (lightStatus != prevLightStatus)
444 {
445 prevLightStatus = lightStatus;
446
447 statusUpdated = true;
448
449 switch (lightStatus)
450 {
451 case 0:
452 IUSaveText(&StatusT[1], "Off");
453 if (LightSP[0].getState() == ISS_ON)
454 {
457 LightSP.apply();
458 }
459 break;
460
461 case 1:
462 IUSaveText(&StatusT[1], "On");
463 if (LightSP[1].getState() == ISS_ON)
464 {
467 LightSP.apply();
468 }
469 break;
470 }
471 }
472
473 if (motorStatus != prevMotorStatus)
474 {
475 prevMotorStatus = motorStatus;
476
477 statusUpdated = true;
478
479 switch (motorStatus)
480 {
481 case 0:
482 IUSaveText(&StatusT[2], "Stopped");
483 break;
484
485 case 1:
486 IUSaveText(&StatusT[2], "Running");
487 break;
488 }
489 }
490
491 if (statusUpdated)
492 IDSetText(&StatusTP, nullptr);
493
494 return true;
495}
496
497bool FlipFlat::getFirmwareVersion()
498{
499 if (isSimulation())
500 {
501 IUSaveText(&FirmwareT[0], "Simulation");
502 IDSetText(&FirmwareTP, nullptr);
503 return true;
504 }
505
506 char response[FLAT_RES] = {0};
507 if (!sendCommand(">V000", response))
508 return false;
509
510 char versionString[4] = { 0 };
511 snprintf(versionString, 4, "%s", response + 4);
512 IUSaveText(&FirmwareT[0], versionString);
513 IDSetText(&FirmwareTP, nullptr);
514
515 return true;
516}
517
519{
520 if (!isConnected())
521 return;
522
523 getStatus();
524
525 // parking or unparking timed out, try again
526 if (ParkCapSP.getState() == IPS_BUSY && !strcmp(StatusT[0].text, "Timed out"))
527 {
528 if (ParkCapSP[0].getState() == ISS_ON)
529 ParkCap();
530 else
531 UnParkCap();
532 }
533
535}
536
537bool FlipFlat::getBrightness()
538{
539 if (isSimulation())
540 {
541 return true;
542 }
543
544 char response[FLAT_RES] = {0};
545 if (!sendCommand(">J000", response))
546 return false;
547
548 char brightnessString[4] = { 0 };
549 snprintf(brightnessString, 4, "%s", response + 4);
550
551 int brightnessValue = 0;
552 int rc = sscanf(brightnessString, "%d", &brightnessValue);
553
554 if (rc <= 0)
555 {
556 LOGF_ERROR("Unable to parse brightness value (%s)", response);
557 return false;
558 }
559
560 if (brightnessValue != prevBrightness)
561 {
562 prevBrightness = brightnessValue;
563 LightIntensityNP[0].setValue(brightnessValue);
565 }
566
567 return true;
568}
569
571{
572 if (isSimulation())
573 {
574 LightIntensityNP[0].setValue(value);
576 return true;
577 }
578
579 char command[FLAT_CMD] = {0};
580 char response[FLAT_RES] = {0};
581
582 snprintf(command, FLAT_CMD, ">B%03d", value);
583
584 if (!sendCommand(command, response))
585 return false;
586
587 char brightnessString[4] = { 0 };
588 snprintf(brightnessString, 4, "%s", response + 4);
589
590 int brightnessValue = 0;
591 int rc = sscanf(brightnessString, "%d", &brightnessValue);
592
593 if (rc <= 0)
594 {
595 LOGF_ERROR("Unable to parse brightness value (%s)", response);
596 return false;
597 }
598
599 if (brightnessValue != prevBrightness)
600 {
601 prevBrightness = brightnessValue;
602 LightIntensityNP[0].setValue(brightnessValue);
604 }
605
606 return true;
607}
608
609bool FlipFlat::sendCommand(const char *command, char *response)
610{
611 int nbytes_written = 0, nbytes_read = 0, rc = -1;
612 char errstr[MAXRBUF] = {0};
613 int i = 0;
614
615 tcflush(PortFD, TCIOFLUSH);
616
617 LOGF_DEBUG("CMD <%s>", command);
618
619 char buffer[FLAT_CMD + 1] = {0}; // space for terminating null
620 snprintf(buffer, FLAT_CMD + 1, "%s\n", command);
621
622 for (i = 0; i < 3; i++)
623 {
624 if ((rc = tty_write(PortFD, buffer, FLAT_CMD, &nbytes_written)) != TTY_OK)
625 {
626 usleep(50000);
627 continue;
628 }
629
630 if ((rc = tty_nread_section(PortFD, response, FLAT_RES, 0xA, FLAT_TIMEOUT, &nbytes_read)) != TTY_OK)
631 usleep(50000);
632 else
633 break;
634 }
635
636 if (i == 3)
637 {
638 tty_error_msg(rc, errstr, MAXRBUF);
639 LOGF_ERROR("%s error: %s.", command, errstr);
640 return false;
641 }
642
643 response[nbytes_read - 1] = 0; // strip \n
644
645 LOGF_DEBUG("RES <%s>", response);
646 return true;
647}
648
650{
651 static_cast<FlipFlat*>(context)->parkTimeout();
652}
653
655{
656 static_cast<FlipFlat*>(context)->unparkTimeout();
657}
658
659void FlipFlat::parkTimeout()
660{
661 if (ParkCapSP.getState() == IPS_BUSY)
662 {
663 LOG_WARN("Parking cap timed out. Retrying...");
664 ParkCap();
665 }
666}
667
668void FlipFlat::unparkTimeout()
669{
670 if (ParkCapSP.getState() == IPS_BUSY)
671 {
672 LOG_WARN("UnParking cap timed out. Retrying...");
673 UnParkCap();
674 }
675}
#define FLAT_TIMEOUT
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 ...
Provides interface to implement remotely controlled dust cover.
virtual bool EnableLightBox(bool enable) override
EnableLightBox Turn on/off on a light box. Must be implemented in the child class.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
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 flip_flat.cpp:86
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
static void parkTimeoutHelper(void *context)
virtual IPState UnParkCap() override
unPark dust cap (open cover). Must be implemented by child.
const char * getDefaultName() override
@ ALNITAK_DUST_COVER
Definition flip_flat.h:60
@ FLIP_FLAT
Definition flip_flat.h:58
virtual bool SetLightBoxBrightness(uint16_t value) override
setBrightness Set light level. Must be implemented in the child class, if supported.
virtual IPState ParkCap() override
Park dust cap (close cover). Must be implemented by child.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition flip_flat.cpp:94
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
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 initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition flip_flat.cpp:50
static void unparkTimeoutHelper(void *context)
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 syncDriverInfo()
syncDriverInfo sends the current driver information to the client.
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
Provides interface to implement controllable light box/switch device.
#define FLAT_CMD
#define FLAT_RES
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition eventloop.c:606
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition eventloop.c:586
int errno
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_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
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:509
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition indidevapi.c:44
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition indidevapi.c:416
#define MAXRBUF
Definition indidriver.c:52
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
#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
char name[MAXINDINAME]
Definition indiapi.h:248