59 return "PlaneWave Rotator";
74 VersionTP[0].
fill(
"VERSION",
"PWI4 Version",
"");
79 MotorStateSP[MOTOR_ENABLE].
fill(
"MOTOR_ENABLE",
"Enable",
ISS_OFF);
80 MotorStateSP[MOTOR_DISABLE].
fill(
"MOTOR_DISABLE",
"Disable",
ISS_OFF);
85 MechPositionNP[0].
fill(
"MECH_POSITION",
"Mechanical (°)",
"%.2f", 0.0, 360.0, 0.1, 0.0);
90 RotatorStatusLP[STATUS_CONNECTED].
fill(
"STATUS_CONNECTED",
"HW Connected",
IPS_IDLE);
91 RotatorStatusLP[STATUS_ENABLED].
fill(
"STATUS_ENABLED",
"Motor Enabled",
IPS_IDLE);
139 m_HttpClient->set_connection_timeout(5, 0);
140 m_HttpClient->set_read_timeout(10, 0);
145 LOG_ERROR(
"Cannot communicate with PWI4. Is PWI4 running and reachable?");
150 bool rotatorConnected = m_Status[
"rotator.is_connected"].as<
bool>();
151 if (!rotatorConnected)
153 LOG_ERROR(
"PWI4 reports rotator is not connected to hardware. "
154 "Please connect the rotator in PWI4 first.");
159 bool motorEnabled = m_Status[
"rotator.is_enabled"].as<
bool>();
162 LOG_INFO(
"Motor is disabled. Sending /rotator/enable …");
163 dispatch(
"/rotator/enable");
166 motorEnabled = m_Status[
"rotator.is_enabled"].as<
bool>();
170 std::string version = m_Status[
"pwi4.version"].as<std::string>();
171 VersionTP[0].setText(version.c_str());
185 double fieldAngle = m_Status[
"rotator.field_angle_degs"].as<
double>();
186 double mechAngle = m_Status[
"rotator.mech_position_degs"].as<
double>();
191 MechPositionNP[0].setValue(mechAngle);
194 m_LastFieldAngle = fieldAngle;
195 m_LastMechPosition = mechAngle;
197 LOGF_INFO(
"Connected to PWI4 %s. Field angle: %.2f°, Mech position: %.2f°",
198 version.c_str(), fieldAngle, mechAngle);
213 MotorStateSP.
update(states, names, n);
214 bool enableRequested = (MotorStateSP[MOTOR_ENABLE].
getState() ==
ISS_ON);
216 bool ok = dispatch(enableRequested ?
"/rotator/enable" :
"/rotator/disable");
219 LOGF_INFO(
"Rotator motor %s.", enableRequested ?
"enabled" :
"disabled");
222 RotatorStatusLP.
apply();
226 LOG_ERROR(
"Failed to change motor state.");
229 MotorStateSP.
apply();
251 double target = values[0];
253 std::ostringstream req;
254 req << std::fixed << std::setprecision(4) <<
"/rotator/goto_mech?degs=" << target;
256 if (dispatch(req.str()))
258 MechPositionNP[0].setValue(target);
263 LOGF_INFO(
"Moving rotator to mechanical position %.2f°.", target);
267 LOG_ERROR(
"Failed to send /rotator/goto_mech command.");
270 MechPositionNP.
apply();
289 double target = angle;
293 std::ostringstream req;
294 req << std::fixed << std::setprecision(4) <<
"/rotator/goto_field?degs=" << target;
296 if (!dispatch(req.str()))
298 LOG_ERROR(
"Failed to send /rotator/goto_field command.");
302 LOGF_INFO(
"Moving rotator to field angle %.2f°%s.", target,
313 bool ok = dispatch(
"/rotator/stop");
315 LOG_INFO(
"Rotator motion aborted.");
317 LOG_ERROR(
"Failed to abort rotator motion.");
327 LOGF_INFO(
"Rotator direction %s.", enabled ?
"reversed" :
"normal");
344 LOG_WARN(
"Failed to get status from PWI4.");
350 bool hwConnected = m_Status[
"rotator.is_connected"].as<
bool>();
351 bool motorEnabled = m_Status[
"rotator.is_enabled"].as<
bool>();
352 bool isMoving = m_Status[
"rotator.is_moving"].as<
bool>();
353 double fieldAngle = m_Status[
"rotator.field_angle_degs"].as<
double>();
354 double mechAngle = m_Status[
"rotator.mech_position_degs"].as<
double>();
359 if (RotatorStatusLP[STATUS_CONNECTED].getState() != connLight ||
360 RotatorStatusLP[STATUS_ENABLED].
getState() != enabledLight)
362 RotatorStatusLP[STATUS_CONNECTED].
setState(connLight);
363 RotatorStatusLP[STATUS_ENABLED].
setState(enabledLight);
364 RotatorStatusLP.
apply();
370 if (MotorStateSP[MOTOR_ENABLE].getState() != onState)
372 MotorStateSP[MOTOR_ENABLE].
setState(onState);
373 MotorStateSP[MOTOR_DISABLE].
setState(offState);
374 MotorStateSP.
apply();
378 bool fieldChanged = std::fabs(fieldAngle - m_LastFieldAngle) > POSITION_THRESHOLD;
388 LOG_INFO(
"Rotator reached target field angle.");
389 fieldChanged =
false;
391 else if (fieldChanged)
398 else if (fieldChanged)
405 m_LastFieldAngle = fieldAngle;
408 bool mechChanged = std::fabs(mechAngle - m_LastMechPosition) > POSITION_THRESHOLD;
414 MechPositionNP[0].setValue(mechAngle);
416 MechPositionNP.
apply();
419 else if (mechChanged)
421 MechPositionNP[0].setValue(mechAngle);
422 MechPositionNP.
apply();
425 else if (mechChanged)
427 MechPositionNP[0].setValue(mechAngle);
428 MechPositionNP.
apply();
432 m_LastMechPosition = mechAngle;
450bool PlaneWaveRotator::getStatus()
452 return dispatch(
"/status");
462bool PlaneWaveRotator::dispatch(
const std::string &request)
466 LOG_ERROR(
"HTTP client not initialized. Is the rotator connected?");
470 auto res = m_HttpClient->Get(request);
480 if (res->status != 200)
482 LOGF_ERROR(
"HTTP GET %s returned HTTP %d: %s",
483 request.c_str(), res->status,
484 res->body.empty() ?
"(no body)" : res->body.c_str());
488 if (res->body.empty())
490 LOGF_ERROR(
"HTTP GET %s returned an empty body.", request.c_str());
499 inif.
decode(
"[status]\n" + res->body);
501 if (inif[
"status"].size() == 0)
503 LOGF_ERROR(
"Parsed status for %s is empty.", request.c_str());
507 m_Status = inif[
"status"];
510 catch (
const std::exception &e)
512 LOGF_ERROR(
"Failed to parse PWI4 response for %s: %s",
513 request.c_str(), e.what());
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
virtual const char * host() const
virtual uint32_t port() 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.
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 isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPState state)
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)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process Rotator switch properties.
void SetCapability(uint32_t cap)
SetRotatorCapability sets the Rotator capabilities. All capabilities must be initialized.
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process Rotator number properties.
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....
void setRotatorConnection(const uint8_t &value)
setRotatorConnection Set Rotator connection mode. Child class should call this in the constructor bef...
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the reverse direction property in the configuration file
Connection::TCP * tcpConnection
The PlaneWaveRotator class provides an INDI driver for the PlaneWave Series 5 rotator,...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
ISNewNumber.
virtual bool AbortRotator() override
AbortRotator — RotatorInterface virtual.
virtual bool updateProperties() override
updateProperties
PlaneWaveRotator()
Constructor / Destructor.
virtual bool Handshake() override
Handshake — build the persistent HTTP client and verify connectivity.
virtual bool ReverseRotator(bool enabled) override
virtual void TimerHit() override
TimerHit — polling loop.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems
virtual ~PlaneWaveRotator()
virtual const char * getDefaultName() override
getDefaultName
virtual IPState MoveRotator(double angle) override
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
ISNewSwitch.
virtual bool initProperties() override
initProperties
void decode(std::istream &is)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Implementations for common driver routines.
#define LOGF_INFO(fmt,...)
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)
std::string to_string(Error error)