34#ifdef _USE_SYSTEM_JSONLIB
35#include <nlohmann/json.hpp>
126 LOG_INFO(
"Connection settings updated.");
136 LOG_INFO(
"Alpaca device number updated.");
165 return "Alpaca Dome";
179 m_ConfigHost = configHost;
181 m_ConfigPort = configPort;
246 LOG_INFO(
"Alpaca dome is ready for operation.");
253 LOG_INFO(
"Alpaca dome is disconnected.");
278 LOG_ERROR(
"Server address or port is not set.");
286 httpClient->set_connection_timeout(std::chrono::milliseconds(connectionTimeout));
287 httpClient->set_read_timeout(std::chrono::milliseconds(timeout));
288 httpClient->set_write_timeout(std::chrono::milliseconds(timeout));
291 bool connected =
false;
292 if (!alpacaGetBool(
"/connected", connected))
294 LOG_ERROR(
"Failed to connect to Alpaca dome. Please check server address and port.");
301 if (!alpacaConnect())
305 LOG_INFO(
"Successfully connected to Alpaca dome.");
308 alpacaGetString(
"/name", m_DeviceName);
309 alpacaGetString(
"/description", m_Description);
310 alpacaGetString(
"/driverinfo", m_DriverInfo);
311 alpacaGetString(
"/driverversion", m_DriverVersion);
312 alpacaGetInt(
"/interfaceversion", m_InterfaceVersion);
315 alpacaGetBool(
"/canfindhome", m_CanFindHome);
316 alpacaGetBool(
"/canpark", m_CanPark);
317 alpacaGetBool(
"/cansetaltitude", m_CanSetAltitude);
318 alpacaGetBool(
"/cansetazimuth", m_CanSetAzimuth);
319 alpacaGetBool(
"/cansetpark", m_CanSetPark);
320 alpacaGetBool(
"/cansetshutter", m_CanSetShutter);
321 alpacaGetBool(
"/canslave", m_CanSlave);
322 alpacaGetBool(
"/cansyncazimuth", m_CanSyncAzimuth);
330 if (m_CanSyncAzimuth)
337 bool slewing, parked;
338 if (alpacaGetBool(
"/slewing", slewing,
false) && alpacaGetBool(
"/atpark", parked,
true))
352 if (!alpacaDisconnect())
354 LOG_ERROR(
"Failed to set disconnected state on Alpaca dome.");
362 LOG_INFO(
"Disconnected from Alpaca dome.");
380 LOG_ERROR(
"Dome does not support azimuth slewing.");
394 LOG_ERROR(
"Dome does not support azimuth slewing.");
398 if (!alpacaDomeSlewToAzimuth(
range360(az)))
409 LOG_ERROR(
"Dome does not support azimuth slewing.");
437 LOG_ERROR(
"Dome does not support syncing.");
442 bool success = alpacaDomeSyncToAzimuth(targetAz);
446 LOGF_INFO(
"Dome successfully synchronized to azimuth angle %.2f.", targetAz);
451 LOGF_ERROR(
"Error: cannot synchronize dome to azimuth angle %.2f.", targetAz);
462 if (alpacaDomeAbortSlew())
468 LOG_WARN(
"Finding home operation is aborted.");
472 LOG_WARN(
"Calibrating operation is aborted.");
480 LOG_WARN(
"Shutter motion aborted!");
506 if (alpacaDomePark())
543 if (alpacaDomeOpenShutter())
556 LOG_WARN(
"Cannot close dome when mount is locking. See: Telescope parking policy, in options tab");
561 if (alpacaDomeCloseShutter())
580 LOG_ERROR(
"Dome does not support setting park position.");
584 if (alpacaDomeSetPark())
586 LOG_INFO(
"Dome park position successfully set to current azimuth");
590 LOG_ERROR(
"Error: cannot set park position to current azimuth");
607 LOG_ERROR(
"Dome does not support homing.");
611 if (alpacaDomeFindHome())
613 LOG_INFO(
"Dome is moving to home position...");
618 LOG_ERROR(
"Error: cannot move the dome to the home position");
626 LOG_ERROR(
"Calibration is not supported by standard basic Alpaca dome.");
634 return "/api/v1/dome/" + std::to_string(
static_cast<int>(
DeviceNumberNP[0].getValue())) + endpoint;
642 std::string form_data;
643 for (
auto & [key, value] : request.
items())
645 if (!form_data.empty()) form_data +=
"&";
647 if (value.is_string())
649 form_data += key +
"=" + value.get<std::string>();
651 else if (value.is_number_integer())
653 form_data += key +
"=" + std::to_string(value.get<
int>());
655 else if (value.is_number_float())
657 form_data += key +
"=" + std::to_string(value.get<
double>());
659 else if (value.is_boolean())
661 form_data += key +
"=" + (value.get<
bool>() ?
"true" :
"false");
666 if (!form_data.empty()) form_data +=
"&";
667 form_data +=
"ClientID=" + std::to_string(getpid());
682 if (force || m_ConfigHost.empty())
688 if (force || m_ConfigPort.empty())
697 return ++m_ClientTransactionID;
702void AlpacaDome::updateStatus()
712 AlapacaDomeState value;
713 if (alpacaGetDeviceState(value))
717 updateShutterStatus(value);
720 updateDomeStatus(value);
726void AlpacaDome::updateDomeStatus(AlapacaDomeState &state)
733 if (isHoming && state.atHome)
735 LOG_INFO(
"Dome has found home position.");
741 else if (isCalibrating)
743 LOG_INFO(
"Dome calibration complete.");
751 LOGF_INFO(
"Dome reached requested azimuth: %.3f Degrees", state.azimuth);
773void AlpacaDome::updateShutterStatus(AlapacaDomeState &state)
782 switch(state.shutterStatus)
795 LOG_INFO(
"Shutter is fully closed.");
833bool AlpacaDome::sendAlpacaGET(
const std::string &endpoint,
nlohmann::json &response)
837 LOG_ERROR(
"HTTP client not initialized.");
842 url +=
"?ClientID=" + std::to_string(getpid()) +
"&ClientTransactionID=" + std::to_string(
getTransactionId());
844 auto result = httpClient->Get(url.c_str());
848 LOGF_ERROR(
"HTTP GET failed for %s: %s", endpoint.c_str(),
853 if (result->status != 200)
855 LOGF_ERROR(
"HTTP GET %s returned status %d", endpoint.c_str(), result->status);
861 response = nlohmann::json::parse(result->body);
864 if (response.
contains(
"ErrorNumber") && response[
"ErrorNumber"].get<
int>() != 0)
866 LOGF_ERROR(
"Alpaca error in %s: %d - %s", endpoint.c_str(),
867 response[
"ErrorNumber"].get<
int>(),
868 response[
"ErrorMessage"].get<std::string>().c_str());
876 LOGF_ERROR(
"JSON parse error for %s: %s", endpoint.c_str(), e.
what());
887 LOG_ERROR(
"HTTP client not initialized.");
894 auto result = httpClient->Put(url, form_data,
"application/x-www-form-urlencoded");
898 LOGF_ERROR(
"HTTP PUT failed for %s: %s", endpoint.c_str(),
903 if (result->status != 200)
905 LOGF_ERROR(
"HTTP PUT %s returned status %d", endpoint.c_str(), result->status);
911 response = nlohmann::json::parse(result->body);
914 if (response.
contains(
"ErrorNumber") && response[
"ErrorNumber"].get<
int>() != 0)
922 LOGF_ERROR(
"Alpaca error in %s: %d - %s", endpoint.c_str(),
923 response[
"ErrorNumber"].get<
int>(),
924 response[
"ErrorMessage"].get<std::string>().c_str());
933 LOGF_ERROR(
"JSON parse error for %s: %s", endpoint.c_str(), e.
what());
940bool AlpacaDome::alpacaConnect()
945 if (m_InterfaceVersion >= 3)
947 if (!sendAlpacaPUT(
"/connect", {}, response))
950 bool connecting =
true;
951 int retryDelay = 100;
954 while (connecting && retryDelay < 4000)
956 if (!alpacaGetBool(
"/connecting", connecting))
960 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
967 if (!sendAlpacaPUT(
"/connected", {{
"Connected",
true}}, response))
970 bool connected =
false;
971 int retryDelay = 100;
974 while (!connected && retryDelay < 4000)
976 if (!alpacaGetBool(
"/connected", connected))
980 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
991bool AlpacaDome::alpacaDisconnect()
996 if (m_InterfaceVersion >= 3)
998 if (!sendAlpacaPUT(
"/disconnect", {}, response))
1003 if (!sendAlpacaPUT(
"/connected", {{
"Connected",
false}}, response))
1012bool AlpacaDome::alpacaGetBool(
const std::string &endpoint,
bool& value,
bool defaultValue)
1015 if (!sendAlpacaGET(endpoint, response))
1017 if (response.
contains(
"Value") && response[
"Value"].is_boolean())
1018 value = response[
"Value"].
get<
bool>();
1020 value = defaultValue;
1026bool AlpacaDome::alpacaGetInt(
const std::string &endpoint,
int &value,
int defaultValue)
1029 if (!sendAlpacaGET(endpoint, response))
1031 if (response.
contains(
"Value") && response[
"Value"].is_number_integer())
1032 value = response[
"Value"].
get<
int>();
1034 value = defaultValue;
1040bool AlpacaDome::alpacaGetDouble(
const std::string &endpoint,
double &value,
double defaultValue)
1043 if (!sendAlpacaGET(endpoint, response))
1045 if (response.
contains(
"Value") && response[
"Value"].is_number_float())
1046 value = response[
"Value"].
get<
double>();
1048 value = defaultValue;
1054bool AlpacaDome::alpacaGetString(
const std::string &endpoint, std::string &value,
const std::string& defaultValue)
1057 if (!sendAlpacaGET(endpoint, response))
1059 if (response.
contains(
"Value") && response[
"Value"].is_string())
1060 value = response[
"Value"].
get<std::string>();
1062 value = defaultValue;
1068bool AlpacaDome::alpacaGetDeviceState(AlapacaDomeState &value,
double defaultAz,
double defaultAlt)
1070 value.atPark = value.atHome = value.slewing =
false;
1071 value.azimuth = defaultAz;
1072 value.altitude = defaultAlt;
1073 value.shutterStatus = -1;
1076 if (m_InterfaceVersion >= 3)
1080 if (!sendAlpacaGET(
"/devicestate", response))
1083 if (!response.
contains(
"Value") || !response[
"Value"].is_array())
1085 LOG_ERROR(
"Error: an array of objects is expected in Value attribute of response");
1089 for (
auto &item : response[
"Value"])
1091 if (!item.contains(
"Name") || !item[
"Name"].is_string())
1093 LOG_ERROR(
"Error: missing string element Name in Value array item, as returned by /devicestate API method");
1096 if (!item.contains(
"Value"))
1098 LOG_ERROR(
"Error: missing element Value in Value array item, as returned by /devicestate API method");
1102 const std::string &key = item[
"Name"].get<std::string>();
1103 if (key ==
"Altitude")
1104 value.altitude = item[
"Value"].get<
double>();
1105 else if (key ==
"AtHome")
1106 value.atHome = item[
"Value"].get<
bool>();
1107 else if (key ==
"AtPark")
1108 value.atPark = item[
"Value"].get<
bool>();
1109 else if (key ==
"Azimuth")
1110 value.azimuth = item[
"Value"].get<
double>();
1111 else if (key ==
"ShutterStatus")
1112 value.shutterStatus = item[
"Value"].get<
int>();
1113 else if (key ==
"Slewing")
1114 value.slewing = item[
"Value"].get<
bool>();
1120 alpacaGetBool(
"/slewing", value.slewing,
false);
1122 alpacaGetBool(
"/athome", value.atHome,
false);
1124 alpacaGetBool(
"/atpark", value.atPark,
false);
1126 alpacaGetDouble(
"/azimuth", value.azimuth, defaultAz);
1128 alpacaGetDouble(
"/altitude", value.altitude, defaultAlt);
1130 alpacaGetInt(
"/shutterstatus", value.shutterStatus, -1);
1138bool AlpacaDome::alpacaDomeAbortSlew()
1141 return sendAlpacaPUT(
"/abortslew", {}, response);
1146bool AlpacaDome::alpacaDomeCloseShutter()
1149 return sendAlpacaPUT(
"/closeshutter", {}, response);
1154bool AlpacaDome::alpacaDomeFindHome()
1157 return sendAlpacaPUT(
"/findhome", {}, response);
1162bool AlpacaDome::alpacaDomeOpenShutter()
1165 return sendAlpacaPUT(
"/openshutter", {}, response);
1170bool AlpacaDome::alpacaDomePark()
1173 return sendAlpacaPUT(
"/park", {}, response);
1178bool AlpacaDome::alpacaDomeSetPark()
1181 return sendAlpacaPUT(
"/setpark", {}, response);
1186bool AlpacaDome::alpacaDomeSlewToAltitude(
double alt)
1190 return sendAlpacaPUT(
"/slewtoaltitude", request, response);
1195bool AlpacaDome::alpacaDomeSlewToAzimuth(
double az)
1199 return sendAlpacaPUT(
"/slewtoazimuth", request, response);
1204bool AlpacaDome::alpacaDomeSyncToAzimuth(
double az)
1208 return sendAlpacaPUT(
"/synctoazimuth", request, response);
std::unique_ptr< AlpacaDome > alpaca_dome(new AlpacaDome())
INDI::PropertySwitch OperationSP
virtual bool saveConfigItems(FILE *fp) override
Save all dome parameters in the configuration file. This overridden method adds the HTTP connection i...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool Abort() override
Abort all dome motion (spinning and shutter).
virtual IPState FindHome()
Request the dome for moving and finding its Home position. The method is asynchronous and the dome sh...
virtual bool SetCurrentPark() override
Set parking position of the dome with the current position.
virtual IPState Park() override
Park the dome.
virtual bool SetSpeed(double rpm) override
Set Dome speed. This does not initiate motion, it sets the speed for the next motion command....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual IPState MoveAbs(double az) override
Move the Dome to an absolute azimuth.
virtual bool Disconnect() override
Disconnect from the remote dome device.
virtual IPState MoveRel(double azDiff) override
Move the Dome to an relative position.
virtual void ISGetProperties(const char *dev) override
Define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual uint32_t getTransactionId()
INDI::PropertyText ServerAddressTP
Additional properties.
virtual IPState Move(DomeDirection dir, DomeMotionCommand operation) override
Move the Dome in a particular direction.
INDI::PropertyNumber DeviceNumberNP
virtual IPState ControlShutter(ShutterOperation operation) override
Open or Close the shutter.
virtual bool updateProperties() override
Called whenever there is a change in the CONNECTION status of the driver. This will enable the driver...
INDI::PropertyText InfoTP
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual std::string buildAlpacaFormData(const nlohmann::json &request)
INDI::PropertyNumber ConnectionSettingsNP
virtual bool initProperties() override
Initialize properties state and value for Alpaca dome device.
virtual const char * getDefaultName() override
virtual void setDefaultServerAddress(const char *host, const char *port, bool force=false)
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual IPState Calibrate()
Request the dome for moving in order to calibrate itself. The calibration is an asynchronous process ...
virtual bool Sync(double az) override
Set the dome current azimuth with the supplied azimuth position.
virtual bool Connect() override
Connect to the remote dome device. For Alpaca drivers, this method tries to connect to the device usi...
virtual IPState UnPark() override
UnPark the dome. The action of the Unpark command is dome specific, but it may include opening the sh...
virtual bool SetDefaultPark() override
Set parking position of the dome with the current position.
virtual std::string getAlpacaURL(const std::string &endpoint)
INDI::PropertyText getText(const char *name) const
bool isDeviceNameMatch(const char *otherName) const
Check that the device name matches the argument.
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
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 SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
void SetDomeCapability(uint32_t cap)
SetDomeCapability set the dome capabilities. All capabilities must be initialized.
void setDomeConnection(const uint8_t &value)
setDomeConnection Set Dome connection mode. Child class should call this in the constructor before Do...
INDI::PropertySwitch ShutterParkPolicySP
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
ShutterOperation
Shutter operation command.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
bool isLocked()
isLocked, is the dome currently locked?
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
INDI::PropertyNumber DomeAbsPosNP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Dome Presets in the configuration file
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
void setShutterState(const ShutterState &value)
void setDomeState(const DomeState &value)
void SetParkDataType(DomeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
INDI::PropertySwitch DomeShutterSP
ShutterState getShutterState() const
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
DomeState getDomeState() const
INDI::PropertyNumber DomeSyncNP
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)
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)
a class to store JSON values
iteration_proxy< iterator > items() noexcept
helper to access iterator member functions in range-based for
bool contains(const typename object_t::key_type &key) const
check the existence of an element in a JSON object
auto get() const noexcept(noexcept(std::declval< const basic_json_t & >().template get_impl< ValueType >(detail::priority_tag< 4 > {}))) -> decltype(std::declval< const basic_json_t & >().template get_impl< ValueType >(detail::priority_tag< 4 > {}))
get a (pointer) value (explicit)
general exception of the basic_json class
const char * what() const noexcept override
returns the explanatory string
const char * CONNECTION_TAB
CONNECTION_TAB Where all device connection settings (serial, usb, ethernet) are defined and controlle...
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 range360(double r)
range360 Limits an angle to be between 0-360 degrees.
const char * IUFindOnSwitchName(ISState *states, char *names[], int n)
Returns the name of the first ON switch it finds in the supplied arguments.
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
#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)