Instrument Neutral Distributed Interface INDI 2.2.5
askar_waf.cpp
Go to the documentation of this file.
1/*
2 Askar-WAF Focuser
3 Copyright (C) 2026 Jiaxing Ruixing Optical Instrument Co., Ltd.
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
18 USA
19
20*/
21
22#include "askar_waf.h"
23
25#include "indicom.h"
26
27#include <cstring>
28#include <memory>
29
30#include <termios.h>
31#include <unistd.h>
32
33static std::unique_ptr<AskarWAF> askarWAF(new AskarWAF());
34
36 setVersion(1, 0);
37
38 // Absolute & relative motion, abort, sync, reverse and backlash compensation
39 // are all natively supported by the Askar-WAF "F...#" command set.
43}
44
47
48 FirmwareVersionTP[VERSION].fill("VERSION", "Firmware", "Unknown");
49 FirmwareVersionTP[MODEL].fill("MODEL", "Model", "Unknown");
50 FirmwareVersionTP.fill(getDeviceName(), "FOCUS_FIRMWARE", "Firmware",
52
53 // Max travel: firmware accepts 100..1,000,000 logical steps (FMn#)
54 FocusMaxPosNP[0].setMin(100);
55 FocusMaxPosNP[0].setMax(1000000);
56 FocusMaxPosNP[0].setStep(1000);
57 FocusMaxPosNP[0].setValue(100000);
58
59 FocusAbsPosNP[0].setMin(0);
60 FocusAbsPosNP[0].setMax(100000);
61 FocusAbsPosNP[0].setStep(1000);
62 FocusAbsPosNP[0].setValue(0);
63
64 FocusRelPosNP[0].setMin(0);
65 FocusRelPosNP[0].setMax(50000);
66 FocusRelPosNP[0].setStep(1000);
67 FocusRelPosNP[0].setValue(0);
68
69 FocusSyncNP[0].setMin(0);
70 FocusSyncNP[0].setMax(100000);
71 FocusSyncNP[0].setStep(1000);
72
73 // Backlash: firmware accepts 0..10000 steps (FBn#); 0 disables compensation
74 FocusBacklashNP[0].setMin(0);
75 FocusBacklashNP[0].setMax(10000);
76 FocusBacklashNP[0].setStep(10);
77 FocusBacklashNP[0].setValue(0);
78
81
82 // Baud rate is irrelevant over USB CDC-ACM, but a value is still required to
83 // open the port.
85
86 return true;
87}
88
91
92 if (isConnected()) {
93 defineProperty(FirmwareVersionTP);
94
95 bool ok = true;
96
97 if (!readMaxPosition()) {
99 ok = false;
100 }
101
102 if (!readPosition()) {
104 ok = false;
105 }
106
107 if (!readBacklash()) {
110 ok = false;
111 }
112
113 if (!readReverse()) {
115 ok = false;
116 }
117
118 lastPosition = static_cast<uint32_t>(FocusAbsPosNP[0].getValue());
119
125
126 if (ok)
127 LOG_INFO("Askar-WAF parameters updated, focuser ready for use.");
128 else
129 LOG_ERROR("Failed to read one or more Askar-WAF parameters after "
130 "connect.");
131 } else {
132 deleteProperty(FirmwareVersionTP);
133 }
134
135 return true;
136}
137
139 char res[WAF_RES] = {0};
140
141 for (int i = 0; i < 3; i++) {
142 if (sendCommand("FV#", res, true)) {
143 char version[16] = {0};
144 if (sscanf(res, "FV%15[^#]#", version) == 1) {
145 FirmwareVersionTP[VERSION].setText(version);
146 LOGF_INFO("Askar-WAF is online. Firmware version %s.", version);
147
148 char model[32] = {0};
149 if (sendCommand("FI#", res, true) &&
150 sscanf(res, "FI%31[^#]#", model) == 1)
151 FirmwareVersionTP[MODEL].setText(model);
152
153 return true;
154 }
155 }
156
157 sleep(1);
158 }
159
160 LOG_ERROR("Error retrieving data from Askar-WAF, please ensure the focuser "
161 "is powered and the port is correct.");
162 return false;
163}
164
165const char *AskarWAF::getDefaultName() { return "Askar WAF"; }
166
167bool AskarWAF::sendCommand(const char *cmd, char *res, bool silent) {
168 int nbytes_written = 0, nbytes_read = 0, rc = TTY_OK;
169
170 tcflush(PortFD, TCIOFLUSH);
171
172 LOGF_DEBUG("CMD <%s>", cmd);
173
174 if ((rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK) {
175 char errstr[MAXRBUF];
176 tty_error_msg(rc, errstr, MAXRBUF);
177 if (!silent)
178 LOGF_ERROR("Serial write error: %s.", errstr);
179 return false;
180 }
181
182 if (res == nullptr) {
183 tcdrain(PortFD);
184 return true;
185 }
186
187 // Leave one byte for the trailing '\0': tty_nread_section can return
188 // nbytes_read == nsize when the stop char is the last byte read.
189 if ((rc = tty_nread_section(PortFD, res, WAF_RES - 1, WAF_DEL, WAF_TIMEOUT,
190 &nbytes_read)) != TTY_OK) {
191 char errstr[MAXRBUF];
192 tty_error_msg(rc, errstr, MAXRBUF);
193 if (!silent)
194 LOGF_ERROR("Serial read error: %s.", errstr);
195 return false;
196 }
197
198 // nbytes_read includes the trailing '#'
199 res[nbytes_read] = '\0';
200
201 LOGF_DEBUG("RES <%s>", res);
202
203 if (strcmp(res, "FE#") == 0) {
204 if (!silent)
205 LOGF_ERROR("Askar-WAF rejected command <%s>.", cmd);
206 return false;
207 }
208
209 return true;
210}
211
212bool AskarWAF::readPosition() {
213 char res[WAF_RES] = {0};
214
215 if (!sendCommand("Fp#", res))
216 return false;
217
218 int32_t pos = 0;
219 if (sscanf(res, "Fp%d#", &pos) != 1) {
220 LOGF_ERROR("Invalid position response <%s>.", res);
221 return false;
222 }
223
224 FocusAbsPosNP[0].setValue(pos);
225 return true;
226}
227
228bool AskarWAF::readMaxPosition() {
229 char res[WAF_RES] = {0};
230
231 if (!sendCommand("Fm#", res))
232 return false;
233
234 int32_t maxStep = 0;
235 if (sscanf(res, "Fm%d#", &maxStep) != 1) {
236 LOGF_ERROR("Invalid max travel response <%s>.", res);
237 return false;
238 }
239
240 FocusMaxPosNP[0].setValue(maxStep);
241
242 FocusAbsPosNP[0].setMax(maxStep);
243 FocusAbsPosNP[0].setMin(0);
244 FocusAbsPosNP[0].setStep(maxStep / 50.0);
245
246 FocusSyncNP[0].setMax(maxStep);
247 FocusSyncNP[0].setMin(0);
248 FocusSyncNP[0].setStep(maxStep / 50.0);
249
250 FocusRelPosNP[0].setMax(maxStep / 2.0);
251 FocusRelPosNP[0].setMin(0);
252 FocusRelPosNP[0].setStep(maxStep / 100.0);
253
257 SyncPresets(static_cast<uint32_t>(maxStep));
258
259 return true;
260}
261
262bool AskarWAF::readBacklash() {
263 char res[WAF_RES] = {0};
264
265 if (!sendCommand("Fb#", res))
266 return false;
267
268 int32_t backlash = 0;
269 if (sscanf(res, "Fb%d#", &backlash) != 1) {
270 LOGF_ERROR("Invalid backlash response <%s>.", res);
271 return false;
272 }
273
274 FocusBacklashNP[0].setValue(backlash);
275
279
280 return true;
281}
282
283bool AskarWAF::readReverse() {
284 char res[WAF_RES] = {0};
285
286 if (!sendCommand("Fr#", res))
287 return false;
288
289 bool reversed = (strcmp(res, "Fr1#") == 0);
290
294
295 return true;
296}
297
298bool AskarWAF::isMoving(bool *ok) {
299 char res[WAF_RES] = {0};
300
301 if (!sendCommand("FQ#", res)) {
302 if (ok)
303 *ok = false;
304 return true;
305 }
306
307 if (strcmp(res, "FQ1#") == 0) {
308 if (ok)
309 *ok = true;
310 return true;
311 }
312
313 if (strcmp(res, "FQ0#") == 0) {
314 if (ok)
315 *ok = true;
316 return false;
317 }
318
319 LOGF_ERROR("Unknown isMoving response <%s>.", res);
320 if (ok)
321 *ok = false;
322 return true;
323}
324
325IPState AskarWAF::MoveAbsFocuser(uint32_t targetTicks) {
326 char cmd[WAF_RES] = {0};
327 char res[WAF_RES] = {0};
328 snprintf(cmd, sizeof(cmd), "FP%u#", targetTicks);
329
330 if (!sendCommand(cmd, res))
331 return IPS_ALERT;
332
333 m_MotionQueryFailures = 0;
334 return IPS_BUSY;
335}
336
338 char cmd[WAF_RES] = {0};
339 char res[WAF_RES] = {0};
340 snprintf(cmd, sizeof(cmd), "FT%c%u#", (dir == FOCUS_INWARD) ? '-' : '+',
341 ticks);
342
343 if (!sendCommand(cmd, res))
344 return IPS_ALERT;
345
346 m_MotionQueryFailures = 0;
347 return IPS_BUSY;
348}
349
351 char res[WAF_RES] = {0};
352
353 if (!sendCommand("FS#", res))
354 return false;
355
356 m_MotionQueryFailures = 0;
357 return true;
358}
359
360bool AskarWAF::SyncFocuser(uint32_t ticks) {
361 char cmd[WAF_RES] = {0};
362 char res[WAF_RES] = {0};
363 snprintf(cmd, sizeof(cmd), "FY%u#", ticks);
364
365 return sendCommand(cmd, res);
366}
367
368bool AskarWAF::ReverseFocuser(bool enabled) {
369 char res[WAF_RES] = {0};
370
371 return sendCommand(enabled ? "FR1#" : "FR0#", res);
372}
373
374bool AskarWAF::SetFocuserBacklash(int32_t steps) {
375 char cmd[WAF_RES] = {0};
376 char res[WAF_RES] = {0};
377 snprintf(cmd, sizeof(cmd), "FB%d#", steps);
378
379 return sendCommand(cmd, res);
380}
381
383 if (!enabled)
384 return SetFocuserBacklash(0);
385
386 int32_t steps = static_cast<int32_t>(FocusBacklashNP[0].getValue());
387 return SetFocuserBacklash(steps > 0 ? steps : 1);
388}
389
391 char cmd[WAF_RES] = {0};
392 char res[WAF_RES] = {0};
393 snprintf(cmd, sizeof(cmd), "FM%u#", ticks);
394
395 // The firmware rejects FMn# (FE#) if the current position exceeds the new max
396 // travel.
397 if (!sendCommand(cmd, res))
398 return false;
399
400 SyncPresets(ticks);
401 return true;
402}
403
405 if (!isConnected())
406 return;
407
408 const bool positionOk = readPosition();
409 if (positionOk) {
410 if (static_cast<uint32_t>(FocusAbsPosNP[0].getValue()) != lastPosition) {
412 lastPosition = static_cast<uint32_t>(FocusAbsPosNP[0].getValue());
413 }
414 }
415
418 bool queryOk = false;
419 const bool moving = isMoving(&queryOk);
420
421 if (!queryOk) {
422 if (++m_MotionQueryFailures >= WAF_MOTION_QUERY_MAX_FAILURES) {
427 m_MotionQueryFailures = 0;
428 LOG_ERROR("Lost contact with Askar-WAF while waiting for motion to "
429 "complete.");
430 }
431 } else {
432 m_MotionQueryFailures = 0;
433
434 // Only clear BUSY once motion has stopped and we have a fresh position.
435 if (!moving && positionOk) {
440 LOG_INFO("Focuser reached requested position.");
441 }
442 }
443 }
444
446}
virtual bool SetFocuserMaxPosition(uint32_t ticks) override
SetFocuserMaxPosition Update focuser maximum position. It only updates the PresetNP property limits.
virtual bool Handshake() override
Handshake Try to communicate with the focuser and read its firmware version.
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
virtual bool SetFocuserBacklashEnabled(bool enabled) override
SetFocuserBacklashEnabled Enables or disables the focuser backlash compensation.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool SyncFocuser(uint32_t ticks) override
SyncFocuser Set current position to ticks without moving the focuser.
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
Definition askar_waf.cpp:89
const char * getDefaultName() override
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
Definition askar_waf.cpp:45
virtual bool ReverseFocuser(bool enabled) override
ReverseFocuser Reverse focuser motion direction.
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
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
INDI::PropertySwitch FocusReverseSP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusSyncNP
INDI::PropertyNumber FocusMaxPosNP
INDI::PropertySwitch FocusBacklashSP
INDI::PropertyNumber FocusBacklashNP
INDI::PropertyNumber FocusAbsPosNP
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 void SyncPresets(uint32_t ticks)
Synchronizes the range (min, max, step) of the preset number properties (PresetNP) based on the focus...
Connection::Serial * serialConnection
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
@ 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_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 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
#define getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2