26#include <libnova/sidereal_time.h>
27#include <libnova/transform.h>
91 FirmwareTP[0].
fill(
"VERSION",
"PWI4 Version",
"");
95 MountGeometryTP[0].
fill(
"GEOMETRY",
"Mount Type",
"");
100 AltAzNP[AZ_VALUE].
fill(
"AZ",
"Azimuth (°)",
"%.4f", 0.0, 360.0, 0.0, 0.0);
101 AltAzNP[ALT_VALUE].
fill(
"ALT",
"Altitude (°)",
"%.4f", -90.0, 90.0, 0.0, 0.0);
106 Axis0TelemetryNP[TELEMETRY_RMS_ERROR].
fill(
107 "AXIS0_RMS",
"RMS Error (\")",
"%.3f", 0, 1000, 0, 0);
108 Axis0TelemetryNP[TELEMETRY_DIST_TO_TARGET].
fill(
109 "AXIS0_DIST",
"Dist to Target (\")",
"%.3f", -1e6, 1e6, 0, 0);
110 Axis0TelemetryNP[TELEMETRY_SERVO_ERROR].
fill(
111 "AXIS0_SERVO",
"Servo Error (\")",
"%.3f", -1e6, 1e6, 0, 0);
112 Axis0TelemetryNP[TELEMETRY_MOTOR_CURRENT].
fill(
113 "AXIS0_CURRENT",
"Motor Current (A)",
"%.3f", 0, 100, 0, 0);
114 Axis0TelemetryNP.
fill(
getDeviceName(),
"AXIS0_TELEMETRY",
"Axis 0 (AZ/RA) Telemetry",
118 Axis1TelemetryNP[TELEMETRY_RMS_ERROR].
fill(
119 "AXIS1_RMS",
"RMS Error (\")",
"%.3f", 0, 1000, 0, 0);
120 Axis1TelemetryNP[TELEMETRY_DIST_TO_TARGET].
fill(
121 "AXIS1_DIST",
"Dist to Target (\")",
"%.3f", -1e6, 1e6, 0, 0);
122 Axis1TelemetryNP[TELEMETRY_SERVO_ERROR].
fill(
123 "AXIS1_SERVO",
"Servo Error (\")",
"%.3f", -1e6, 1e6, 0, 0);
124 Axis1TelemetryNP[TELEMETRY_MOTOR_CURRENT].
fill(
125 "AXIS1_CURRENT",
"Motor Current (A)",
"%.3f", 0, 100, 0, 0);
126 Axis1TelemetryNP.
fill(
getDeviceName(),
"AXIS1_TELEMETRY",
"Axis 1 (ALT/Dec) Telemetry",
130 DistanceToSunNP[0].
fill(
"DIST_TO_SUN",
"Distance (°)",
"%.2f", 0, 180, 0, 0);
149 FindHomeSP[0].
fill(
"FIND_HOME",
"Find Home",
ISS_OFF);
156 ModelFilenameTP[0].
fill(
"MODEL_FILE",
"Active Model",
"");
161 ModelInfoNP[MODEL_POINTS_TOTAL].
fill(
"MODEL_TOTAL",
"Total Points",
"%.0f", 0, 10000, 1, 0);
162 ModelInfoNP[MODEL_POINTS_ENABLED].
fill(
"MODEL_ENABLED",
"Enabled Points",
"%.0f", 0, 10000, 1, 0);
163 ModelInfoNP[MODEL_RMS_ERROR].
fill(
"MODEL_RMS",
"RMS Error (\")",
"%.3f", 0, 10000, 0, 0);
168 ModelOperationSP[MODEL_SAVE_AS_DEFAULT].
fill(
"MODEL_SAVE_DEFAULT",
"Save as Default",
ISS_OFF);
169 ModelOperationSP[MODEL_CLEAR_POINTS].
fill(
"MODEL_CLEAR",
"Clear All Points",
ISS_OFF);
174 ModelLoadFilenameTP[0].
fill(
"MODEL_LOAD_FILE",
"Filename",
"DefaultModel.pxp");
179 ModelSaveFilenameTP[0].
fill(
"MODEL_SAVE_FILE",
"Filename",
"MyModel.pxp");
185 SlewTimeConstantNP[0].
fill(
"SLEW_TIME_CONSTANT",
"Time Constant (s)",
186 "%.2f", 0.1, 5.0, 0.1, 0.5);
187 SlewTimeConstantNP.
fill(
getDeviceName(),
"MOUNT_SLEW_CONSTANT",
"Slew Aggressiveness",
273 m_HttpClient->set_connection_timeout(5, 0);
274 m_HttpClient->set_read_timeout(10, 0);
279 LOG_ERROR(
"Cannot communicate with PWI4. Is PWI4 running and reachable?");
284 bool mountConnected = m_Status[
"mount.is_connected"].as<
bool>();
287 LOG_INFO(
"Mount not connected to PWI4. Sending /mount/connect …");
288 if (!dispatch(
"/mount/connect"))
290 LOG_ERROR(
"Failed to connect mount via PWI4.");
293 mountConnected = m_Status[
"mount.is_connected"].as<
bool>();
296 LOG_ERROR(
"Mount still not connected after /mount/connect.");
304 std::string version = m_Status[
"pwi4.version"].as<std::string>();
305 FirmwareTP[0].setText(version.c_str());
309 int geometry = m_Status[
"mount.geometry"].as<
int>();
310 const char *geomStr =
"Unknown";
317 geomStr =
"Equatorial Fork";
320 geomStr =
"German Equatorial";
323 MountGeometryTP[0].setText(geomStr);
327 SlewTimeConstantNP[0].setValue(m_Status[
"mount.slew_time_constant"].as<double>());
330 LOGF_INFO(
"Connected to PWI4 %s — mount geometry: %s", version.c_str(), geomStr);
337bool PlaneWave::getStatus()
339 return dispatch(
"/status");
351 double ra = m_Status[
"mount.ra_apparent_hours"].as<
double>();
352 double de = m_Status[
"mount.dec_apparent_degs"].as<
double>();
355 bool isSlewing = m_Status[
"mount.is_slewing"].as<
bool>();
356 bool isTracking = m_Status[
"mount.is_tracking"].as<
bool>();
364 if (!isSlewing && !isTracking)
396 double az = m_Status[
"mount.azimuth_degs"].as<
double>();
397 double alt = m_Status[
"mount.altitude_degs"].as<
double>();
398 AltAzNP[AZ_VALUE].setValue(az);
399 AltAzNP[ALT_VALUE].setValue(alt);
406 int geometry = m_Status[
"mount.geometry"].as<
int>();
409 double axis0Pos = m_Status[
"mount.axis0.position_degs"].as<
double>();
415 Axis0TelemetryNP[TELEMETRY_RMS_ERROR].setValue(
416 m_Status[
"mount.axis0.rms_error_arcsec"].as<double>());
417 Axis0TelemetryNP[TELEMETRY_DIST_TO_TARGET].setValue(
418 m_Status[
"mount.axis0.dist_to_target_arcsec"].as<double>());
419 Axis0TelemetryNP[TELEMETRY_SERVO_ERROR].setValue(
420 m_Status[
"mount.axis0.servo_error_arcsec"].as<double>());
421 Axis0TelemetryNP[TELEMETRY_MOTOR_CURRENT].setValue(
422 m_Status[
"mount.axis0.measured_current_amps"].as<double>());
424 Axis0TelemetryNP.
apply();
427 Axis1TelemetryNP[TELEMETRY_RMS_ERROR].setValue(
428 m_Status[
"mount.axis1.rms_error_arcsec"].as<double>());
429 Axis1TelemetryNP[TELEMETRY_DIST_TO_TARGET].setValue(
430 m_Status[
"mount.axis1.dist_to_target_arcsec"].as<double>());
431 Axis1TelemetryNP[TELEMETRY_SERVO_ERROR].setValue(
432 m_Status[
"mount.axis1.servo_error_arcsec"].as<double>());
433 Axis1TelemetryNP[TELEMETRY_MOTOR_CURRENT].setValue(
434 m_Status[
"mount.axis1.measured_current_amps"].as<double>());
436 Axis1TelemetryNP.
apply();
439 bool axis0Enabled = m_Status[
"mount.axis0.is_enabled"].as<
bool>();
440 bool axis1Enabled = m_Status[
"mount.axis1.is_enabled"].as<
bool>();
445 Axis0MotorSP.
apply();
450 Axis1MotorSP.
apply();
453 double sunDist = m_Status[
"mount.distance_to_sun_degs"].as<
double>();
454 DistanceToSunNP[0].setValue(sunDist);
457 DistanceToSunNP.
apply();
460 std::string modelFile = m_Status[
"mount.model.filename"].as<std::string>();
461 if (modelFile != std::string(ModelFilenameTP[0].
getText()))
463 ModelFilenameTP[0].setText(modelFile.c_str());
465 ModelFilenameTP.
apply();
468 ModelInfoNP[MODEL_POINTS_TOTAL].setValue(
469 m_Status[
"mount.model.num_points_total"].as<double>());
470 ModelInfoNP[MODEL_POINTS_ENABLED].setValue(
471 m_Status[
"mount.model.num_points_enabled"].as<double>());
472 ModelInfoNP[MODEL_RMS_ERROR].setValue(
473 m_Status[
"mount.model.rms_error_arcsec"].as<double>());
480 double pwi4Lat = m_Status[
"site.latitude_degs"].as<
double>();
481 double pwi4Lon = m_Status[
"site.longitude_degs"].as<
double>();
482 double pwi4Elv = m_Status[
"site.height_meters"].as<
double>();
501 std::ostringstream req;
502 req << std::fixed << std::setprecision(9)
503 <<
"/mount/goto_ra_dec_j2000?ra_hours=" <<
ra
504 <<
"&dec_degs=" <<
dec;
506 if (!dispatch(req.str()))
521 std::ostringstream req;
522 req << std::fixed << std::setprecision(9)
523 <<
"/mount/model/add_point?ra_j2000_hours=" <<
ra
524 <<
"&dec_j2000_degs=" <<
dec;
526 if (!dispatch(req.str()))
529 LOG_INFO(
"Sync: added a pointing-model calibration point at the current position.");
538 if (!dispatch(
"/mount/park"))
551 if (!enableAxis(0,
true))
553 LOG_ERROR(
"Failed to enable axis 0 motor.");
557 if (!enableAxis(1,
true))
559 LOG_ERROR(
"Failed to enable axis 1 motor.");
572 if (!dispatch(
"/mount/set_park_here"))
575 LOG_INFO(
"Park position set to current position.");
594 return dispatch(
"/mount/stop");
604 LOG_ERROR(
"Please unpark the mount before issuing any motion commands.");
608 double rate = getSlewRateArcsecPerSec();
612 std::ostringstream req;
613 req << std::fixed << std::setprecision(4);
616 req <<
"/mount/offset?dec_set_rate_arcsec_per_sec=" << rate;
618 req <<
"/mount/offset?dec_set_rate_arcsec_per_sec=0";
620 return dispatch(req.str());
630 LOG_ERROR(
"Please unpark the mount before issuing any motion commands.");
634 double rate = getSlewRateArcsecPerSec();
638 std::ostringstream req;
639 req << std::fixed << std::setprecision(4);
642 req <<
"/mount/offset?ra_set_rate_arcsec_per_sec=" << rate;
644 req <<
"/mount/offset?ra_set_rate_arcsec_per_sec=0";
646 return dispatch(req.str());
661 LOG_INFO(
"Note: site location is managed by PWI4. "
662 "INDI displays the location configured in PWI4.");
682 if (!dispatch(
"/mount/tracking_on"))
692 std::ostringstream req;
693 req << std::fixed << std::setprecision(6)
694 <<
"/mount/offset?ra_set_rate_arcsec_per_sec=" << dRA
695 <<
"&dec_set_rate_arcsec_per_sec=" << dDE;
697 return dispatch(req.str());
702 return dispatch(
"/mount/offset?ra_reset=0&dec_reset=0");
710 std::ostringstream req;
711 req << std::fixed << std::setprecision(6)
712 <<
"/mount/offset?ra_set_rate_arcsec_per_sec=" << raRate
713 <<
"&dec_set_rate_arcsec_per_sec=" << deRate;
715 return dispatch(req.str());
727 if (!dispatch(
"/mount/tracking_off"))
730 dispatch(
"/mount/offset?ra_reset=0&dec_reset=0");
740 SlewTimeConstantNP.
save(fp);
754 Axis0MotorSP.
update(states, names, n);
756 if (enableAxis(0, enable))
758 LOGF_INFO(
"Axis 0 motor %s.", enable ?
"enabled" :
"disabled");
763 LOG_ERROR(
"Failed to change axis 0 motor state.");
766 Axis0MotorSP.
apply();
773 Axis1MotorSP.
update(states, names, n);
775 if (enableAxis(1, enable))
777 LOGF_INFO(
"Axis 1 motor %s.", enable ?
"enabled" :
"disabled");
782 LOG_ERROR(
"Failed to change axis 1 motor state.");
785 Axis1MotorSP.
apply();
792 if (dispatch(
"/mount/find_home"))
794 LOG_INFO(
"Home finding sequence started.");
799 LOG_ERROR(
"Failed to start home finding.");
811 ModelOperationSP.
update(states, names, n);
814 if (ModelOperationSP[MODEL_SAVE_AS_DEFAULT].getState() ==
ISS_ON)
816 ok = dispatch(
"/mount/model/save_as_default");
818 LOG_INFO(
"Pointing model saved as default.");
820 LOG_ERROR(
"Failed to save pointing model as default.");
822 else if (ModelOperationSP[MODEL_CLEAR_POINTS].getState() ==
ISS_ON)
824 ok = dispatch(
"/mount/model/clear_points");
826 LOG_INFO(
"All pointing model calibration points cleared.");
828 LOG_ERROR(
"Failed to clear pointing model points.");
835 ModelOperationSP.
apply();
853 ModelLoadFilenameTP.
update(texts, names, n);
854 const char *filename = ModelLoadFilenameTP[0].
getText();
855 std::string req =
"/mount/model/load?filename=";
860 LOGF_INFO(
"Pointing model '%s' loaded.", filename);
865 LOGF_ERROR(
"Failed to load pointing model '%s'.", filename);
868 ModelLoadFilenameTP.
apply();
875 ModelSaveFilenameTP.
update(texts, names, n);
876 const char *filename = ModelSaveFilenameTP[0].
getText();
877 std::string req =
"/mount/model/save?filename=";
882 LOGF_INFO(
"Pointing model saved as '%s'.", filename);
887 LOGF_ERROR(
"Failed to save pointing model as '%s'.", filename);
890 ModelSaveFilenameTP.
apply();
908 SlewTimeConstantNP.
update(values, names, n);
909 double tc = SlewTimeConstantNP[0].getValue();
911 std::ostringstream req;
912 req << std::fixed << std::setprecision(3)
913 <<
"/mount/set_slew_time_constant?value=" << tc;
915 if (dispatch(req.str()))
917 LOGF_INFO(
"Slew time constant set to %.2f s.", tc);
922 LOG_ERROR(
"Failed to set slew time constant.");
925 SlewTimeConstantNP.
apply();
936bool PlaneWave::enableAxis(
int axis,
bool enable)
938 std::ostringstream req;
939 req << (enable ?
"/mount/enable?axis=" :
"/mount/disable?axis=") << axis;
940 return dispatch(req.str());
946double PlaneWave::getSlewRateArcsecPerSec()
const
949 if (idx >= 0 && idx < 4)
950 return SLEW_RATES[idx];
951 return SLEW_RATES[3];
961bool PlaneWave::dispatch(
const std::string &request)
965 LOG_ERROR(
"HTTP client not initialized. Is the mount connected?");
969 auto res = m_HttpClient->Get(request);
979 if (res->status != 200)
981 LOGF_ERROR(
"HTTP GET %s returned HTTP %d: %s",
982 request.c_str(), res->status,
983 res->body.empty() ?
"(no body)" : res->body.c_str());
987 if (res->body.empty())
989 LOGF_ERROR(
"HTTP GET %s returned an empty body.", request.c_str());
998 inif.
decode(
"[status]\n" + res->body);
1000 if (inif[
"status"].size() == 0)
1002 LOGF_ERROR(
"Parsed status for %s is empty.", request.c_str());
1006 m_Status = inif[
"status"];
1009 catch (
const std::exception &e)
1011 LOGF_ERROR(
"Failed to parse PWI4 response for %s: %s",
1012 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
INDI::PropertyText getText(const char *name) const
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.
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.
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)
int findOnSwitchIndex() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], 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)
INDI::PropertyViewText * getText() const
TelescopeStatus TrackState
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
void setTelescopeConnection(const uint8_t &value)
setTelescopeConnection Set telescope connection mode. Child class should call this in the constructor...
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
Connection::TCP * tcpConnection
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
@ TELESCOPE_HAS_TRACK_RATE
@ TELESCOPE_HAS_PIER_SIDE
@ TELESCOPE_HAS_TRACK_MODE
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
void setPierSide(TelescopePierSide side)
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
virtual bool UnPark() override
UnPark — re-enable both motors before clearing the parked state.
virtual bool Abort() override
Abort.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
virtual bool Sync(double ra, double dec) override
Sync — adds a pointing-model calibration point at the current position.
virtual bool Goto(double ra, double dec) override
Goto — J2000 RA/Dec (INDI convention)
virtual bool SetTrackRate(double raRate, double deRate) override
SetTrackRate — called when the user changes TrackRateNP.
virtual bool Park() override
Park.
virtual bool updateProperties() override
updateProperties
virtual bool SetDefaultPark() override
SetDefaultPark.
virtual bool Handshake() override
Handshake — build the persistent client and verify the mount is live.
virtual bool initProperties() override
initProperties
virtual const char * getDefaultName() override
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
MoveNS — jog in Dec using a continuous offset rate.
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
ISNewText.
virtual bool updateTime(ln_date *utc, double utc_offset) override
updateTime — PWI4 uses the host PC clock; nothing to push.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
ISNewNumber.
PlaneWave()
Constructor / Destructor.
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
MoveWE — jog in RA using a continuous offset rate.
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems
virtual bool ReadScopeStatus() override
ReadScopeStatus — called every polling interval.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
ISNewSwitch.
virtual bool SetCurrentPark() override
SetCurrentPark — tell PWI4 to store the current position as the park point.
void decode(std::istream &is)
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.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
INDI::PropertyNumber TrackRateNP
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch TrackModeSP
INDI::PropertyNumber LocationNP
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)
std::unique_ptr< PlaneWave > PlaneWave_mount(new PlaneWave())