Instrument Neutral Distributed Interface INDI 2.2.5
camelot.cpp
Go to the documentation of this file.
1/*
2 INDI Camelot Rotator
3 Copyright (C) 2025 Jasem Mutlaq ([email protected])
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19*/
20
21#include "camelot.h"
22#include "indicom.h"
23
24#include <cmath>
25#include <memory>
26#include <regex>
27#include <termios.h>
28#include <cstring>
29#include <sys/ioctl.h>
30#include <chrono>
31#include <math.h>
32#include <iomanip>
33
34// We declare an auto pointer to Camelot.
35static std::unique_ptr<Camelot> camelot(new Camelot());
36
38{
39 setVersion(1, 0);
40}
41
46{
48
52
54
55 // Speed
56 RotatorSpeedSP[SPEED_FAST].fill("SPEED_FAST", "Fast", ISS_OFF);
57 RotatorSpeedSP[SPEED_MEDIUM].fill("SPEED_MEDIUM", "Medium", ISS_ON);
58 RotatorSpeedSP[SPEED_SLOW].fill("SPEED_SLOW", "Slow", ISS_OFF);
59 RotatorSpeedSP.fill(getDeviceName(), "ROTATOR_SPEED", "Speed", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
60
61 // Power
62 RotatorPowerNP[POWER_NORMAL].fill("POWER_NORMAL", "Normal", "%.f", 0, 255, 1, 120);
63 RotatorPowerNP[POWER_HOLD].fill("POWER_HOLD", "Hold", "%.f", 0, 255, 1, 100);
64 RotatorPowerNP.fill(getDeviceName(), "ROTATOR_POWER", "Power", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
65
66 return true;
67}
68
73{
74 // Need to call this before INDI::Rotator::updateProperties();
75 if (isConnected())
76 {
77 char res[DRIVER_LEN] = {0};
78
79 // Position
80 if (sendCommand("P#", res))
81 {
82 try
83 {
84 double position = std::stod(res) / 10.0;
85 GotoRotatorNP[0].setValue(position);
87 }
88 catch (const std::invalid_argument& ia)
89 {
90 LOGF_ERROR("Invalid argument for std::stod in updateProperties: %s", ia.what());
92 }
93 catch (const std::out_of_range& oor)
94 {
95 LOGF_ERROR("Out of range for std::stod in updateProperties: %s", oor.what());
97 }
98 }
99
100 // Direction
101 if (sendCommand("K#", res))
102 {
103 if (strstr(res, "Normal"))
105 else
107 }
108 }
109
111
112 if (isConnected())
113 {
114 queryStatus();
115
116 defineProperty(RotatorSpeedSP);
117 defineProperty(RotatorPowerNP);
118 }
119 else
120 {
121 deleteProperty(RotatorSpeedSP);
122 deleteProperty(RotatorPowerNP);
123 }
124
125 return true;
126}
127
132{
133 return "Camelot Rotator";
134}
135
140{
141 char res[DRIVER_LEN] = {0};
142 // Try up to 3 times
143 for (int i = 0; i < 3; i++)
144 {
145 if (sendCommand("#", res))
146 {
147 if (strstr(res, "OK.ROT!"))
148 return true;
149 }
150
151 usleep(100000);
152 }
153 return false;
154}
155
160{
161 char cmd[DRIVER_LEN] = {0};
162 // Command is T#### where #### is degrees * 10
163 snprintf(cmd, DRIVER_LEN, "T%04d", static_cast<int>(round(degrees * 10.0)));
164 if (sendCommand(cmd))
165 {
166 return IPS_BUSY;
167 }
168
169 return IPS_ALERT;
170}
171
176{
177 return sendCommand("L#");
178}
179
183bool Camelot::ReverseRotator(bool enabled)
184{
185 INDI_UNUSED(enabled);
186 return sendCommand("D#");
187}
188
192bool Camelot::SyncRotator(double degrees)
193{
194 char cmd[DRIVER_LEN] = {0};
195 // Command is S### where ### is degrees * 10
196 snprintf(cmd, DRIVER_LEN, "S%03d", static_cast<int>(round(degrees * 10.0)));
197 return sendCommand(cmd);
198}
199
203bool Camelot::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
204{
205 if (dev && !strcmp(dev, getDeviceName()))
206 {
207 if (RotatorSpeedSP.isNameMatch(name))
208 {
209 updateProperty(RotatorSpeedSP, states, names, n, [this, states, n]
210 {
211 char cmd[DRIVER_LEN] = {0};
212 snprintf(cmd, DRIVER_LEN, "Z%d", IUFindOnStateIndex(states, n));
213 return sendCommand(cmd);
214 }, true);
215 return true;
216 }
217 }
218
219 return INDI::Rotator::ISNewSwitch(dev, name, states, names, n);
220}
221
225bool Camelot::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
226{
227 if (dev && !strcmp(dev, getDeviceName()))
228 {
229 if (RotatorPowerNP.isNameMatch(name))
230 {
231 updateProperty(RotatorPowerNP, values, names, n, [this, values]()
232 {
233 char cmd[DRIVER_LEN] = {0};
234 snprintf(cmd, DRIVER_LEN, "*%d", static_cast<int>(values[POWER_NORMAL]));
235 auto normalOk = sendCommand(cmd);
236 snprintf(cmd, DRIVER_LEN, "+%d", static_cast<int>(values[POWER_HOLD]));
237 auto holdOk = sendCommand(cmd);
238 return normalOk && holdOk;
239 }, true);
240 return true;
241 }
242 }
243 return INDI::Rotator::ISNewNumber(dev, name, values, names, n);
244}
245
249bool Camelot::queryStatus()
250{
251 char res[DRIVER_LEN] = {0};
252
253 // Speed
254 if (sendCommand("Y#", res))
255 {
256 if (strstr(res, "Fast"))
257 RotatorSpeedSP[SPEED_FAST].setState(ISS_ON);
258 else if (strstr(res, "Medium"))
259 RotatorSpeedSP[SPEED_MEDIUM].setState(ISS_ON);
260 else if (strstr(res, "Slow"))
261 RotatorSpeedSP[SPEED_SLOW].setState(ISS_ON);
262 }
263
264 // Power
265 if (sendCommand("R1#", res))
266 {
267 RotatorPowerNP[POWER_NORMAL].setValue(std::stoi(res));
268 }
269
270 if (sendCommand("R0#", res))
271 {
272 RotatorPowerNP[POWER_HOLD].setValue(std::stoi(res));
273 }
274 RotatorPowerNP.setState(IPS_OK);
275
276 return true;
277}
278
283{
284 if (!isConnected())
285 return;
286
287 char res[DRIVER_LEN] = {0};
288 double current_pos = GotoRotatorNP[0].getValue();
289 IPState current_state = GotoRotatorNP.getState();
290
291 if (current_state == IPS_BUSY)
292 {
293 if (sendCommand("J#", res) && strstr(res, "M0:OK"))
295 }
296
297 if (sendCommand("P#", res))
298 {
299 try
300 {
301 GotoRotatorNP[0].setValue(std::stod(res) / 10.0);
302 }
303 catch (const std::invalid_argument& ia)
304 {
305 LOGF_ERROR("Invalid argument for std::stod in TimerHit: %s", ia.what());
306 }
307 catch (const std::out_of_range& oor)
308 {
309 LOGF_ERROR("Out of range for std::stod in TimerHit: %s", oor.what());
310 }
311 }
312
313 if (std::abs(current_pos - GotoRotatorNP[0].getValue()) > 0.1 || current_state != GotoRotatorNP.getState())
314 {
316 }
317
319}
320
325{
327 RotatorSpeedSP.save(fp);
328 RotatorPowerNP.save(fp);
329 return true;
330
331}
332
336bool Camelot::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
337{
338 int nbytes_written = 0, nbytes_read = 0, rc = -1;
339 int retries = 0;
340
341 do
342 {
343 tcflush(PortFD, TCIOFLUSH);
344
345 if (cmd_len > 0)
346 {
347 char hex_cmd[DRIVER_LEN * 3] = {0};
348 hexDump(hex_cmd, cmd, cmd_len);
349 LOGF_DEBUG("CMD <%s>", hex_cmd);
350 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
351 }
352 else
353 {
354 LOGF_DEBUG("CMD <%s>", cmd);
355
356 char formatted_command[DRIVER_LEN] = {0};
357 snprintf(formatted_command, DRIVER_LEN, "%s", cmd);
358 rc = tty_write_string(PortFD, formatted_command, &nbytes_written);
359 }
360
361 if (rc != TTY_OK)
362 {
363 char errstr[MAXRBUF] = {0};
364 tty_error_msg(rc, errstr, MAXRBUF);
365 LOGF_ERROR("Serial write error: %s.", errstr);
366 return false;
367 }
368
369 if (res == nullptr)
370 return true;
371
372 if (res_len > 0)
373 rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
374 else
375 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
376
377 if (rc == TTY_OK)
378 break;
379
380 if (rc == TTY_TIME_OUT)
381 retries++;
382 else
383 {
384 char errstr[MAXRBUF] = {0};
385 tty_error_msg(rc, errstr, MAXRBUF);
386 LOGF_ERROR("%s Serial read error: %s.", cmd, errstr);
387 return false;
388 }
389 }
390 while (retries < 3);
391
392 if (rc != TTY_OK)
393 {
394 char errstr[MAXRBUF] = {0};
395 tty_error_msg(rc, errstr, MAXRBUF);
396 LOGF_ERROR("%s Serial read error after 3 retries: %s.", cmd, errstr);
397 return false;
398 }
399
400 if (res_len > 0)
401 {
402 char hex_res[DRIVER_LEN * 3] = {0};
403 hexDump(hex_res, res, res_len);
404 LOGF_DEBUG("RES <%s>", hex_res);
405 }
406 else
407 {
408 // Remove extra #
409 res[nbytes_read - 1] = 0;
410 LOGF_DEBUG("RES <%s>", res);
411 }
412
413 return true;
414}
415
419void Camelot::hexDump(char * buf, const char * data, uint32_t size)
420{
421 for (uint32_t i = 0; i < size; i++)
422 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
423
424 if (size > 0)
425 buf[3 * size - 1] = '\0';
426}
bool SyncRotator(double degrees) override
SyncRotator Set current angle as the supplied angle without moving the rotator.
Definition camelot.cpp:192
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition camelot.cpp:203
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition camelot.cpp:72
Camelot()
Definition camelot.cpp:37
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition camelot.cpp:225
bool Handshake() override
perform handshake with device to check communication
Definition camelot.cpp:139
const char * getDefaultName() override
Definition camelot.cpp:131
bool saveConfigItems(FILE *fp) override
Save configuration items.
Definition camelot.cpp:324
bool AbortRotator() override
AbortRotator Abort all motion.
Definition camelot.cpp:175
bool ReverseRotator(bool enabled) override
ReverseRotator Reverse the direction of the rotator. CW is usually the normal direction,...
Definition camelot.cpp:183
bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition camelot.cpp:45
IPState MoveRotator(double degrees) override
MoveRotator Go to specific angle.
Definition camelot.cpp:159
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition camelot.cpp:282
bool isConnected() const
bool updateProperty(PropertyType &property, ValueType *values, char *names[], int n, std::function< bool()> updater, bool saveConfig=false)
Generic helper function to update an INDI property based on an external operation.
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)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
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.
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
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, ISRule rule, double timeout, IPState state)
void SetCapability(uint32_t cap)
SetRotatorCapability sets the Rotator capabilities. All capabilities must be initialized.
INDI::PropertySwitch ReverseRotatorSP
INDI::PropertyNumber GotoRotatorNP
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the reverse direction property in the configuration file
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
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_ATMOST1
Definition indiapi.h:172
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
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
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
@ TTY_TIME_OUT
Definition indicom.h:58
int IUFindOnStateIndex(ISState *states, int n)
Returns the index of first ON switch it finds in the ISState array.
Definition indidevapi.c:346
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2
double round(double value, int decimal_places)