Instrument Neutral Distributed Interface INDI 2.2.5
pinefeat_cef.cpp
Go to the documentation of this file.
1/*
2 Pinefeat EF / EF-S Lens Controller – Canon® Lens Compatible
3 Copyright (C) 2025 Pinefeat LLP ([email protected])
4
5 Based on Moonlite focuser
6 Copyright (C) 2013-2019 Jasem Mutlaq ([email protected])
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21*/
22
23#include "pinefeat_cef.h"
24
26
27#include <cstring>
28#include <termios.h>
29#include <thread>
30
31#define ERR_NC(res) (strcmp(res, "nc") > 0 ? "lens is not attached" : res)
32
33static std::unique_ptr<PinefeatCEF> pinefeatCEF(new PinefeatCEF());
34
36{
37 setVersion(1, 1);
38
40
41 lastUpdate = std::chrono::steady_clock::now();
42}
43
45{
46 return "Pinefeat EF Lens Controller";
47}
48
50{
52
53 FocusSpeedNP[0].setMinMax(1, 4);
54 FocusSpeedNP[0].setStep(1);
55 FocusSpeedNP[0].setValue(1);
56
57 FocusMaxPosNP[0].setMinMax(0, 32767);
58 FocusMaxPosNP[0].setStep(1);
59 FocusMaxPosNP[0].setValue(0);
60
61 FocusRelPosNP[0].setMinMax(0, 32767);
62 FocusRelPosNP[0].setStep(1);
63 FocusRelPosNP[0].setValue(0);
64
65 FocusAbsPosNP[0].setMinMax(0, 32767);
66 FocusAbsPosNP[0].setStep(1);
67 FocusAbsPosNP[0].setValue(0);
68
69 CalibrateSP[0].fill("CALIBRATE", "Calibrate", ISS_OFF);
70 CalibrateSP.fill(m_defaultDevice->getDeviceName(), "CALIBRATE", "Calibrate", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60,
71 IPS_OK);
72
73 ApertureAbsNP[0].fill("APERTURE_ABSOLUTE", "f-stop", "%.f", 0.0, 327.67, 0.0, 0.0);
74 ApertureAbsNP.fill(getDeviceName(), "ABS_APERTURE", "Absolute Aperture", MAIN_CONTROL_TAB, IP_WO,
75 60, IPS_OK);
76
77 ApertureRelNP[0].fill("APERTURE_RELATIVE", "f-stop", "%.f", -327.68, 327.67, 0.0, 0.0);
78 ApertureRelNP.fill(getDeviceName(), "REL_APERTURE", "Relative Aperture", MAIN_CONTROL_TAB, IP_WO,
79 60, IPS_OK);
80
81 ApertureRangeTP[0].fill("APERTURE_RANGE", "f-stop", nullptr);
82 ApertureRangeTP.fill(getDeviceName(), "RANGE_APERTURE", "Aperture range", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
83
84 FocusDistanceTP[0].fill("FOCUS_DISTANCE", "meter", nullptr);
85 FocusDistanceTP.fill(getDeviceName(), "FOCUS_DISTANCE", "Focus Distance", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
86
87 FirmwareTP[0].fill("FIRMWARE_VERSION", "Version", "Unknown");
88 FirmwareTP.fill(getDeviceName(), "FIRMWARE_VERSION", "Firmware", INFO_TAB, IP_RO, 0, IPS_IDLE);
89
91
93
94 return true;
95}
96
98{
100
101 if (isConnected())
102 {
103 defineProperty(FirmwareTP);
104 defineProperty(FocusDistanceTP);
105 defineProperty(CalibrateSP);
106 defineProperty(ApertureRangeTP);
107 defineProperty(ApertureAbsNP);
108 defineProperty(ApertureRelNP);
109
110 int32_t pos, max;
111 std::string dist, aper;
112 if (readFocusPosition(pos)
113 && (max = pos, 1)
114 && readFocusMaxPosition(max)
115 && readFocusDistance(dist)
116 && readApertureRange(aper))
117 {
118 updateProperties(pos, max, dist, aper);
119 LOG_INFO("Parameters updated, the controller is ready for use.");
120 }
121 }
122 else
123 {
124 deleteProperty(FirmwareTP);
125 deleteProperty(FocusDistanceTP);
126 deleteProperty(CalibrateSP);
127 deleteProperty(ApertureRangeTP);
128 deleteProperty(ApertureAbsNP);
129 deleteProperty(ApertureRelNP);
130 }
131
132 return true;
133}
134
135void PinefeatCEF::updateProperties(const int32_t pos, const int32_t max, const std::string dist, const std::string aper)
136{
137 FocusAbsPosNP[0].setValue(pos);
140
142 {
145 }
146
148 {
149 FocusMaxPosNP[0].setValue(max);
152
153 double values[] = { FocusMaxPosNP[0].getValue() };
154 char *names[] = { (char*)FocusMaxPosNP[0].getName() };
155 ISNewNumber(getDeviceName(), FocusMaxPosNP.getName(), values, names, 1);
156 }
157
158 FocusDistanceTP[0].setText(dist);
159 FocusDistanceTP.apply();
160
161 ApertureRangeTP[0].setText(aper);
162 ApertureRangeTP.apply();
163}
164
166{
167 for (int i = 0; i < 3; i++)
168 {
169 if (readFirmwareVersion())
170 {
171 return true;
172 }
173
174 std::this_thread::sleep_for(std::chrono::milliseconds(getCurrentPollingPeriod()));
175 }
176
177 LOG_ERROR("Can't detect the controller, please ensure the device is powered and the port is correct.");
178 return false;
179}
180
181bool PinefeatCEF::readFirmwareVersion()
182{
183 char res[CEF_BUF] = {0};
184
185 if (!sendCommand("v\n", res))
186 return false;
187
188 int major, minor;
189 if (sscanf(res, "%d.%d", &major, &minor) == 2)
190 {
191 firmwareMinor = minor;
192 }
193
194 FirmwareTP[0].setText(res);
195 FirmwareTP.setState(IPS_OK);
196
197 LOGF_INFO("Detected firmware version %s.", res);
198
199 return true;
200}
201
202bool PinefeatCEF::readFocusPosition(int32_t &pos)
203{
204 char res[CEF_BUF] = {0};
205
206 if (!sendCommand("f\n", res))
207 return false;
208
209 int rc = sscanf(res, "%d", &pos);
210 if (rc <= 0)
211 {
212 LOGF_ERROR("Can't read focus position: %s.", ERR_NC(res));
213 return false;
214 }
215
216 return true;
217}
218
219bool PinefeatCEF::readFocusMaxPosition(int32_t &pos)
220{
221 if (firmwareMinor < 3)
222 {
223 return true;
224 }
225
226 char res[CEF_BUF] = {0};
227
228 if (!sendCommand("r\n", res))
229 return false;
230
231 int rc = sscanf(res, "%*d-%d", &pos);
232 if (rc <= 0)
233 {
234 LOGF_ERROR("Can't read max focus position: %s.", ERR_NC(res));
235 return false;
236 }
237
238 return true;
239}
240
241bool PinefeatCEF::readFocusDistance(std::string &result)
242{
243 char res[CEF_BUF] = {0};
244
245 if (!sendCommand("d\n", res))
246 return false;
247
248 result = res;
249 return true;
250}
251
252bool PinefeatCEF::readApertureRange(std::string &result)
253{
254 char res[CEF_BUF] = {0};
255
256 if (!sendCommand("a\n", res))
257 return false;
258
259 result = res;
260 return true;
261}
262
263bool PinefeatCEF::isNotMoving()
264{
265 char res[CEF_BUF] = {0};
266 return sendCommand("e\n", res) && strstr(res, "n");
267}
268
269bool PinefeatCEF::moveFocusAbs(uint32_t position)
270{
271 char cmd[CEF_BUF] = {0};
272 char res[CEF_BUF] = {0};
273 snprintf(cmd, CEF_BUF, "f%d\n", position);
274
275 if (!sendCommand(cmd, res))
276 return false;
277
278 if (!strstr(res, "ok"))
279 {
280 LOGF_ERROR("Can't focus: %s.", ERR_NC(res));
281 return false;
282 }
283
284 return true;
285}
286
287bool PinefeatCEF::moveFocusRel(FocusDirection dir, uint32_t offset)
288{
289 char cmd[CEF_BUF] = {0};
290 char res[CEF_BUF] = {0};
291 snprintf(cmd, CEF_BUF, "f%s%d\n", (dir == FOCUS_INWARD) ? "-" : "+", offset);
292
293 if (!sendCommand(cmd, res))
294 return false;
295
296 if (!strstr(res, "ok"))
297 {
298 LOGF_ERROR("Can't focus: %s.", ERR_NC(res));
299 return false;
300 }
301
302 return true;
303}
304
305bool PinefeatCEF::setSpeed(int speed)
306{
307 char cmd[CEF_BUF] = {0};
308 char res[CEF_BUF] = {0};
309 snprintf(cmd, CEF_BUF, "s%d\n", speed);
310
311 if (!sendCommand(cmd, res))
312 return false;
313
314 if (!strstr(res, "ok"))
315 {
316 LOGF_ERROR("Can't set speed: %s.", ERR_NC(res));
317 return false;
318 }
319
320 return true;
321}
322
323bool PinefeatCEF::setApertureAbs(double value)
324{
325 char cmd[CEF_BUF] = {0};
326 char res[CEF_BUF] = {0};
327 snprintf(cmd, CEF_BUF, "a%.6g\n", value);
328
329 if (!sendCommand(cmd, res))
330 return false;
331
332 if (!strstr(res, "ok"))
333 {
334 LOGF_ERROR("Can't set aperture: %s.", ERR_NC(res));
335 return false;
336 }
337
338 LOGF_INFO("Aperture is set to f/%.6g.", value);
339
340 return true;
341}
342
343bool PinefeatCEF::setApertureRel(double value)
344{
345 char cmd[CEF_BUF] = {0};
346 char res[CEF_BUF] = {0};
347 snprintf(cmd, CEF_BUF, "a%s%.6g\n", (value < 0) ? "" : "+", value);
348
349 if (!sendCommand(cmd, res))
350 return false;
351
352 if (!strstr(res, "ok"))
353 {
354 LOGF_ERROR("Can't set aperture: %s.", ERR_NC(res));
355 return false;
356 }
357
358 LOGF_INFO("Iris is %s by f/%.6g further.", (value > 0) ? "closed" : "opened", abs(value));
359
360 return true;
361}
362
363bool PinefeatCEF::calibrate()
364{
365 char res[CEF_BUF] = {0};
366
367 if (!sendCommand("c\n", res))
368 return false;
369
370 if (!strstr(res, "ok"))
371 {
372 LOGF_ERROR("Can't calibrate: %s.", ERR_NC(res));
373 return false;
374 }
375
376 return true;
377}
378
379bool PinefeatCEF::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
380{
381 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
382 {
383 if (CalibrateSP.isNameMatch(name))
384 {
385 CalibrateSP.reset();
386
387 if (calibrate())
388 {
389 // Delay further update until calibration starter
390 lastUpdate = std::chrono::steady_clock::now();
391
394
397
398 CalibrateSP.setState(IPS_OK);
399 }
400 else
401 CalibrateSP.setState(IPS_ALERT);
402
403 CalibrateSP.apply();
404 return true;
405 }
406 }
407
408 return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
409}
410
411bool PinefeatCEF::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
412{
413 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
414 {
415 if (ApertureAbsNP.isNameMatch(name))
416 {
417 ApertureAbsNP.update(values, names, n);
418
419 bool res = setApertureAbs(ApertureAbsNP[0].getValue());
420 if (res)
421 ApertureAbsNP.setState(IPS_OK);
422 else
423 ApertureAbsNP.setState(IPS_ALERT);
424
425 ApertureAbsNP.apply();
426 return res;
427 }
428
429 if (ApertureRelNP.isNameMatch(name))
430 {
431 ApertureRelNP.update(values, names, n);
432
433 bool res = setApertureRel(ApertureRelNP[0].getValue());
434 if (res)
435 ApertureRelNP.setState(IPS_OK);
436 else
437 ApertureRelNP.setState(IPS_ALERT);
438
439 ApertureRelNP.apply();
440 return res;
441 }
442 }
443
444 return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
445}
446
448{
449 return setSpeed(speed);
450}
451
453{
454 if (!moveFocusAbs(targetTicks))
455 return IPS_ALERT;
456
457 return IPS_BUSY;
458}
459
461{
462 if (!moveFocusRel(dir, ticks))
463 return IPS_ALERT;
464
465 return IPS_BUSY;
466}
467
469{
470 if (!isConnected())
471 return;
472
473 auto now = std::chrono::steady_clock::now();
474 auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate).count();
475
476 if (elapsed >= 1 && isNotMoving())
477 {
478 int32_t pos, max;
479 std::string dist, aper;
480 if (readFocusPosition(pos)
481 && (max = pos, 1)
482 && readFocusMaxPosition(max)
483 && readFocusDistance(dist)
484 && readApertureRange(aper))
485 {
486 updateProperties(pos, max, dist, aper);
487 }
488 }
489
491}
492
493bool PinefeatCEF::sendCommand(const char * cmd, char * res)
494{
495 int nbytes_written = 0, nbytes_read = 0, rc = -1;
496
497 tcflush(PortFD, TCIOFLUSH);
498
499 LOGF_DEBUG("CMD <%s>", cmd);
500
501 if ((rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK)
502 {
503 char errstr[MAXRBUF] = {0};
504 tty_error_msg(rc, errstr, MAXRBUF);
505 LOGF_ERROR("Serial write error: %s.", errstr);
506 return false;
507 }
508
509 if (res == nullptr)
510 {
511 tcdrain(PortFD);
512 return true;
513 }
514
515 if ((rc = tty_nread_section(PortFD, res, CEF_BUF, CEF_DEL, CEF_TIMEOUT, &nbytes_read)) != TTY_OK)
516 {
517 char errstr[MAXRBUF] = {0};
518 tty_error_msg(rc, errstr, MAXRBUF);
519 LOGF_ERROR("Serial read error: %s.", errstr);
520 return false;
521 }
522
523 LOGF_DEBUG("RES <%s>", res);
524
525 tcflush(PortFD, TCIOFLUSH);
526
527 return true;
528}
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
const char * getDeviceName() 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.
INDI::PropertyNumber FocusRelPosNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusSpeedNP
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...
Connection::Serial * serialConnection
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
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)
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)
const char * getDefaultName() override
IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
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...
bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
bool SetFocuserSpeed(int speed) override
SetFocuserSpeed Set Focuser speed.
bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
bool Handshake() override
Performs a device-specific handshake after a connection is established. This method should be overrid...
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...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
double max(void)
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
@ IP_WO
Definition indiapi.h:183
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_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
@ 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
#define ERR_NC(res)
uint8_t cmd[4]
Definition pwc-ioctl.h:2