Instrument Neutral Distributed Interface INDI 2.2.5
wanderer_rotator_base.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2025 Frank Wang & Jérémie Klein. All rights reserved.
3
4 WandererRotator Lite V1
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
26#include "indicom.h"
28#include <cmath>
29#include <cstring>
30#include <memory>
31#include <termios.h>
32#include <unistd.h>
33#include <stdio.h>
34#include <sstream>
35#include <iostream>
36
37
38
45{
46
48
50
52 // Calibrate
53 SetZeroSP[0].fill("Set_Zero", "Mechanical Zero", ISS_OFF);
54 SetZeroSP.fill(getDeviceName(), "Set_Zero", "Set Current As", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
55
56 // BACKLASH
57 BacklashNP[BACKLASH].fill( "BACKLASH", "Degree", "%.2f", 0, 3, 0.1, 0);
58 BacklashNP.fill(getDeviceName(), "BACKLASH", "Backlash", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
59
60 // ACCURACY
61 AccuracyNP[0].fill("ACCURACY", "Degree (0=off)", "%.2f", 0, 10, 0.1, 0);
62 AccuracyNP.fill(getDeviceName(), "ACCURACY", "Accuracy", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
64
66
67
68 return true;
69}
70
89
90bool WandererRotatorBase::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
91{
92
93 if (dev && !strcmp(dev, getDeviceName()))
94 {
95 if (SetZeroSP.isNameMatch(name))
96 {
99 GotoRotatorNP[0].setValue(0);
101 LOG_INFO("Virtual Mechanical Angle is set to zero.");
102 return true;
103 }
104 }
105 return Rotator::ISNewSwitch(dev, name, states, names, n);
106}
107
108bool WandererRotatorBase::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
109{
110 if (dev && !strcmp(dev, getDeviceName()))
111 {
112 // backlash
113 if (BacklashNP.isNameMatch(name))
114 {
115 bool rc1 = false;
116 BacklashNP.update(values, names, n);
118
119 char cmd[16];
120 snprintf(cmd, 16, "%d", (int)(backlash * 10 + 1600000));
121 rc1 = sendCommand(cmd);
122
124 if (BacklashNP.getState() == IPS_OK)
125 BacklashNP.update(values, names, n);
127 LOG_INFO("Backlash Set");
128 return true;
129 }
130
131 // accuracy
132 if (AccuracyNP.isNameMatch(name))
133 {
134 return updateProperty(AccuracyNP, values, names, n, [this, values]()
135 {
136 accuracy = values[0];
137 return true;
138 });
139 }
140
141 }
142 return Rotator::ISNewNumber(dev, name, values, names, n);
143}
144
146{
148 AccuracyNP.save(fp);
149 return true;
150}
151
153{
155 tcflush(PortFD, TCIOFLUSH);
156 int nbytes_read_name = 0, nbytes_written = 0, rc = -1;
157 char name[64] = {0};
158
159 if ((rc = tty_write_string(PortFD, "1500001", &nbytes_written)) != TTY_OK)
160 {
161 char errorMessage[MAXRBUF];
162 tty_error_msg(rc, errorMessage, MAXRBUF);
163 LOGF_ERROR("Serial write error: %s", errorMessage);
164 return false;
165 }
166
167 //Device Model//////////////////////////////////////////////////////////////////////////////////////////////////////
168 if ((rc = tty_read_section(PortFD, name, 'A', 3, &nbytes_read_name)) != TTY_OK)
169 {
170 tcflush(PortFD, TCIOFLUSH);
171 if ((rc = tty_write_string(PortFD, "1500001", &nbytes_written)) != TTY_OK)
172 {
173 char errorMessage[MAXRBUF];
174 tty_error_msg(rc, errorMessage, MAXRBUF);
175 LOGF_ERROR("Serial write error: %s", errorMessage);
176 return false;
177 }
178 if ((rc = tty_read_section(PortFD, name, 'A', 3, &nbytes_read_name)) != TTY_OK)
179 {
180 char errorMessage[MAXRBUF];
181 tty_error_msg(rc, errorMessage, MAXRBUF);
182 LOG_INFO("No data received, the device may not be WandererRotator, please check the serial port!");
183 LOGF_ERROR("Device read error: %s", errorMessage);
184 return false;
185 }
186 }
187 name[nbytes_read_name - 1] = '\0';
188 if(strcmp(name, getRotatorHandshakeName()) != 0)
189 {
190 LOGF_ERROR("The device is not %s", getDefaultName());
191 LOGF_INFO("The device is %s.", name);
192 return false;
193 }
194 // Frimware version/////////////////////////////////////////////////////////////////////////////////////////////
195 int nbytes_read_version = 0;
196 char version[64] = {0};
197 tty_read_section(PortFD, version, 'A', 5, &nbytes_read_version);
198
199 version[nbytes_read_version - 1] = '\0';
200 LOGF_INFO("Firmware Version:%s", version);
201 firmware = std::atoi(version);
203 {
204 LOG_ERROR("The firmware is outdated, please upgrade to the latest firmware!");
205 LOGF_ERROR("The current firmware is %d.", firmware);
206 return false;
207 }
208
209 // Angle//////////////////////////////////////////////////////////////////////////////////////////
210 char M_angle[64] = {0};
211 int nbytes_read_M_angle = 0;
212 tty_read_section(PortFD, M_angle, 'A', 5, &nbytes_read_M_angle);
213 M_angle[nbytes_read_M_angle - 1] = '\0';
214 M_angleread = std::strtod(M_angle, NULL);
215
216 if(std::fabs(M_angleread) > 400000)
217 {
218 rc = sendCommand("1500002");
219 LOG_WARN("Virtual Mechanical Angle is too large, it is now set to zero!");
220 rc = sendCommand("1500001");
221 rc = tty_read_section(PortFD, M_angle, 'A', 5, &nbytes_read_M_angle);
222 rc = tty_read_section(PortFD, M_angle, 'A', 5, &nbytes_read_M_angle);
223 rc = tty_read_section(PortFD, M_angle, 'A', 5, &nbytes_read_M_angle);
224 M_angle[nbytes_read_M_angle - 1] = '\0';
225 M_angleread = std::strtod(M_angle, NULL);
226 }
227 GotoRotatorNP[0].setValue(std::fabs(M_angleread / 1000));
228 //backlash/////////////////////////////////////////////////////////////////////
229 char M_backlash[64] = {0};
230 int nbytes_read_M_backlash = 0;
231 tty_read_section(PortFD, M_backlash, 'A', 5, &nbytes_read_M_backlash);
232 M_backlash[nbytes_read_M_backlash - 1] = '\0';
233 M_backlashread = std::strtod(M_backlash, NULL);
234
238 //reverse/////////////////////////////////////////////////////////////////////
239 char M_reverse[64] = {0};
240 int nbytes_read_M_reverse = 0;
241 tty_read_section(PortFD, M_reverse, 'A', 5, &nbytes_read_M_reverse);
242 M_reverse[nbytes_read_M_reverse - 1] = '\0';
243 M_reverseread = std::strtod(M_reverse, NULL);
244 if(M_reverseread == 0)
245 {
246 ReverseRotator(false);
247 }
248 else
249 {
250 ReverseRotator(true);
251 }
252
253 tcflush(PortFD, TCIOFLUSH);
254 return true;
255}
256
257
259{
260 if (accuracy > 0)
261 angle = std::round(angle / accuracy) * accuracy;
262
263 angle = angle - GotoRotatorNP[0].getValue();
264
265 // Gate on the actual step delta the firmware will see. Anything that
266 // rounds to zero steps is a no-op the firmware won't acknowledge, so
267 // return immediately instead of blocking in TimerHit ("not powered").
268 int steps = static_cast<int>(std::round(angle * getStepsPerDegree()));
269 if (steps == 0)
270 return IPS_OK;
271
272 char cmd[16];
273 int position = steps + 1000000;
274 positionhistory = angle;
275 snprintf(cmd, 16, "%d", position);
276 Move(cmd);
277
278 return IPS_BUSY;
279}
280
281
283{
284
285
287 {
288 haltcommand = true;
289 int nbytes_written = 0, rc = -1;
290 tcflush(PortFD, TCIOFLUSH);
291 if ((rc = tty_write_string(PortFD, "Stop", &nbytes_written)) != TTY_OK)
292 {
293 char errorMessage[MAXRBUF];
294 tty_error_msg(rc, errorMessage, MAXRBUF);
295 LOGF_ERROR("Serial write error: %s", errorMessage);
296 return false;
297 }
298 SetTimer(100);
299 }
300 return true;
301}
302
308{
309 if(GotoRotatorNP[0].getValue() != 0)
310 {
311 double angle = -1 * GotoRotatorNP[0].getValue();
312 positionhistory = angle;
313 char cmd[16];
314 int position = (int)(angle * getStepsPerDegree() + 1000000);
315 snprintf(cmd, 16, "%d", position);
317 Move(cmd);
318 LOG_INFO("Moving to zero...");
319 }
320 return IPS_OK;
321}
322
323
325{
326
327 if (enabled)
328 {
329 char cmd[16];
330 snprintf(cmd, 16, "%d", 1700001);
331 if (sendCommand(cmd) != true)
332 {
333 LOG_ERROR("Serial write error.");
334 return false;
335 }
336 ReverseState = true;
337 return true;
338 }
339 else
340 {
341 char cmd[16];
342 snprintf(cmd, 16, "%d", 1700000);
343 if (sendCommand(cmd) != true)
344 {
345 LOG_ERROR("Serial write error.");
346 return false;
347 }
348 ReverseState = false;
349 return true;
350 }
351 return false;
352}
353
354
355
357{
358
359 if (GotoRotatorNP.getState() == IPS_BUSY || haltcommand == true)
360 {
361
362 if(nowtime < estime && haltcommand == false)
363 {
364 GotoRotatorNP[0].setValue(GotoRotatorNP[0].getValue() + std::copysign(1.0, positionhistory));
366 nowtime = nowtime + 240;
367 SetTimer(240);
368 return;
369 }
370 else
371 {
372 int rc = -1;
373 estime = 0;
374 nowtime = 0;
375 char M_angle[64] = {0};
376 int nbytes_read_M_angle = 0;
377 rc = tty_read_section(PortFD, M_angle, 'A', 5, &nbytes_read_M_angle);
378 if(rc != TTY_OK)
379 {
380 LOG_ERROR("Rotator not powered!");
381 GotoRotatorNP[0].setValue(initangle);
383 haltcommand = false;
384 return;
385 }
386 tty_read_section(PortFD, M_angle, 'A', 5, &nbytes_read_M_angle);
387
388 M_angle[nbytes_read_M_angle - 1] = '\0';
389 M_angleread = std::strtod(M_angle, NULL);
390 GotoRotatorNP[0].setValue(std::fabs(M_angleread / 1000));
393 haltcommand = false;
394 }
395 }
396
397
398 SetTimer(2000);
399}
400
401
402
404{
405 initangle = GotoRotatorNP[0].getValue();
406 int nbytes_written = 0, rc = -1;
407 LOGF_DEBUG("CMD <%s>", cmd);
408 if ((rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK)
409 {
410 char errorMessage[MAXRBUF];
411 tty_error_msg(rc, errorMessage, MAXRBUF);
412 LOGF_ERROR("Serial write error: %s", errorMessage);
413 return false;
414 }
415 SetTimer(2000);
416 nowtime = 0;
417 estime = abs((std::atoi(cmd) - 1000000) / getStepsPerDegree() * 240);
418 return true;
419}
420
421
422bool WandererRotatorBase::sendCommand(std::string command)
423{
424 int nbytes_written = 0, rc = -1;
425 std::string command_termination = "\n";
426 LOGF_DEBUG("CMD: %s", command.c_str());
427 if ((rc = tty_write_string(PortFD, (command + command_termination).c_str(), &nbytes_written)) != TTY_OK)
428 {
429 char errorMessage[MAXRBUF];
430 tty_error_msg(rc, errorMessage, MAXRBUF);
431 LOGF_ERROR("Serial write error: %s", errorMessage);
432 return false;
433 }
434 return true;
435}
436
437
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
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)
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
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
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 SetCapability(uint32_t cap)
SetRotatorCapability sets the Rotator capabilities. All capabilities must be initialized.
INDI::PropertyNumber GotoRotatorNP
Connection::Serial * serialConnection
Definition indirotator.h:95
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
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
INDI::PropertyNumber BacklashNP
bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the reverse direction property in the configuration file
bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
bool Handshake() override
perform handshake with device to check communication
bool ReverseRotator(bool enabled) override
ReverseRotator Reverse the direction of the rotator. CW is usually the normal direction,...
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual const char * getRotatorHandshakeName()=0
bool AbortRotator() override
AbortRotator Abort all motion.
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
IPState MoveRotator(double angle) override
MoveRotator Go to specific angle.
INDI::PropertyNumber AccuracyNP
IPState HomeRotator() override
WandererRotatorBase::HomeRotator.
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual const char * getDefaultName() override=0
virtual int getMinimumCompatibleFirmwareVersion()=0
virtual int getStepsPerDegree()=0
bool Move(const char *cmd)
bool sendCommand(std::string command)
INDI::PropertySwitch SetZeroSP
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
@ 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_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:653
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
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 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
#define getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2