Instrument Neutral Distributed Interface INDI 2.2.5
moonlite.cpp
Go to the documentation of this file.
1/*
2 Moonlite Focuser
3 Copyright (C) 2013-2019 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 "moonlite.h"
22
23#include "indicom.h"
24
25#include <cmath>
26#include <cstring>
27#include <memory>
28
29#include <termios.h>
30#include <unistd.h>
31
32static std::unique_ptr<MoonLite> moonLite(new MoonLite());
33
35{
36 setVersion(1, 1);
37
38 // Can move in Absolute & Relative motions, can AbortFocuser motion, and has variable speed.
41}
42
44{
46
47 FocusSpeedNP[0].setMin(1);
48 FocusSpeedNP[0].setMax(5);
49 FocusSpeedNP[0].setValue(1);
50
51 // Step Mode
52 StepModeSP[FOCUS_HALF_STEP].fill("FOCUS_HALF_STEP", "Half Step", ISS_OFF);
53 StepModeSP[FOCUS_FULL_STEP].fill("FOCUS_FULL_STEP", "Full Step", ISS_ON);
54 StepModeSP.fill(getDeviceName(), "Step Mode", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
55 IPS_IDLE);
56
57 // Focuser temperature
58 TemperatureNP[0].fill("TEMPERATURE", "Celsius", "%6.2f", -50, 70., 0., 0.);
59 TemperatureNP.fill(getDeviceName(), "FOCUS_TEMPERATURE", "Temperature",
61
62 // Temperature Settings
63 TemperatureSettingNP[Calibration].fill("Calibration", "", "%6.2f", -100, 100, 0.5, 0);
64 TemperatureSettingNP[Coefficient].fill("Coefficient", "", "%6.2f", -100, 100, 0.5, 0);
65 TemperatureSettingNP.fill(getDeviceName(), "T. Settings", "",
67
68 // Compensate for temperature
69 TemperatureCompensateSP[INDI_ENABLED].fill("Enable", "", ISS_OFF);
70 TemperatureCompensateSP[INDI_DISABLED].fill("Disable", "", ISS_ON);
71 TemperatureCompensateSP.fill(getDeviceName(), "T. Compensate",
73
74 /* Relative and absolute movement */
75 FocusRelPosNP[0].setMin(0.);
76 FocusRelPosNP[0].setMax(50000.);
77 FocusRelPosNP[0].setValue(0);
78 FocusRelPosNP[0].setStep(1000);
79
80 FocusAbsPosNP[0].setMin(0.);
81 FocusAbsPosNP[0].setMax(100000.);
82 FocusAbsPosNP[0].setValue(0);
83 FocusAbsPosNP[0].setStep(1000);
84
87
88 return true;
89}
90
92{
94
95 if (isConnected())
96 {
97 defineProperty(TemperatureNP);
98 defineProperty(StepModeSP);
99 defineProperty(TemperatureSettingNP);
100 defineProperty(TemperatureCompensateSP);
101
102 GetFocusParams();
103
104 LOG_INFO("MoonLite parameters updated, focuser ready for use.");
105 }
106 else
107 {
108 deleteProperty(TemperatureNP.getName());
109 deleteProperty(StepModeSP.getName());
110 deleteProperty(TemperatureSettingNP.getName());
111 deleteProperty(TemperatureCompensateSP.getName());
112 }
113
114 return true;
115}
116
118{
119 if (Ack())
120 {
121 LOG_INFO("MoonLite is online. Getting focus parameters...");
122 return true;
123 }
124
125 LOG_INFO(
126 "Error retrieving data from MoonLite, please ensure MoonLite controller is powered and the port is correct.");
127 return false;
128}
129
131{
132 return "MoonLite";
133}
134
135bool MoonLite::Ack()
136{
137 bool success = false;
138
139 for (int i = 0; i < 3; i++)
140 {
141 if (readVersion())
142 {
143 success = true;
144 break;
145 }
146
147 sleep(1);
148 }
149
150 return success;
151}
152
153bool MoonLite::readStepMode()
154{
155 char res[ML_RES] = {0};
156
157 if (sendCommand(":GH#", res) == false)
158 return false;
159
160 if (strcmp(res, "FF#") == 0)
161 StepModeSP[FOCUS_HALF_STEP].setState(ISS_ON);
162 else if (strcmp(res, "00#") == 0)
163 StepModeSP[FOCUS_FULL_STEP].setState(ISS_ON);
164 else
165 {
166 LOGF_ERROR("Unknown error: focuser step value (%s)", res);
167 return false;
168 }
169
170 return true;
171}
172
173bool MoonLite::readVersion()
174{
175 char res[ML_RES] = {0};
176
177 if (sendCommand(":GV#", res, true, 2) == false)
178 return false;
179
180 LOGF_INFO("Detected firmware version %c.%c", res[0], res[1]);
181
182 return true;
183}
184
185bool MoonLite::readTemperature()
186{
187 char res[ML_RES] = {0};
188
189 sendCommand(":C#");
190
191 if (sendCommand(":GT#", res) == false)
192 return false;
193
194 uint32_t temp = 0;
195 int rc = sscanf(res, "%X", &temp);
196 if (rc > 0)
197 // Signed hex
198 TemperatureNP[0].setValue(static_cast<int16_t>(temp) / 2.0);
199 else
200 {
201 LOGF_ERROR("Unknown error: focuser temperature value (%s)", res);
202 return false;
203 }
204
205 return true;
206}
207
208bool MoonLite::readTemperatureCoefficient()
209{
210 char res[ML_RES] = {0};
211
212 if (sendCommand(":GC#", res) == false)
213 return false;
214
215 uint8_t coefficient = 0;
216 int rc = sscanf(res, "%hhX", &coefficient);
217 if (rc > 0)
218 // Signed HEX of two digits
219 TemperatureSettingNP[1].setValue(static_cast<int8_t>(coefficient) / 2.0);
220 else
221 {
222 LOGF_ERROR("Unknown error: focuser temperature coefficient value (%s)", res);
223 return false;
224 }
225
226 return true;
227}
228
229bool MoonLite::readPosition()
230{
231 char res[ML_RES] = {0};
232
233 if (sendCommand(":GP#", res) == false)
234 return false;
235
236 int32_t pos;
237 int rc = sscanf(res, "%X#", &pos);
238
239 if (rc > 0)
240 FocusAbsPosNP[0].setValue(pos);
241 else
242 {
243 LOGF_ERROR("Unknown error: focuser position value (%s)", res);
244 return false;
245 }
246
247 return true;
248}
249
250bool MoonLite::readSpeed()
251{
252 char res[ML_RES] = {0};
253
254 if (sendCommand(":GD#", res) == false)
255 return false;
256
257 uint16_t speed = 0;
258 int rc = sscanf(res, "%hX#", &speed);
259
260 if (rc > 0)
261 {
262 int focus_speed = -1;
263 while (speed > 0)
264 {
265 speed >>= 1;
266 focus_speed++;
267 }
268 FocusSpeedNP[0].setValue(focus_speed);
269 }
270 else
271 {
272 LOGF_ERROR("Unknown error: focuser speed value (%s)", res);
273 return false;
274 }
275
276 return true;
277}
278
279bool MoonLite::isMoving()
280{
281 char res[ML_RES] = {0};
282
283 if (sendCommand(":GI#", res) == false)
284 return false;
285
286 // JM 2020-03-13: 01# and 1# should be both accepted
287 if (strstr(res, "1#"))
288 return true;
289 else if (strstr(res, "0#"))
290 return false;
291
292 LOGF_ERROR("Unknown error: isMoving value (%s)", res);
293 return false;
294}
295
296bool MoonLite::setTemperatureCalibration(double calibration)
297{
298 char cmd[ML_RES] = {0};
299 uint8_t hex = static_cast<int8_t>(calibration * 2) & 0xFF;
300 snprintf(cmd, ML_RES, ":PO%02X#", hex);
301 return sendCommand(cmd);
302}
303
304bool MoonLite::setTemperatureCoefficient(double coefficient)
305{
306 char cmd[ML_RES] = {0};
307 uint8_t hex = static_cast<int8_t>(coefficient * 2) & 0xFF;
308 snprintf(cmd, ML_RES, ":SC%02X#", hex);
309 return sendCommand(cmd);
310}
311
312bool MoonLite::SyncFocuser(uint32_t ticks)
313{
314 char cmd[ML_RES] = {0};
315 snprintf(cmd, ML_RES, ":SP%04X#", ticks);
316 return sendCommand(cmd);
317}
318
319bool MoonLite::MoveFocuser(uint32_t position)
320{
321 char cmd[ML_RES] = {0};
322 snprintf(cmd, ML_RES, ":SN%04X#", position);
323 // Set Position First
324 if (sendCommand(cmd) == false)
325 return false;
326 // Now start motion toward position
327 if (sendCommand(":FG#") == false)
328 return false;
329
330 return true;
331}
332
333bool MoonLite::setStepMode(FocusStepMode mode)
334{
335 char cmd[ML_RES] = {0};
336 snprintf(cmd, ML_RES, ":S%c#", (mode == FOCUS_HALF_STEP) ? 'H' : 'F');
337 return sendCommand(cmd);
338}
339
340bool MoonLite::setSpeed(int speed)
341{
342 char cmd[ML_RES] = {0};
343 int hex_value = 1;
344 hex_value <<= speed;
345 snprintf(cmd, ML_RES, ":SD%02X#", hex_value);
346 return sendCommand(cmd);
347}
348
349bool MoonLite::setTemperatureCompensation(bool enable)
350{
351 char cmd[ML_RES] = {0};
352 snprintf(cmd, ML_RES, ":%c#", enable ? '+' : '-');
353 return sendCommand(cmd);
354}
355
356bool MoonLite::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
357{
358 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
359 {
360 // Focus Step Mode
361 if (StepModeSP.isNameMatch(name))
362 {
363 int current_mode = StepModeSP.findOnSwitchIndex();
364
365 StepModeSP.update(states, names, n);
366
367 int target_mode = StepModeSP.findOnSwitchIndex();
368
369 if (current_mode == target_mode)
370 {
371 StepModeSP.setState(IPS_OK);
372 StepModeSP.apply();
373 }
374
375 bool rc = setStepMode(target_mode == 0 ? FOCUS_HALF_STEP : FOCUS_FULL_STEP);
376 if (!rc)
377 {
378 StepModeSP.reset();
379 StepModeSP[current_mode].setState(ISS_ON);
380 StepModeSP.setState(IPS_ALERT);
381 StepModeSP.apply();
382 return false;
383 }
384
385 StepModeSP.setState(IPS_OK);
386 StepModeSP.apply();
387 return true;
388 }
389
390 // Temperature Compensation Mode
391 if (TemperatureCompensateSP.isNameMatch(name))
392 {
393 int last_index = TemperatureCompensateSP.findOnSwitchIndex();
394 TemperatureCompensateSP.update(states, names, n);
395
396 bool rc = setTemperatureCompensation((TemperatureCompensateSP[INDI_ENABLED].getState() == ISS_ON));
397
398 if (!rc)
399 {
400 TemperatureCompensateSP.setState(IPS_ALERT);
401 TemperatureCompensateSP.reset();
402 TemperatureCompensateSP[last_index].setState(ISS_ON);
403 TemperatureCompensateSP.apply();
404 return false;
405 }
406
407 TemperatureCompensateSP.setState(IPS_OK);
408 TemperatureCompensateSP.apply();
409 return true;
410 }
411 }
412
413 return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
414}
415
416bool MoonLite::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
417{
418 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
419 {
420 // Temperature Settings
421 if (TemperatureSettingNP.isNameMatch(name))
422 {
423 TemperatureSettingNP.update(values, names, n);
424 if (!setTemperatureCalibration(TemperatureSettingNP[Calibration].getValue()) ||
425 !setTemperatureCoefficient(TemperatureSettingNP[Coefficient].getValue()))
426 {
427 TemperatureSettingNP.setState(IPS_ALERT);
428 TemperatureSettingNP.apply();
429 return false;
430 }
431
432 TemperatureSettingNP.setState(IPS_OK);
433 TemperatureSettingNP.apply();
434 return true;
435 }
436 }
437
438 return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
439}
440
441void MoonLite::GetFocusParams()
442{
443 if (readPosition())
445
446 if (readTemperature())
447 TemperatureNP.apply();
448
449 if (readTemperatureCoefficient())
450 TemperatureSettingNP.apply();
451
452 if (readSpeed())
454
455 if (readStepMode())
456 StepModeSP.apply();
457}
458
460{
461 return setSpeed(speed);
462}
463
464IPState MoonLite::MoveFocuser(FocusDirection dir, int speed, uint16_t duration)
465{
466 if (speed != static_cast<int>(FocusSpeedNP[0].getValue()))
467 {
468 if (!setSpeed(speed))
469 return IPS_ALERT;
470 }
471
472 // either go all the way in or all the way out
473 // then use timer to stop
474 if (dir == FOCUS_INWARD)
475 MoveFocuser(0);
476 else
477 MoveFocuser(static_cast<uint32_t>(FocusMaxPosNP[0].getValue()));
478
479 IEAddTimer(duration, &MoonLite::timedMoveHelper, this);
480 return IPS_BUSY;
481}
482
483void MoonLite::timedMoveHelper(void * context)
484{
485 static_cast<MoonLite *>(context)->timedMoveCallback();
486}
487
488void MoonLite::timedMoveCallback()
489{
490 AbortFocuser();
494 FocusTimerNP[0].setValue(0);
498}
499
501{
502 targetPos = targetTicks;
503
504 if (!MoveFocuser(targetPos))
505 return IPS_ALERT;
506
507 return IPS_BUSY;
508}
509
511{
512 // Clamp
513 int32_t offset = ((dir == FOCUS_INWARD) ? -1 : 1) * static_cast<int32_t>(ticks);
514 int32_t newPosition = FocusAbsPosNP[0].getValue() + offset;
515 newPosition = std::max(static_cast<int32_t>(FocusAbsPosNP[0].getMin()),
516 std::min(static_cast<int32_t>(FocusAbsPosNP[0].getMax()),
517 newPosition));
518
519 if (!MoveFocuser(newPosition))
520 return IPS_ALERT;
521
522 FocusRelPosNP[0].setValue(ticks);
524
525 return IPS_BUSY;
526}
527
529{
530 if (!isConnected())
531 return;
532
533 bool rc = readPosition();
534 if (rc)
535 {
536 if (fabs(lastPos - FocusAbsPosNP[0].getValue()) > 5)
537 {
539 lastPos = static_cast<uint32_t>(FocusAbsPosNP[0].getValue());
540 }
541 }
542
543 rc = readTemperature();
544 if (rc)
545 {
546 if (std::abs(lastTemperature - TemperatureNP[0].getValue()) >= 0.5)
547 {
548 TemperatureNP.apply();
549 lastTemperature = static_cast<uint32_t>(TemperatureNP[0].getValue());
550 }
551 }
552
554 {
555 if (!isMoving())
556 {
561 lastPos = static_cast<uint32_t>(FocusAbsPosNP[0].getValue());
562 LOG_INFO("Focuser reached requested position.");
563 }
564 }
565
567}
568
570{
571 return sendCommand(":FQ#");
572}
573
575{
576 Focuser::saveConfigItems(fp);
577
578 StepModeSP.save(fp);
579
580 return true;
581}
582
583bool MoonLite::sendCommand(const char * cmd, char * res, bool silent, int nret)
584{
585 int nbytes_written = 0, nbytes_read = 0, rc = -1;
586
587 tcflush(PortFD, TCIOFLUSH);
588
589 LOGF_DEBUG("CMD <%s>", cmd);
590
591 if ((rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK)
592 {
593 char errstr[MAXRBUF] = {0};
594 tty_error_msg(rc, errstr, MAXRBUF);
595 if (!silent)
596 LOGF_ERROR("Serial write error: %s.", errstr);
597 return false;
598 }
599
600 if (res == nullptr)
601 {
602 tcdrain(PortFD);
603 return true;
604 }
605
606 // this is to handle the GV command which doesn't return the terminator, use the number of chars expected
607 if (nret == 0)
608 {
609 rc = tty_nread_section(PortFD, res, ML_RES, ML_DEL, ML_TIMEOUT, &nbytes_read);
610 }
611 else
612 {
613 rc = tty_read(PortFD, res, nret, ML_TIMEOUT, &nbytes_read);
614 }
615 if (rc != TTY_OK)
616 {
617 char errstr[MAXRBUF] = {0};
618 tty_error_msg(rc, errstr, MAXRBUF);
619 if (!silent)
620 LOGF_ERROR("Serial read error: %s.", errstr);
621 return false;
622 }
623
624 LOGF_DEBUG("RES <%s>", res);
625
626 tcflush(PortFD, TCIOFLUSH);
627
628 return true;
629}
bool isConnected() const
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
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.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
INDI::PropertyNumber FocusRelPosNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusSpeedNP
INDI::PropertyNumber FocusTimerNP
INDI::PropertyNumber FocusMaxPosNP
INDI::PropertyNumber FocusAbsPosNP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
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.
const char * getName() const
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)
virtual IPState MoveFocuser(FocusDirection dir, int speed, uint16_t duration) override
MoveFocuser Move focuser in a specific direction and speed for period of time.
Definition moonlite.cpp:464
virtual bool saveConfigItems(FILE *fp) override
Definition moonlite.cpp:574
static void timedMoveHelper(void *context)
Definition moonlite.cpp:483
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition moonlite.cpp:528
virtual bool SetFocuserSpeed(int speed) override
SetFocuserSpeed Set Focuser speed.
Definition moonlite.cpp:459
@ FOCUS_HALF_STEP
Definition moonlite.h:33
@ FOCUS_FULL_STEP
Definition moonlite.h:33
const char * getDefaultName() override
Definition moonlite.cpp:130
virtual bool SyncFocuser(uint32_t ticks) override
SyncFocuser Set the supplied position as the current focuser position.
Definition moonlite.cpp:312
virtual bool Handshake() override
Handshake Try to communicate with Focuser and see if there is a valid response.
Definition moonlite.cpp:117
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
Definition moonlite.cpp:569
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
Definition moonlite.cpp:356
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveAbsFocuser Move to an absolute target position.
Definition moonlite.cpp:500
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
Definition moonlite.cpp:91
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
Definition moonlite.cpp:43
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
Definition moonlite.cpp:416
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveRelFocuser Move focuser for a relative amount of ticks in a specific direction.
Definition moonlite.cpp:510
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
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
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(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
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
#define getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2