diff --git a/src/atom/async/timer.cpp b/src/atom/async/timer.cpp index 85fe72e3..f35b56aa 100644 --- a/src/atom/async/timer.cpp +++ b/src/atom/async/timer.cpp @@ -123,4 +123,9 @@ auto Timer::getTaskCount() const -> size_t { std::unique_lock lock(m_mutex); return m_taskQueue.size(); } + +void Timer::wait() { + std::unique_lock lock(m_mutex); + m_cond.wait(lock, [&]() { return m_taskQueue.empty(); }); +} } // namespace atom::async diff --git a/src/atom/async/timer.hpp b/src/atom/async/timer.hpp index b0563b5a..7f494f32 100644 --- a/src/atom/async/timer.hpp +++ b/src/atom/async/timer.hpp @@ -141,6 +141,11 @@ class Timer { */ void stop(); + /** + * @brief Blocks the calling thread until all tasks are completed. + */ + void wait(); + /** * @brief Sets a callback function to be called when a task is executed. * diff --git a/src/atom/system/gpio.cpp b/src/atom/system/gpio.cpp new file mode 100644 index 00000000..3dc196d4 --- /dev/null +++ b/src/atom/system/gpio.cpp @@ -0,0 +1,127 @@ +#include "gpio.hpp" + +#include +#include +#include +#include +#include + +#include "atom/error/exception.hpp" +#include "atom/log/loguru.hpp" + +#define GPIO_EXPORT "/sys/class/gpio/export" +#define GPIO_PATH "/sys/class/gpio" + +namespace atom::system { +class GPIO::Impl { +public: + explicit Impl(std::string pin) : pin_(std::move(pin)) { + exportGPIO(); + setGPIODirection("out"); + } + + ~Impl() { + try { + setGPIODirection("in"); + } catch (...) { + // Suppress all exceptions + } + } + + void setValue(bool value) { setGPIOValue(value ? "1" : "0"); } + + bool getValue() { return readGPIOValue(); } + + void setDirection(const std::string& direction) { + setGPIODirection(direction); + } + + static void notifyOnChange(const std::string& pin, + const std::function& callback) { + std::thread([pin, callback]() { + std::string path = + std::string(GPIO_PATH) + "/gpio" + pin + "/value"; + int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) { + LOG_F(ERROR, "Failed to open gpio value for reading"); + return; + } + + char lastValue = '0'; + while (true) { + char value[3] = {0}; + if (read(fd, value, sizeof(value) - 1) > 0) { + if (value[0] != lastValue) { + lastValue = value[0]; + callback(value[0] == '1'); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + lseek(fd, 0, SEEK_SET); + } + close(fd); + }).detach(); + } + +private: + std::string pin_; + + void exportGPIO() { executeGPIOCommand(GPIO_EXPORT, pin_); } + + void setGPIODirection(const std::string& direction) { + std::string path = + std::string(GPIO_PATH) + "/gpio" + pin_ + "/direction"; + executeGPIOCommand(path, direction); + } + + void setGPIOValue(const std::string& value) { + std::string path = std::string(GPIO_PATH) + "/gpio" + pin_ + "/value"; + executeGPIOCommand(path, value); + } + + auto readGPIOValue() -> bool { + std::string path = std::string(GPIO_PATH) + "/gpio" + pin_ + "/value"; + char value[3] = {0}; + int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) { + THROW_RUNTIME_ERROR("Failed to open gpio value for reading"); + } + ssize_t bytes = read(fd, value, sizeof(value) - 1); + close(fd); + if (bytes < 0) { + THROW_RUNTIME_ERROR("Failed to read gpio value"); + } + return value[0] == '1'; + } + + static void executeGPIOCommand(const std::string& path, + const std::string& command) { + int fd = open(path.c_str(), O_WRONLY); + if (fd < 0) { + THROW_RUNTIME_ERROR("Failed to open gpio path: " + path); + } + ssize_t bytes = write(fd, command.c_str(), command.length()); + close(fd); + if (bytes != static_cast(command.length())) { + THROW_RUNTIME_ERROR("Failed to write to gpio path: " + path); + } + } +}; + +GPIO::GPIO(const std::string& pin) : impl_(std::make_unique(pin)) {} + +GPIO::~GPIO() = default; + +void GPIO::setValue(bool value) { impl_->setValue(value); } + +bool GPIO::getValue() { return impl_->getValue(); } + +void GPIO::setDirection(const std::string& direction) { + impl_->setDirection(direction); +} + +void GPIO::notifyOnChange(const std::string& pin, + std::function callback) { + Impl::notifyOnChange(pin, std::move(callback)); +} +} // namespace atom::system diff --git a/src/atom/system/gpio.hpp b/src/atom/system/gpio.hpp new file mode 100644 index 00000000..5f3305b6 --- /dev/null +++ b/src/atom/system/gpio.hpp @@ -0,0 +1,26 @@ +#ifndef ATOM_SYSTEM_GPIO_HPP +#define ATOM_SYSTEM_GPIO_HPP + +#include +#include +#include + +namespace atom::system { +class GPIO { +public: + GPIO(const std::string& pin); + ~GPIO(); + + void setValue(bool value); + bool getValue(); + void setDirection(const std::string& direction); + static void notifyOnChange(const std::string& pin, + std::function callback); + +private: + class Impl; + std::unique_ptr impl_; +}; +} // namespace atom::system + +#endif // ATOM_SYSTEM_GPIO_HPP \ No newline at end of file diff --git a/src/atom/utils/print.hpp b/src/atom/utils/print.hpp index c53739a9..80666417 100644 --- a/src/atom/utils/print.hpp +++ b/src/atom/utils/print.hpp @@ -256,13 +256,12 @@ class FormatLiteral { std::make_format_args(std::forward(args)...)); } }; +} // namespace atom::utils constexpr auto operator""_fmt(const char* str, std::size_t len) { - return FormatLiteral(std::string_view(str, len)); + return atom::utils::FormatLiteral(std::string_view(str, len)); } -} // namespace atom::utils - #if __cplusplus >= 202302L namespace std { diff --git a/src/client/indi/camera.cpp b/src/client/indi/camera.cpp index 66531833..63102cb2 100644 --- a/src/client/indi/camera.cpp +++ b/src/client/indi/camera.cpp @@ -12,11 +12,11 @@ #include "atom/components/registry.hpp" #include "atom/error/exception.hpp" #include "atom/log/loguru.hpp" +#include "atom/macro.hpp" #include "components/component.hpp" #include "device/template/camera.hpp" #include "function/conversion.hpp" #include "function/type_info.hpp" -#include "atom/macro.hpp" INDICamera::INDICamera(std::string deviceName) : AtomCamera(name_), name_(std::move(deviceName)) {} @@ -413,6 +413,95 @@ auto INDICamera::abortExposure() -> bool { return true; } +auto INDICamera::getExposureStatus() -> bool { + INDI::PropertySwitch ccdExposure = device_.getProperty("CCD_EXPOSURE"); + if (!ccdExposure.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_EXPOSURE property..."); + return false; + } + if (ccdExposure[0].getState() == ISS_ON) { + LOG_F(INFO, "Exposure is in progress..."); + return true; + } + LOG_F(INFO, "Exposure is not in progress..."); + return false; +} + +auto INDICamera::getExposureResult() -> bool { + /* + TODO: Implement getExposureResult + INDI::PropertySwitch ccdExposure = device_.getProperty("CCD_EXPOSURE"); + if (!ccdExposure.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_EXPOSURE property..."); + return false; + } + if (ccdExposure[0].getState() == ISS_ON) { + LOG_F(INFO, "Exposure is in progress..."); + return false; + } + LOG_F(INFO, "Exposure is not in progress..."); + */ + return true; +} + +auto INDICamera::saveExposureResult() -> bool { + /* + TODO: Implement saveExposureResult + */ + return true; +} + +// TODO: Check these functions for correctness +auto INDICamera::startVideo() -> bool { + INDI::PropertySwitch ccdVideo = device_.getProperty("CCD_VIDEO_STREAM"); + if (!ccdVideo.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_VIDEO_STREAM property..."); + return false; + } + ccdVideo[0].setState(ISS_ON); + sendNewProperty(ccdVideo); + return true; +} + +auto INDICamera::stopVideo() -> bool { + INDI::PropertySwitch ccdVideo = device_.getProperty("CCD_VIDEO_STREAM"); + if (!ccdVideo.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_VIDEO_STREAM property..."); + return false; + } + ccdVideo[0].setState(ISS_OFF); + sendNewProperty(ccdVideo); + return true; +} + +auto INDICamera::getVideoStatus() -> bool { + INDI::PropertySwitch ccdVideo = device_.getProperty("CCD_VIDEO_STREAM"); + if (!ccdVideo.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_VIDEO_STREAM property..."); + return false; + } + if (ccdVideo[0].getState() == ISS_ON) { + LOG_F(INFO, "Video is in progress..."); + return true; + } + LOG_F(INFO, "Video is not in progress..."); + return false; +} + +auto INDICamera::getVideoResult() -> bool { + /* + TODO: Implement getVideoResult + */ + return true; +} + +auto INDICamera::saveVideoResult() -> bool { + /* + TODO: Implement saveVideoResult + */ + return true; +} + auto INDICamera::startCooling() -> bool { return setCooling(true); } auto INDICamera::stopCooling() -> bool { return setCooling(false); } @@ -432,6 +521,36 @@ auto INDICamera::setCooling(bool enable) -> bool { return true; } +// TODO: Check this functions for correctness +auto INDICamera::getCoolingStatus() -> bool { + INDI::PropertySwitch ccdCooler = device_.getProperty("CCD_COOLER"); + if (!ccdCooler.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_COOLER property..."); + return false; + } + if (ccdCooler[0].getState() == ISS_ON) { + LOG_F(INFO, "Cooler is ON"); + return true; + } + LOG_F(INFO, "Cooler is OFF"); + return false; +} + +// TODO: Check this functions for correctness +auto INDICamera::isCoolingAvailable() -> bool { + INDI::PropertySwitch ccdCooler = device_.getProperty("CCD_COOLER"); + if (!ccdCooler.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_COOLER property..."); + return false; + } + if (ccdCooler[0].getState() == ISS_ON) { + LOG_F(INFO, "Cooler is available"); + return true; + } + LOG_F(INFO, "Cooler is not available"); + return false; +} + auto INDICamera::getTemperature() -> std::optional { INDI::PropertyNumber ccdTemperature = device_.getProperty("CCD_TEMPERATURE"); @@ -466,6 +585,32 @@ auto INDICamera::setTemperature(const double &value) -> bool { return true; } +// TODO: Check this functions for correctness +auto INDICamera::getCoolingPower() -> bool { + INDI::PropertyNumber ccdCoolerPower = + device_.getProperty("CCD_COOLER_POWER"); + if (!ccdCoolerPower.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_COOLER_POWER property..."); + return false; + } + LOG_F(INFO, "Cooling power: {}", ccdCoolerPower[0].getValue()); + return true; +} + +// TODO: Check this functions for correctness +auto INDICamera::setCoolingPower(const double &value) -> bool { + INDI::PropertyNumber ccdCoolerPower = + device_.getProperty("CCD_COOLER_POWER"); + if (!ccdCoolerPower.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_COOLER_POWER property..."); + return false; + } + LOG_F(INFO, "Setting cooling power to {}...", value); + ccdCoolerPower[0].setValue(value); + sendNewProperty(ccdCoolerPower); + return true; +} + auto INDICamera::getCameraFrameInfo() -> std::optional> { INDI::PropertyNumber ccdFrameInfo = device_.getProperty("CCD_FRAME"); @@ -544,6 +689,17 @@ auto INDICamera::setGain(const int &value) -> bool { return true; } +// TODO: Check this functions for correctness +auto INDICamera::isGainAvailable() -> bool { + INDI::PropertyNumber ccdGain = device_.getProperty("CCD_GAIN"); + + if (!ccdGain.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_GAIN property..."); + return false; + } + return true; +} + auto INDICamera::getOffset() -> std::optional { INDI::PropertyNumber ccdOffset = device_.getProperty("CCD_OFFSET"); @@ -571,6 +727,140 @@ auto INDICamera::setOffset(const int &value) -> bool { return true; } +// TODO: Check this functions for correctness +auto INDICamera::isOffsetAvailable() -> bool { + INDI::PropertyNumber ccdOffset = device_.getProperty("CCD_OFFSET"); + + if (!ccdOffset.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_OFFSET property..."); + return false; + } + return true; +} + +auto INDICamera::getISO() -> bool { + /* + TODO: Implement getISO + */ + return true; +} + +auto INDICamera::setISO(const int &iso) -> bool { + /* + TODO: Implement setISO + */ + return true; +} + +auto INDICamera::isISOAvailable() -> bool { + /* + TODO: Implement isISOAvailable + */ + return true; +} + +// TODO: Check this functions for correctness +auto INDICamera::getFrame() -> std::optional> { + INDI::PropertyNumber ccdFrame = device_.getProperty("CCD_FRAME"); + + if (!ccdFrame.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_FRAME property..."); + return std::nullopt; + } + + frameX_ = ccdFrame[0].getValue(); + frameY_ = ccdFrame[1].getValue(); + frameWidth_ = ccdFrame[2].getValue(); + frameHeight_ = ccdFrame[3].getValue(); + LOG_F(INFO, "Current frame: X: {}, Y: {}, WIDTH: {}, HEIGHT: {}", frameX_, + frameY_, frameWidth_, frameHeight_); + return std::make_pair(frameWidth_, frameHeight_); +} + +// TODO: Check this functions for correctness +auto INDICamera::setFrame(const int &x, const int &y, const int &w, + const int &h) -> bool { + INDI::PropertyNumber ccdFrame = device_.getProperty("CCD_FRAME"); + + if (!ccdFrame.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_FRAME property..."); + return false; + } + LOG_F(INFO, "Setting frame to X: {}, Y: {}, WIDTH: {}, HEIGHT: {}", x, y, w, + h); + ccdFrame[0].setValue(x); + ccdFrame[1].setValue(y); + ccdFrame[2].setValue(w); + ccdFrame[3].setValue(h); + sendNewProperty(ccdFrame); + return true; +} + +// TODO: Check this functions for correctness +auto INDICamera::isFrameSettingAvailable() -> bool { + INDI::PropertyNumber ccdFrame = device_.getProperty("CCD_FRAME"); + + if (!ccdFrame.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_FRAME property..."); + return false; + } + return true; +} + +// TODO: Check this functions for correctness +auto INDICamera::getFrameType() -> bool { + INDI::PropertySwitch ccdFrameType = device_.getProperty("CCD_FRAME_TYPE"); + + if (!ccdFrameType.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_FRAME_TYPE property..."); + return false; + } + + if (ccdFrameType[0].getState() == ISS_ON) { + LOG_F(INFO, "Frame type: Light"); + return "Light"; + } else if (ccdFrameType[1].getState() == ISS_ON) { + LOG_F(INFO, "Frame type: Bias"); + return "Bias"; + } else if (ccdFrameType[2].getState() == ISS_ON) { + LOG_F(INFO, "Frame type: Dark"); + return "Dark"; + } else if (ccdFrameType[3].getState() == ISS_ON) { + LOG_F(INFO, "Frame type: Flat"); + return "Flat"; + } else { + LOG_F(ERROR, "Frame type: Unknown"); + return "Unknown"; + } +} + +// TODO: Check this functions for correctness +auto INDICamera::setFrameType(FrameType type) -> bool { + INDI::PropertySwitch ccdFrameType = device_.getProperty("CCD_FRAME_TYPE"); + + if (!ccdFrameType.isValid()) { + LOG_F(ERROR, "Error: unable to find CCD_FRAME_TYPE property..."); + return false; + } + + sendNewProperty(ccdFrameType); + return true; +} + +auto INDICamera::getUploadMode() -> bool { + /* + TODO: Implement getUploadMode + */ + return true; +} + +auto INDICamera::setUploadMode(UploadMode mode) -> bool { + /* + TODO: Implement setUploadMode + */ + return true; +} + auto INDICamera::getBinning() -> std::optional> { INDI::PropertyNumber ccdBinning = device_.getProperty("CCD_BINNING"); diff --git a/src/client/indi/camera.hpp b/src/client/indi/camera.hpp index 6f2ce04e..8200530c 100644 --- a/src/client/indi/camera.hpp +++ b/src/client/indi/camera.hpp @@ -27,9 +27,9 @@ class INDICamera : public INDI::BaseClient, public AtomCamera { explicit INDICamera(std::string name); ~INDICamera() override = default; - auto initialize() -> bool override = 0; + auto initialize() -> bool override; - auto destroy() -> bool override = 0; + auto destroy() -> bool override; auto connect(const std::string &deviceName, int timeout, int maxRetry) -> bool override; @@ -48,21 +48,55 @@ class INDICamera : public INDI::BaseClient, public AtomCamera { auto startExposure(const double &exposure) -> bool override; auto abortExposure() -> bool override; + auto getExposureStatus() -> bool override; + auto getExposureResult() -> bool override; + auto saveExposureResult() -> bool override; + + auto startVideo() -> bool override; + auto stopVideo() -> bool override; + auto getVideoResult() -> bool override; + auto getVideoStatus() -> bool override; + auto saveVideoResult() -> bool override; auto startCooling() -> bool override; auto stopCooling() -> bool override; + auto getCoolingStatus() -> bool override; + auto isCoolingAvailable() -> bool override; auto setTemperature(const double &value) -> bool override; auto getTemperature() -> std::optional override; + auto getCoolingPower() -> bool override; + auto setCoolingPower(const double &value) -> bool override; + auto getCameraFrameInfo() -> std::optional>; auto setCameraFrameInfo(int x, int y, int width, int height) -> bool; auto resetCameraFrameInfo() -> bool; auto getGain() -> std::optional override; auto setGain(const int &value) -> bool override; + auto isGainAvailable() -> bool override; + auto getOffset() -> std::optional override; auto setOffset(const int &value) -> bool override; + auto isOffsetAvailable() -> bool override; + + auto getISO() -> bool override; + auto setISO(const int &iso) -> bool override; + auto isISOAvailable() -> bool override; + + auto getFrame() -> std::optional> override; + auto setFrame(const int &x, const int &y, const int &w, + const int &h) -> bool override; + auto isFrameSettingAvailable() -> bool override; + + auto getFrameType() -> bool override; + + auto setFrameType(FrameType type) -> bool override; + + auto getUploadMode() -> bool override; + + auto setUploadMode(UploadMode mode) -> bool override; auto setBinning(const int &hor, const int &ver) -> bool override; auto getBinning() -> std::optional> override; diff --git a/src/config/configor.cpp b/src/config/configor.cpp index 42cf2a02..8d052e72 100644 --- a/src/config/configor.cpp +++ b/src/config/configor.cpp @@ -343,6 +343,39 @@ auto ConfigManager::setValue(const std::string& key_path, return false; } +auto ConfigManager::setValue(const std::string& key_path, + json&& value) -> bool { + std::unique_lock lock(m_impl_->rwMutex); + + // Check if the key_path is "/" and set the root value directly + if (key_path == "/") { + m_impl_->config = std::move(value); + LOG_F(INFO, "Set root config: {}", m_impl_->config.dump()); + return true; + } + + json* p = &m_impl_->config; + auto keys = key_path | std::views::split('/'); + + for (auto it = keys.begin(); it != keys.end(); ++it) { + std::string keyStr = std::string((*it).begin(), (*it).end()); + LOG_F(INFO, "Set config: {}", keyStr); + + if (std::next(it) == keys.end()) { // If this is the last key + (*p)[keyStr] = std::move(value); + LOG_F(INFO, "Final config: {}", m_impl_->config.dump()); + return true; + } + + if (!p->contains(keyStr) || !(*p)[keyStr].is_object()) { + (*p)[keyStr] = json::object(); + } + p = &(*p)[keyStr]; + LOG_F(INFO, "Current config: {}", p->dump()); + } + return false; +} + auto ConfigManager::appendValue(const std::string& key_path, const json& value) -> bool { std::unique_lock lock(m_impl_->rwMutex); diff --git a/src/config/configor.hpp b/src/config/configor.hpp index 3c5c290b..b385ed35 100644 --- a/src/config/configor.hpp +++ b/src/config/configor.hpp @@ -162,6 +162,13 @@ class ConfigManager { */ auto setValue(const std::string& key_path, const json& value) -> bool; + /** + * @brief Sets the value for the specified key path. + * @param key_path The path to set the configuration value. + * @param value The JSON value to set. + * @return bool True if the value was successfully set, false otherwise. + */ + auto setValue(const std::string& key_path, json&& value) -> bool; /** * @brief Appends a value to an array at the specified key path. * @param key_path The path to the array. diff --git a/src/device/basic.hpp b/src/device/basic.hpp new file mode 100644 index 00000000..d6e79201 --- /dev/null +++ b/src/device/basic.hpp @@ -0,0 +1,136 @@ +#ifndef LITHIUM_DEVICE_BASIC_HPP +#define LITHIUM_DEVICE_BASIC_HPP + +#include +#include +#include + +#include "atom/macro.hpp" +#include "atom/type/json.hpp" + +class AtomDriver; + +namespace lithium::device { + +struct Device { + std::string label; + std::string manufacturer; + std::string driverName; + std::string version; +} ATOM_ALIGNAS(128); + +struct DevGroup { + std::string groupName; + std::vector devices; +} ATOM_ALIGNAS(64); + +struct DriversList { + std::vector devGroups; + int selectedGroup = + -1; // Fixed typo: changed 'selectedGrounp' to 'selectedGroup' +} ATOM_ALIGNAS(32); + +struct SystemDevice { + std::string description; + int deviceIndiGroup; + std::string deviceIndiName; + std::string driverIndiName; + std::string driverForm; + std::shared_ptr driver; + bool isConnect; +} ATOM_ALIGNAS(128); + +struct SystemDeviceList { + std::vector systemDevices; + int currentDeviceCode = -1; +} ATOM_ALIGNAS(32); + +} // namespace lithium::device + +// to_json and from_json functions for Device +inline void to_json(nlohmann::json& jsonObj, + const lithium::device::Device& device) { + jsonObj = nlohmann::json{{"label", device.label}, + {"manufacturer", device.manufacturer}, + {"driverName", device.driverName}, + {"version", device.version}}; +} + +inline void from_json(const nlohmann::json& jsonObj, + lithium::device::Device& device) { + jsonObj.at("label").get_to(device.label); + jsonObj.at("manufacturer").get_to(device.manufacturer); + jsonObj.at("driverName").get_to(device.driverName); + jsonObj.at("version").get_to(device.version); +} + +inline void to_json(nlohmann::json& jsonArray, + const std::vector& vec) { + jsonArray = nlohmann::json::array(); + for (const auto& device : vec) { + jsonArray.push_back({{"label", device.label}, + {"manufacturer", device.manufacturer}, + {"driverName", device.driverName}, + {"version", device.version}}); + } +} + +inline void from_json(const nlohmann::json& jsonArray, + std::vector& vec) { + for (const auto& jsonObj : jsonArray) { + lithium::device::Device device; + jsonObj.at("label").get_to(device.label); + jsonObj.at("manufacturer").get_to(device.manufacturer); + jsonObj.at("driverName").get_to(device.driverName); + jsonObj.at("version").get_to(device.version); + vec.push_back(device); + } +} + +// to_json and from_json functions for DevGroup +inline void to_json(nlohmann::json& jsonObj, + const lithium::device::DevGroup& group) { + jsonObj = nlohmann::json{{"group", group.groupName}}; + to_json(jsonObj["devices"], group.devices); +} + +inline void from_json(const nlohmann::json& jsonObj, + lithium::device::DevGroup& group) { + jsonObj.at("group").get_to(group.groupName); + from_json(jsonObj.at("devices"), group.devices); +} + +inline void to_json(nlohmann::json& jsonArray, + const std::vector& vec) { + jsonArray = nlohmann::json::array(); + for (const auto& group : vec) { + jsonArray.push_back({{"group", group.groupName}}); + to_json(jsonArray.back()["devices"], group.devices); + } +} + +inline void from_json(const nlohmann::json& jsonArray, + std::vector& vec) { + for (const auto& jsonObj : jsonArray) { + lithium::device::DevGroup group; + jsonObj.at("group").get_to(group.groupName); + from_json(jsonObj.at("devices"), group.devices); + vec.push_back(group); + } +} + +// to_json and from_json functions for DriversList +inline void to_json(nlohmann::json& jsonObj, + const lithium::device::DriversList& driversList) { + to_json(jsonObj["devGroups"], driversList.devGroups); + jsonObj["selectedGroup"] = driversList.selectedGroup; + // Fixed typo +} + +inline void from_json(const nlohmann::json& jsonObj, + lithium::device::DriversList& driversList) { + from_json(jsonObj.at("devGroups"), driversList.devGroups); + jsonObj.at("selectedGroup").get_to(driversList.selectedGroup); +} + +#endif \ No newline at end of file diff --git a/src/device/manager.cpp b/src/device/manager.cpp deleted file mode 100644 index 79974c49..00000000 --- a/src/device/manager.cpp +++ /dev/null @@ -1,317 +0,0 @@ -#include "manager.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "addon/manager.hpp" -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "device/template/camera.hpp" -#include "device/template/device.hpp" - -using json = nlohmann::json; - -namespace lithium { -auto DeviceManager::createShared() -> std::shared_ptr { - return std::make_shared(); -} - -auto DeviceManager::addDeviceFromComponent(const std::string& device_type, - const std::string& device_name, - const std::string& component, - const std::string& entry) -> bool { - if (componentManager_.expired()) { - LOG_F(ERROR, "Component manager expired"); - return false; - } - auto component_ = componentManager_.lock(); - if (!component_->hasComponent(component)) { - LOG_F(ERROR, "Component {} not found", component); - return false; - } - auto componentPtr = component_->getComponent(component).value(); - if (componentPtr.expired()) { - LOG_F(ERROR, "Component {} expired", component); - return false; - } - try { - if (device_type == "camera") { - auto driver = std::dynamic_pointer_cast( - std::any_cast>( - componentPtr.lock()->dispatch("create_instance", - device_name))); - devicesByName_[device_name] = driver; - devicesByUUID_[driver->getUUID()] = driver; - devicesByType_[device_type].push_back(driver); - return true; - } - } catch (const std::bad_cast& e) { - LOG_F(ERROR, "Failed to cast component {} to {}", component, - device_type); - } - return false; -} - -std::shared_ptr DeviceManager::getDeviceByUUID( - const std::string& uuid) const { - auto it = devicesByUUID_.find(uuid); - if (it != devicesByUUID_.end()) { - return it->second; - } - return nullptr; -} - -std::shared_ptr DeviceManager::getDeviceByName( - const std::string& name) const { - auto it = devicesByName_.find(name); - if (it != devicesByName_.end()) { - return it->second; - } - return nullptr; -} - -std::vector> DeviceManager::getDevicesByType( - const std::string& type) const { - auto it = devicesByType_.find(type); - if (it != devicesByType_.end()) { - return it->second; - } - return {}; -} - -bool DeviceManager::removeDeviceByUUID(const std::string& uuid) { - auto it = devicesByUUID_.find(uuid); - if (it != devicesByUUID_.end()) { - devicesByName_.erase(it->second->getName()); - auto& typeList = devicesByType_[it->second->getType()]; - typeList.erase( - std::remove(typeList.begin(), typeList.end(), it->second), - typeList.end()); - devicesByUUID_.erase(it); - return true; - } - return false; -} - -bool DeviceManager::removeDeviceByName(const std::string& name) { - auto it = devicesByName_.find(name); - if (it != devicesByName_.end()) { - devicesByUUID_.erase(it->second->getUUID()); - auto& typeList = devicesByType_[it->second->getType()]; - typeList.erase( - std::remove(typeList.begin(), typeList.end(), it->second), - typeList.end()); - devicesByName_.erase(it); - return true; - } - return false; -} - -void DeviceManager::listDevices() const { - std::cout << "Devices list:" << std::endl; - for (const auto& pair : devicesByUUID_) { - std::cout << "UUID: " << pair.first - << ", Name: " << pair.second->getName() - << ", Type: " << pair.second->getType() << std::endl; - } -} - -bool DeviceManager::updateDeviceName(const std::string& uuid, - const std::string& newName) { - auto device = getDeviceByUUID(uuid); - if (device) { - devicesByName_.erase(device->getName()); - device->setName(newName); - devicesByName_[newName] = device; - return true; - } - return false; -} - -size_t DeviceManager::getDeviceCount() const { return devicesByUUID_.size(); } - -auto DeviceManager::getCameraByName(const std::string& name) const - -> std::shared_ptr { - auto it = devicesByName_.find(name); - if (it != devicesByName_.end()) { - return std::dynamic_pointer_cast(it->second); - } - return nullptr; -} - -std::vector> DeviceManager::findDevices( - const DeviceFilter& filter) const { - std::vector> result; - for (const auto& pair : devicesByUUID_) { - if (filter(pair.second)) { - result.push_back(pair.second); - } - } - return result; -} - -void DeviceManager::setDeviceUpdateCallback( - const std::string& uuid, - std::function&)> callback) { - updateCallbacks_[uuid] = std::move(callback); -} - -void DeviceManager::removeDeviceUpdateCallback(const std::string& uuid) { - updateCallbacks_.erase(uuid); -} - -auto DeviceManager::getDeviceUsageStatistics(const std::string& uuid) const - -> std::pair { - auto it = deviceUsageStats_.find(uuid); - if (it != deviceUsageStats_.end()) { - auto now = std::chrono::steady_clock::now(); - auto duration = std::chrono::duration_cast( - now - it->second.first); - return {duration, it->second.second}; - } - return {std::chrono::seconds(0), 0}; -} - -void DeviceManager::resetDeviceUsageStatistics(const std::string& uuid) { - deviceUsageStats_[uuid] = {std::chrono::steady_clock::now(), 0}; -} - -auto DeviceManager::getLastErrorForDevice(const std::string& uuid) const - -> std::string { - auto it = lastDeviceErrors_.find(uuid); - return (it != lastDeviceErrors_.end()) ? it->second : ""; -} - -void DeviceManager::clearLastErrorForDevice(const std::string& uuid) { - lastDeviceErrors_.erase(uuid); -} - -void DeviceManager::enableDeviceLogging(const std::string& uuid, bool enable) { - if (enable) { - deviceLogs_[uuid] = {}; - } else { - deviceLogs_.erase(uuid); - } -} - -auto DeviceManager::getDeviceLog(const std::string& uuid) const - -> std::vector { - auto it = deviceLogs_.find(uuid); - return (it != deviceLogs_.end()) ? it->second : std::vector(); -} - -auto DeviceManager::createDeviceGroup( - const std::string& groupName, - const std::vector& deviceUUIDs) -> bool { - if (deviceGroups_.find(groupName) != deviceGroups_.end()) { - return false; // Group already exists - } - deviceGroups_[groupName] = deviceUUIDs; - return true; -} - -auto DeviceManager::removeDeviceGroup(const std::string& groupName) -> bool { - return deviceGroups_.erase(groupName) > 0; -} - -auto DeviceManager::getDeviceGroup(const std::string& groupName) const - -> std::vector> { - std::vector> result; - auto it = deviceGroups_.find(groupName); - if (it != deviceGroups_.end()) { - for (const auto& uuid : it->second) { - auto device = getDeviceByUUID(uuid); - if (device) { - result.push_back(device); - } - } - } - return result; -} - -void DeviceManager::performBulkOperation( - const std::vector& deviceUUIDs, - const std::function&)>& operation) { - for (const auto& uuid : deviceUUIDs) { - auto device = getDeviceByUUID(uuid); - if (device) { - operation(device); - } - } -} - -auto DeviceManager::loadFromFile(const std::string& filename) -> bool { - std::ifstream file(filename); - if (!file.is_open()) { - LOG_F(ERROR, "Failed to open file: {}", filename); - return false; - } - - try { - nlohmann::json json; - file >> json; - - for (const auto& deviceJson : json) { - // Implement device creation from JSON - // This is a placeholder and needs to be implemented based on your - // specific device structure - std::string uuid = deviceJson["uuid"]; - std::string name = deviceJson["name"]; - std::string type = deviceJson["type"]; - - // Create device based on type - std::shared_ptr device; - if (type == "camera") { - device = std::make_shared(name); - } else { - // Add other device types as needed - LOG_F(WARNING, "Unknown device type: {}", type); - continue; - } - - // Set device properties - // device->setUUID(uuid); - // Set other properties as needed - - // Add device to manager - devicesByUUID_[uuid] = device; - devicesByName_[name] = device; - devicesByType_[type].push_back(device); - } - } catch (const std::exception& e) { - LOG_F(ERROR, "Error parsing JSON: {}", e.what()); - return false; - } - - return true; -} - -// Update the existing saveToFile method -auto DeviceManager::saveToFile(const std::string& filename) const -> bool { - nlohmann::json json; - for (const auto& pair : devicesByUUID_) { - nlohmann::json deviceJson; - deviceJson["uuid"] = pair.second->getUUID(); - deviceJson["name"] = pair.second->getName(); - deviceJson["type"] = pair.second->getType(); - // Add other device properties as needed - json.push_back(deviceJson); - } - - std::ofstream file(filename); - if (!file.is_open()) { - LOG_F(ERROR, "Failed to open file for writing: {}", filename); - return false; - } - - file << json.dump(4); - return true; -} - -} // namespace lithium diff --git a/src/device/manager.hpp b/src/device/manager.hpp deleted file mode 100644 index f8322c66..00000000 --- a/src/device/manager.hpp +++ /dev/null @@ -1,112 +0,0 @@ -#ifndef LITHIUM_DEVICE_MANAGER_HPP -#define LITHIUM_DEVICE_MANAGER_HPP - -#include -#include -#include -#include -#include -#include - -#include "device/template/camera.hpp" -#include "template/device.hpp" - -namespace lithium { - -class ComponentManager; - -class DeviceManager { -public: - static auto createShared() -> std::shared_ptr; - - template - std::shared_ptr addDevice(const std::string& name) { - auto device = std::make_shared(name); - devicesByUUID_[device->getUUID()] = device; - devicesByName_[name] = device; - devicesByType_[device->getType()].push_back(device); - return device; - } - - auto addDeviceFromComponent(const std::string& device_type, - const std::string& device_name, - const std::string& component, - const std::string& entry) -> bool; - - std::shared_ptr getDeviceByUUID(const std::string& uuid) const; - std::shared_ptr getDeviceByName(const std::string& name) const; - std::vector> getDevicesByType( - const std::string& type) const; - - auto removeDeviceByUUID(const std::string& uuid) -> bool; - auto removeDeviceByName(const std::string& name) -> bool; - - void listDevices() const; - - auto updateDeviceName(const std::string& uuid, - const std::string& newName) -> bool; - auto updateDeviceStatus(const std::string& uuid, - const std::string& newStatus) -> bool; - - auto getDeviceCount() const -> size_t; - auto saveToFile(const std::string& filename) const -> bool; - auto loadFromFile(const std::string& filename) -> bool; - - auto getCameraByName(const std::string& name) const - -> std::shared_ptr; - - // New functionality - using DeviceFilter = - std::function&)>; - std::vector> findDevices( - const DeviceFilter& filter) const; - - void setDeviceUpdateCallback( - const std::string& uuid, - std::function&)> callback); - void removeDeviceUpdateCallback(const std::string& uuid); - - auto getDeviceUsageStatistics(const std::string& uuid) const - -> std::pair; - void resetDeviceUsageStatistics(const std::string& uuid); - - auto getLastErrorForDevice(const std::string& uuid) const -> std::string; - void clearLastErrorForDevice(const std::string& uuid); - - void enableDeviceLogging(const std::string& uuid, bool enable); - auto getDeviceLog(const std::string& uuid) const - -> std::vector; - - auto createDeviceGroup(const std::string& groupName, - const std::vector& deviceUUIDs) -> bool; - auto removeDeviceGroup(const std::string& groupName) -> bool; - auto getDeviceGroup(const std::string& groupName) const - -> std::vector>; - - void performBulkOperation( - const std::vector& deviceUUIDs, - const std::function&)>& operation); - -private: - std::unordered_map> devicesByUUID_; - std::unordered_map> devicesByName_; - std::unordered_map>> - devicesByType_; - std::weak_ptr componentManager_; - std::shared_ptr main_camera_; - - // New member variables - std::unordered_map&)>> - updateCallbacks_; - std::unordered_map> - deviceUsageStats_; - std::unordered_map lastDeviceErrors_; - std::unordered_map> deviceLogs_; - std::unordered_map> deviceGroups_; -}; - -} // namespace lithium - -#endif diff --git a/src/device/template/camera.hpp b/src/device/template/camera.hpp index 3193345b..507c162b 100644 --- a/src/device/template/camera.hpp +++ b/src/device/template/camera.hpp @@ -18,6 +18,7 @@ Description: AtomCamera Simulator and Basic Definition #include #include +#include #ifdef ENABLE_SHARED_MEMORY #include "shared_memory.hpp" @@ -109,7 +110,7 @@ class AtomCamera : public AtomDriver { virtual auto isISOAvailable() -> bool = 0; - virtual auto getFrame() -> bool = 0; + virtual auto getFrame() -> std::optional>; virtual auto setFrame(const int &x, const int &y, const int &w, const int &h) -> bool = 0; diff --git a/src/device/template/telescope.hpp b/src/device/template/telescope.hpp index f3f1cc8d..bffbeda7 100644 --- a/src/device/template/telescope.hpp +++ b/src/device/template/telescope.hpp @@ -20,7 +20,7 @@ Description: AtomTelescope Simulator and Basic Definition enum class ConnectionMode { SERIAL, TCP, NONE }; -enum class BAUD_RATE { B9600, B19200, B38400, B57600, B115200, B230400, NONE }; +enum class T_BAUD_RATE { B9600, B19200, B38400, B57600, B115200, B230400, NONE }; enum class TrackMode { SIDEREAL, SOLAR, LUNAR, CUSTOM, NONE }; diff --git a/src/server/middleware/indi_server.cpp b/src/server/middleware/indi_server.cpp new file mode 100644 index 00000000..a5594fd5 --- /dev/null +++ b/src/server/middleware/indi_server.cpp @@ -0,0 +1,1219 @@ +#include "indi_server.hpp" +#include + +#include "config/configor.hpp" +#include "device/basic.hpp" + +#include "atom/async/message_bus.hpp" +#include "atom/async/pool.hpp" +#include "atom/async/timer.hpp" +#include "atom/error/exception.hpp" +#include "atom/function/global_ptr.hpp" +#include "atom/log/loguru.hpp" +#include "atom/sysinfo/disk.hpp" +#include "atom/system/command.hpp" +#include "atom/system/env.hpp" +#include "atom/system/gpio.hpp" +#include "atom/system/process_manager.hpp" +#include "atom/type/json.hpp" +#include "atom/utils/print.hpp" +#include "atom/utils/qtimer.hpp" + +#include "device/template/camera.hpp" +#include "device/template/filterwheel.hpp" +#include "device/template/focuser.hpp" +#include "device/template/guider.hpp" +#include "device/template/solver.hpp" +#include "device/template/telescope.hpp" + +#include "utils/constant.hpp" + +#define GPIO_PIN_1 "516" +#define GPIO_PIN_2 "527" + +namespace lithium::middleware { +namespace internal { +auto clearCheckDeviceExists(const std::string& driverName) -> bool { + LOG_F(INFO, "Middleware::indiDriverConfirm: Checking device exists"); + return true; +} + +void printSystemDeviceList(device::SystemDeviceList s) { + LOG_F(INFO, + "Middleware::printSystemDeviceList: Printing system device list"); + std::string dpName; + for (auto& systemDevice : s.systemDevices) { + dpName = systemDevice.deviceIndiName; + LOG_F(INFO, + "Middleware::printSystemDeviceList: Device {} is connected: {}", + dpName, systemDevice.isConnect); + } +} + +void saveSystemDeviceList(const device::SystemDeviceList& deviceList) { + const std::string directory = "config"; // 配置文件夹名 + const std::string filename = + directory + "/device_connect.dat"; // 在配置文件夹中创建文件 + + std::ofstream outfile(filename, std::ios::binary); + + if (!outfile.is_open()) { + std::cerr << "打开文件写入时发生错误: " << filename << std::endl; + return; + } + + for (const auto& device : deviceList.systemDevices) { + // 转换 std::string 成员为 UTF-8 字符串 + const std::string& descriptionUtf8 = device.description; + const std::string& deviceIndiNameUtf8 = device.deviceIndiName; + const std::string& driverIndiNameUtf8 = device.driverIndiName; + const std::string& driverFromUtf8 = device.driverForm; + + // 写入 std::string 大小信息和数据 + size_t descriptionSize = descriptionUtf8.size(); + outfile.write(reinterpret_cast(&descriptionSize), + sizeof(size_t)); + outfile.write(descriptionUtf8.data(), descriptionSize); + + outfile.write(reinterpret_cast(&device.deviceIndiGroup), + sizeof(int)); + + size_t deviceIndiNameSize = deviceIndiNameUtf8.size(); + outfile.write(reinterpret_cast(&deviceIndiNameSize), + sizeof(size_t)); + outfile.write(deviceIndiNameUtf8.data(), deviceIndiNameSize); + + size_t driverIndiNameSize = driverIndiNameUtf8.size(); + outfile.write(reinterpret_cast(&driverIndiNameSize), + sizeof(size_t)); + outfile.write(driverIndiNameUtf8.data(), driverIndiNameSize); + + size_t driverFromSize = driverFromUtf8.size(); + outfile.write(reinterpret_cast(&driverFromSize), + sizeof(size_t)); + outfile.write(driverFromUtf8.data(), driverFromSize); + + outfile.write(reinterpret_cast(&device.isConnect), + sizeof(bool)); + } + + outfile.close(); +} + +void clearSystemDeviceListItem(device::SystemDeviceList& s, int index) { + // clear one device + LOG_F(INFO, "Middleware::clearSystemDeviceListItem: Clearing device"); + if (s.systemDevices.empty()) { + LOG_F(INFO, + "Middleware::clearSystemDeviceListItem: System device list is " + "empty"); + } else { + auto& currentDevice = s.systemDevices[index]; + currentDevice.deviceIndiGroup = -1; + currentDevice.deviceIndiName = ""; + currentDevice.driverIndiName = ""; + currentDevice.driverForm = ""; + currentDevice.isConnect = false; + currentDevice.driver = nullptr; + currentDevice.description = ""; + LOG_F(INFO, "Middleware::clearSystemDeviceListItem: Device is cleared"); + } +} + +void selectIndiDevice(int systemNumber, int grounpNumber) { + std::shared_ptr systemDeviceListPtr; + GET_OR_CREATE_PTR(systemDeviceListPtr, device::SystemDeviceList, + Constants::SYSTEM_DEVICE_LIST) + systemDeviceListPtr->currentDeviceCode = systemNumber; + std::shared_ptr driversListPtr; + GET_OR_CREATE_PTR(driversListPtr, device::DriversList, + Constants::DRIVERS_LIST) + driversListPtr->selectedGroup = grounpNumber; + + static const std::unordered_map deviceDescriptions = { + {0, "Mount"}, + {1, "Guider"}, + {2, "PoleCamera"}, + {3, ""}, + {4, ""}, + {5, ""}, + {20, "Main Camera #1"}, + {21, "CFW #1"}, + {22, "Focuser #1"}, + {23, "Lens Cover #1"}}; + + auto it = deviceDescriptions.find(systemNumber); + if (it != deviceDescriptions.end()) { + systemDeviceListPtr->systemDevices[systemNumber].description = + it->second; + } + + LOG_F(INFO, "Middleware::SelectIndiDevice: Selecting device"); + LOG_F(INFO, "Middleware::SelectIndiDevice: System number: {}", + systemNumber); + + for (auto& device : driversListPtr->devGroups[grounpNumber].devices) { + LOG_F(INFO, "Middleware::SelectIndiDevice: Device: {}", + device.driverName); + + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + messageBusPtr->publish("main", "AddDriver:" + device.driverName); + } +} + +void DeviceSelect(int systemNumber, int grounpNumber) { + LOG_F(INFO, "Middleware::DeviceSelect: Selecting device"); + std::shared_ptr systemDeviceListPtr; + GET_OR_CREATE_PTR(systemDeviceListPtr, device::SystemDeviceList, + Constants::SYSTEM_DEVICE_LIST) + clearSystemDeviceListItem(*systemDeviceListPtr, systemNumber); + selectIndiDevice(systemNumber, grounpNumber); +} + +int getFocuserPosition() { + std::shared_ptr dpFocuser; + GET_OR_CREATE_PTR(dpFocuser, AtomFocuser, Constants::MAIN_FOCUSER) + if (dpFocuser) { + return getFocuserPosition(); + } + return -1; +} + +void focusingLooping() { + std::shared_ptr dpMainCamera; + if (dpMainCamera) { + return; + } + + std::shared_ptr isFocusingLooping; + GET_OR_CREATE_PTR(isFocusingLooping, bool, Constants::IS_FOCUSING_LOOPING) + *isFocusingLooping = true; + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, ConfigManager, Constants::CONFIG_MANAGER) + auto status = configManager->getValue("/lithium/device/camera/status") + ->get(); + if (status == "Displaying") { + double expTimeSec; + auto expTime = + configManager->getValue("/lithium/device/camera/current_exposure"); + if (expTime) { + expTimeSec = expTime->get() / 1000; + } else { + expTimeSec = 1; + } + + configManager->setValue("/lithium/device/camera/status", "Exposuring"); + LOG_F(INFO, "Middleware::focusingLooping: Focusing looping"); + + auto [x, y] = dpMainCamera->getFrame().value(); + std::array cameraResolution{x, y}; + auto boxSideLength = + configManager->getValue("/lithium/device/camera/box_side_length") + ->get(); + auto [ROI_X, ROI_Y] = + configManager->getValue("/lithium/device/camera/roi") + ->get>(); + std::array ROI{boxSideLength, boxSideLength}; + auto [cameraX, cameraY] = + configManager->getValue("/lithium/device/camera_frame") + ->get>(); + cameraX = ROI_X * cameraResolution[0] / (double)x; + cameraY = ROI_Y * cameraResolution[1] / (double)y; + if (cameraX < x - ROI[0] && cameraY < y - ROI[1]) { + dpMainCamera->setFrame(cameraX, cameraY, boxSideLength, + boxSideLength); + } else { + LOG_F(INFO, + "Middleware::focusingLooping: Too close to the edge, please " + "reselect the area."); + if (cameraX + ROI[0] > x) { + cameraX = x - ROI[0]; + } + if (cameraY + ROI[1] > y) { + cameraY = y - ROI[1]; + } + dpMainCamera->setFrame(cameraX, cameraY, boxSideLength, + boxSideLength); + } + /* + int cameraX = + glROI_x * cameraResolution.width() / (double)CaptureViewWidth; + int cameraY = + glROI_y * cameraResolution.height() / (double)CaptureViewHeight; + + if (cameraX < glMainCCDSizeX - ROI.width() && + cameraY < glMainCCDSizeY - ROI.height()) { + indi_Client->setCCDFrameInfo( + dpMainCamera, cameraX, cameraY, BoxSideLength, + BoxSideLength); // add by CJQ 2023.2.15 + indi_Client->takeExposure(dpMainCamera, expTime_sec); + } else { + qDebug("Too close to the edge, please reselect the area."); // + TODO: if (cameraX + ROI.width() > glMainCCDSizeX) cameraX = + glMainCCDSizeX - ROI.width(); if (cameraY + ROI.height() > + glMainCCDSizeY) cameraY = glMainCCDSizeY - ROI.height(); + + indi_Client->setCCDFrameInfo(dpMainCamera, cameraX, cameraY, + ROI.width(), + ROI.height()); // add by CJQ 2023.2.15 + indi_Client->takeExposure(dpMainCamera, expTime_sec); + } + */ + dpMainCamera->startExposure(expTimeSec); + } +} + +void focuserMove(bool isInward, int steps) { + std::shared_ptr dpFocuser; + GET_OR_CREATE_PTR(dpFocuser, AtomFocuser, Constants::MAIN_FOCUSER) + if (dpFocuser) { + std::shared_ptr focusTimer; + GET_OR_CREATE_PTR(focusTimer, atom::async::Timer, Constants::MAIN_TIMER) + auto currentPosition = getFocuserPosition(); + int targetPosition; + targetPosition = currentPosition + (isInward ? steps : -steps); + LOG_F(INFO, "Focuser Move: {} -> {}", currentPosition, targetPosition); + + dpFocuser->setFocuserMoveDirection(isInward); + dpFocuser->moveFocuserSteps(steps); + + focusTimer->setInterval( + [&targetPosition]() { + auto currentPosition = getFocuserPosition(); + if (currentPosition == targetPosition) { + LOG_F(INFO, "Focuser Move Complete!"); + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + messageBusPtr->publish("main", "FocuserMoveDone"); + } else { + LOG_F(INFO, "Focuser Moving: {} -> {}", currentPosition, + targetPosition); + } + }, + 1000, 30, 0); + } +} + +int fitQuadraticCurve(const std::vector>& data, + double& a, double& b, double& c) { + int n = data.size(); + if (n < 5) { + return -1; // 数据点数量不足 + } + + double sumX = 0, sumY = 0, sumX2 = 0, sumX3 = 0, sumX4 = 0; + double sumXY = 0, sumX2Y = 0; + + for (const auto& point : data) { + double x = point.first; + double y = point.second; + double x2 = x * x; + double x3 = x2 * x; + double x4 = x3 * x; + + sumX += x; + sumY += y; + sumX2 += x2; + sumX3 += x3; + sumX4 += x4; + sumXY += x * y; + sumX2Y += x2 * y; + } + + double denom = n * (sumX2 * sumX4 - sumX3 * sumX3) - + sumX * (sumX * sumX4 - sumX2 * sumX3) + + sumX2 * (sumX * sumX3 - sumX2 * sumX2); + if (denom == 0) { + return -1; // 无法拟合 + } + + a = (n * (sumX2 * sumX2Y - sumX3 * sumXY) - + sumX * (sumX * sumX2Y - sumX2 * sumXY) + + sumX2 * (sumX * sumXY - sumX2 * sumY)) / + denom; + b = (n * (sumX4 * sumXY - sumX3 * sumX2Y) - + sumX2 * (sumX2 * sumX2Y - sumX3 * sumXY) + + sumX3 * (sumX2 * sumY - sumX * sumXY)) / + denom; + c = (sumY * (sumX2 * sumX4 - sumX3 * sumX3) - + sumX * (sumX2 * sumX2Y - sumX3 * sumXY) + + sumX2 * (sumX2 * sumXY - sumX3 * sumY)) / + denom; + + return 0; // 拟合成功 +} + +device::SystemDeviceList readSystemDeviceList() { + device::SystemDeviceList deviceList; + const std::string directory = "config"; + const std::string filename = + directory + "/device_connect.dat"; // 在配置文件夹中创建文件 + std::ifstream infile(filename, std::ios::binary); + + if (!infile.is_open()) { + LOG_F(INFO, "Middleware::readSystemDeviceList: File not found: {}", + filename); + return deviceList; + } + + while (true) { + device::SystemDevice device; + + // 读取 description + size_t descriptionSize; + infile.read(reinterpret_cast(&descriptionSize), sizeof(size_t)); + if (infile.eof()) + break; + device.description.resize(descriptionSize); + infile.read(&device.description[0], descriptionSize); + + // 读取 deviceIndiGroup + infile.read(reinterpret_cast(&device.deviceIndiGroup), + sizeof(int)); + + // 读取 deviceIndiName + size_t deviceIndiNameSize; + infile.read(reinterpret_cast(&deviceIndiNameSize), + sizeof(size_t)); + device.deviceIndiName.resize(deviceIndiNameSize); + infile.read(&device.deviceIndiName[0], deviceIndiNameSize); + + // 读取 driverIndiName + size_t driverIndiNameSize; + infile.read(reinterpret_cast(&driverIndiNameSize), + sizeof(size_t)); + device.driverIndiName.resize(driverIndiNameSize); + infile.read(&device.driverIndiName[0], driverIndiNameSize); + + // 读取 driverForm + size_t driverFormSize; + infile.read(reinterpret_cast(&driverFormSize), sizeof(size_t)); + device.driverForm.resize(driverFormSize); + infile.read(&device.driverForm[0], driverFormSize); + + // 读取 isConnect + infile.read(reinterpret_cast(&device.isConnect), sizeof(bool)); + + deviceList.systemDevices.push_back(device); + } + + infile.close(); + return deviceList; +} + +int getTotalDeviceFromSystemDeviceList(const device::SystemDeviceList& s) { + return std::count_if( + s.systemDevices.begin(), s.systemDevices.end(), + [](const auto& dev) { return !dev.deviceIndiName.empty(); }); +} + +void cleanSystemDeviceListConnect(device::SystemDeviceList& s) { + for (auto& device : s.systemDevices) { + device.isConnect = false; + device.driver = nullptr; + } +} + +void startIndiDriver(const std::string& driverName) { + std::string s; + s = "echo "; + s.append("\"start "); + s.append(driverName); + s.append("\""); + s.append("> /tmp/myFIFO"); + system(s.c_str()); + // qDebug() << "startIndiDriver" << driver_name; + LOG_F(INFO, "Start INDI Driver | DriverName: {}", driverName); +} + +void stopIndiDriver(const std::string& driverName) { + std::string s; + s = "echo "; + s.append("\"stop "); + s.append(driverName); + s.append("\""); + s.append("> /tmp/myFIFO"); + system(s.c_str()); + LOG_F(INFO, "Stop INDI Driver | DriverName: {}", driverName); +} + +void stopIndiDriverAll(const device::DriversList& driver_list) { + // before each connection. need to stop all of the indi driver + // need to make sure disconnect all the driver for first. If the driver is + // under operation, stop it may cause crash + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, ConfigManager, Constants::CONFIG_MANAGER) + bool status = configManager->getValue("/lithium/server/indi/status") + ->get(); // get the indi server status + if (!status) { + LOG_F(ERROR, "stopIndiDriverAll | ERROR | INDI DRIVER NOT running"); + return; + } + + for (const auto& group : driver_list.devGroups) { + for (const auto& device : group.devices) { + stopIndiDriver(device.driverName); + } + } +} + +std::string printDevices() { + LOG_F(INFO, "Middleware::printDevices: Printing devices"); + std::string dev; + std::shared_ptr systemDeviceListPtr; + GET_OR_CREATE_PTR(systemDeviceListPtr, device::SystemDeviceList, + Constants::SYSTEM_DEVICE_LIST) + const auto& deviceList = systemDeviceListPtr->systemDevices; + if (deviceList.empty()) { + LOG_F(INFO, "Middleware::printDevices: No device exist"); + } else { + for (size_t i = 0; i < deviceList.size(); ++i) { + LOG_F(INFO, "Middleware::printDevices: Device: {}", + deviceList[i].deviceIndiName); + if (i > 0) { + dev.append("|"); // 添加分隔符 + } + dev.append(deviceList[i].deviceIndiName); // 添加设备名称 + dev.append(":"); + dev.append(std::to_string(i)); // 添加序号 + } + } + + LOG_F(INFO, "Middleware::printDevices: Devices printed"); + return dev; +} + +bool getIndexFromSystemDeviceList(const device::SystemDeviceList& s, + const std::string& devname, int& index) { + auto it = std::find_if( + s.systemDevices.begin(), s.systemDevices.end(), + [&devname](const auto& dev) { return dev.deviceIndiName == devname; }); + + if (it != s.systemDevices.end()) { + index = std::distance(s.systemDevices.begin(), it); + LOG_F(INFO, + "getIndexFromSystemDeviceList | found device in system list. " + "device name: {} index: {}", + devname, index); + return true; + } else { + index = 0; + LOG_F(INFO, + "getIndexFromSystemDeviceList | not found device in system list, " + "devname: {}", + devname); + return false; + } +} + +std::string getDeviceNameFromList(int index) { + std::shared_ptr systemDeviceListPtr; + GET_OR_CREATE_PTR(systemDeviceListPtr, device::SystemDeviceList, + Constants::SYSTEM_DEVICE_LIST) + const auto& deviceNames = systemDeviceListPtr->systemDevices; + if (index < 0 || index >= static_cast(deviceNames.size())) { + return ""; + } + return deviceNames[index].deviceIndiName; +} + +uint8_t MSB(uint16_t i) { return static_cast((i >> 8) & 0xFF); } + +uint8_t LSB(uint16_t i) { return static_cast(i & 0xFF); } + +auto callPHDWhichCamera(const std::string& Camera) -> bool { + unsigned int vendcommand; + unsigned int baseAddress; + + /* + bzero(sharedmemory_phd, 1024); // 共享内存清空 + + baseAddress = 0x03; + vendcommand = 0x0d; + + sharedmemory_phd[1] = MSB(vendcommand); + sharedmemory_phd[2] = LSB(vendcommand); + + sharedmemory_phd[0] = 0x01; // enable command + + int length = Camera.length() + 1; + + unsigned char addr = 0; + // memcpy(sharedmemory_phd + baseAddress + addr, &index, sizeof(int)); + // addr = addr + sizeof(int); + memcpy(sharedmemory_phd + baseAddress + addr, &length, sizeof(int)); + addr = addr + sizeof(int); + memcpy(sharedmemory_phd + baseAddress + addr, Camera.c_str(), length); + addr = addr + length; + + // wait stellarium finished this task + QElapsedTimer t; + t.start(); + + while (sharedmemory_phd[0] == 0x01 && t.elapsed() < 500) { + // QCoreApplication::processEvents(); + } // wait stellarium run end + + if (t.elapsed() >= 500) + return QHYCCD_ERROR; // timeout + else + return QHYCCD_SUCCESS; + */ + return true; +} + +} // namespace internal + +auto indiDriverConfirm(const std::string& driverName) -> bool { + LOG_F(INFO, "Middleware::indiDriverConfirm: Checking driver: {}", + driverName); + + auto isExist = internal::clearCheckDeviceExists(driverName); + if (!isExist) { + std::shared_ptr systemDeviceListPtr; + GET_OR_CREATE_PTR(systemDeviceListPtr, device::SystemDeviceList, + Constants::SYSTEM_DEVICE_LIST) + auto& currentDevice = + systemDeviceListPtr + ->systemDevices[systemDeviceListPtr->currentDeviceCode]; + currentDevice.deviceIndiGroup = -1; + currentDevice.deviceIndiName = ""; + currentDevice.driverIndiName = ""; + currentDevice.driverForm = ""; + currentDevice.isConnect = false; + currentDevice.driver = nullptr; + currentDevice.description = ""; + } + LOG_F(INFO, "Middleware::indiDriverConfirm: Driver {} is exist: {}", + driverName, isExist); + return isExist; +} + +void indiDeviceConfirm(const std::string& deviceName, + const std::string& driverName) { + LOG_F(INFO, + "Middleware::indiDeviceConfirm: Checking device: {} with driver: {}", + deviceName, driverName); + + int deviceCode; + std::shared_ptr systemDeviceListPtr; + GET_OR_CREATE_PTR(systemDeviceListPtr, device::SystemDeviceList, + Constants::SYSTEM_DEVICE_LIST) + deviceCode = systemDeviceListPtr->currentDeviceCode; + + std::shared_ptr driversListPtr; + GET_OR_CREATE_PTR(driversListPtr, device::DriversList, + Constants::DRIVERS_LIST) + + auto& currentDevice = systemDeviceListPtr->systemDevices[deviceCode]; + currentDevice.driverIndiName = driverName; + currentDevice.deviceIndiGroup = driversListPtr->selectedGroup; + currentDevice.deviceIndiName = deviceName; + + LOG_F(INFO, + "Middleware::indiDeviceConfirm: Device {} with driver {} is " + "confirmed", + deviceName, driverName); + + internal::printSystemDeviceList(*systemDeviceListPtr); + + internal::saveSystemDeviceList(*systemDeviceListPtr); +} + +void printDevGroups2(const device::DriversList& driversList, int ListNum, + const std::string& group) { + LOG_F(INFO, "Middleware::printDevGroups: printDevGroups2:"); + + for (int index = 0; index < driversList.devGroups.size(); ++index) { + const auto& devGroup = driversList.devGroups[index]; + LOG_F(INFO, "Middleware::printDevGroups: Group: {}", + devGroup.groupName); + + if (devGroup.groupName == group) { + LOG_F(INFO, "Middleware::printDevGroups: Group: {}", + devGroup.groupName); + /* + for (const auto& device : devGroup.devices) { + LOG_F(INFO, "Middleware::printDevGroups: Device: {}", + device.driverName); std::shared_ptr + messageBusPtr; GET_OR_CREATE_PTR(messageBusPtr, + atom::async::MessageBus, Constants::MESSAGE_BUS) + messageBusPtr->publish("main", "AddDriver:" + + device.driverName); + } + */ + internal::selectIndiDevice(ListNum, index); + } + } +} + +void indiCapture(int expTime) { + auto glIsFocusingLooping = + GetPtr(Constants::IS_FOCUSING_LOOPING).value(); + *glIsFocusingLooping = false; + double expTimeSec = static_cast(expTime) / 1000; + LOG_F(INFO, "INDI_Capture | exptime: {}", expTimeSec); + + auto dpMainCameraOpt = GetPtr(Constants::MAIN_CAMERA); + if (!dpMainCameraOpt.has_value()) { + LOG_F(ERROR, "INDI_Capture | dpMainCamera is NULL"); + return; + } + + auto dpMainCamera = dpMainCameraOpt.value(); + auto configManagerPtr = + GetPtr(Constants::CONFIG_MANAGER).value(); + configManagerPtr->setValue("/lithium/device/camera/status", "Exposuring"); + LOG_F(INFO, "INDI_Capture | Camera status: Exposuring"); + + dpMainCamera->getGain(); + dpMainCamera->getOffset(); + + auto messageBusPtr = + GetPtr(Constants::MESSAGE_BUS).value(); + auto [x, y] = dpMainCamera->getFrame().value(); + messageBusPtr->publish("main", "MainCameraSize:{}:{}"_fmt(x, y)); + + dpMainCamera->startExposure(expTimeSec); + LOG_F(INFO, "INDI_Capture | Camera status: Exposuring"); +} + +void indiAbortCapture() { + auto dpMainCameraOpt = GetPtr(Constants::MAIN_CAMERA); + if (!dpMainCameraOpt.has_value()) { + LOG_F(ERROR, "INDI_AbortCapture | dpMainCamera is NULL"); + return; + } + + auto dpMainCamera = dpMainCameraOpt.value(); + dpMainCamera->abortExposure(); + LOG_F(INFO, "INDI_AbortCapture | Camera status: Aborted"); +} + +auto setFocusSpeed(int speed) -> int { + std::shared_ptr dpFocuser; + if (dpFocuser) { + dpFocuser->setFocuserSpeed(speed); + auto [value, min, max] = dpFocuser->getFocuserSpeed().value(); + LOG_F(INFO, "INDI_FocusSpeed | Focuser Speed: {}, {}, {}", value, min, + max); + return value; + } + LOG_F(ERROR, "INDI_FocusSpeed | dpFocuser is NULL"); + return -1; +} + +auto focusMoveAndCalHFR(bool isInward, int steps) -> double { + double FWHM = 0; + + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, ConfigManager, Constants::CONFIG_MANAGER) + configManager->setValue("/lithium/device/focuser/calc_fwhm", false); + + internal::focuserMove(isInward, steps); + + std::shared_ptr focusTimer; + GET_OR_CREATE_PTR(focusTimer, atom::async::Timer, Constants::MAIN_TIMER) + + focusTimer->setInterval( + [&FWHM, configManager]() { + if (configManager->getValue("/lithium/device/focuser/calc_fwhm") + ->get()) { + FWHM = configManager->getValue("/lithium/device/focuser/fwhm") + ->get(); // 假设 this->FWHM 保存了计算结果 + LOG_F(INFO, "FWHM Calculation Complete!"); + } + }, + 1000, 30, 0); + + focusTimer->wait(); + return FWHM; +} + +void autofocus() { + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, ConfigManager, Constants::CONFIG_MANAGER) + configManager->setValue("/lithium/device/focuser/auto_focus", false); + + int stepIncrement = + configManager + ->getValue("/lithium/device/focuser/auto_focus_step_increase") + .value_or(100); + LOG_F(INFO, "AutoFocus | Step Increase: {}", stepIncrement); + + bool isInward = true; + focusMoveAndCalHFR(!isInward, stepIncrement * 5); + + int initialPosition = internal::getFocuserPosition(); + int currentPosition = initialPosition; + int onePassSteps = 8; + int lostStarNum = 0; + std::vector> focusMeasures; + + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + + auto stopAutoFocus = [&]() { + LOG_F(INFO, "AutoFocus | Stop Auto Focus"); + messageBusPtr->publish("main", "AutoFocusOver:true"); + }; + + for (int i = 1; i < onePassSteps; i++) { + if (configManager->getValue("/lithium/device/focuser/auto_focus") + .value_or(false)) { + stopAutoFocus(); + return; + } + double hfr = focusMoveAndCalHFR(isInward, stepIncrement); + LOG_F(INFO, "AutoFocus | Pass1: HFR-{}({}) Calculation Complete!", i, + hfr); + if (hfr == -1) { + lostStarNum++; + if (lostStarNum >= 3) { + LOG_F(INFO, "AutoFocus | Too many number of lost star points."); + // TODO: Implement FocusGotoAndCalFWHM(initialPosition - + // stepIncrement * 5); + LOG_F(INFO, "AutoFocus | Returned to the starting point."); + stopAutoFocus(); + return; + } + } + currentPosition = internal::getFocuserPosition(); + focusMeasures.emplace_back(currentPosition, hfr); + } + + auto fitAndCheck = [&](double& a, double& b, double& c) -> bool { + int result = internal::fitQuadraticCurve(focusMeasures, a, b, c); + if (result != 0 || a >= 0) { + LOG_F(INFO, "AutoFocus | Fit failed or parabola opens upward"); + return false; + } + return true; + }; + + double a; + double b; + double c; + if (!fitAndCheck(a, b, c)) { + stopAutoFocus(); + return; + } + + int minPointX = + configManager->getValue("/lithium/device/focuser/auto_focus_min_point") + .value_or(0); + int countLessThan = std::count_if( + focusMeasures.begin(), focusMeasures.end(), + [&](const auto& point) { return point.first < minPointX; }); + int countGreaterThan = focusMeasures.size() - countLessThan; + + if (countLessThan > countGreaterThan) { + LOG_F(INFO, "AutoFocus | More points are less than minPointX."); + if (a > 0) { + focusMoveAndCalHFR(!isInward, + stepIncrement * (onePassSteps - 1) * 2); + } + } else if (countGreaterThan > countLessThan) { + LOG_F(INFO, "AutoFocus | More points are greater than minPointX."); + if (a < 0) { + focusMoveAndCalHFR(!isInward, + stepIncrement * (onePassSteps - 1) * 2); + } + } + + for (int i = 1; i < onePassSteps; i++) { + if (configManager->getValue("/lithium/device/focuser/auto_focus") + .value_or(false)) { + stopAutoFocus(); + return; + } + double hfr = focusMoveAndCalHFR(isInward, stepIncrement); + LOG_F(INFO, "AutoFocus | Pass2: HFR-{}({}) Calculation Complete!", i, + hfr); + currentPosition = internal::getFocuserPosition(); + focusMeasures.emplace_back(currentPosition, hfr); + } + + if (!fitAndCheck(a, b, c)) { + stopAutoFocus(); + return; + } + + int pass3Steps = std::abs(countLessThan - countGreaterThan); + LOG_F(INFO, "AutoFocus | Pass3Steps: {}", pass3Steps); + + for (int i = 1; i <= pass3Steps; i++) { + if (configManager->getValue("/lithium/device/focuser/auto_focus") + .value_or(false)) { + stopAutoFocus(); + return; + } + double HFR = focusMoveAndCalHFR(isInward, stepIncrement); + LOG_F(INFO, "AutoFocus | Pass3: HFR-{}({}) Calculation Complete!", i, + HFR); + currentPosition = internal::getFocuserPosition(); + focusMeasures.emplace_back(currentPosition, HFR); + } + + // TODO: Implement FocusGotoAndCalFWHM(minPointX); + LOG_F(INFO, "Auto focus complete. Best step: {}", minPointX); + messageBusPtr->publish("main", "AutoFocusOver:true"); +} + +void deviceConnect() { + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, ConfigManager, Constants::CONFIG_MANAGER) + bool oneTouchConnect = + configManager->getValue("/lithium/device/oneTouchConnect") + .value_or(false); + bool oneTouchConnectFirst = + configManager->getValue("/lithium/device/oneTouchConnectFirst") + .value_or(true); + + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + + std::shared_ptr systemDeviceListPtr; + GET_OR_CREATE_PTR(systemDeviceListPtr, device::SystemDeviceList, + Constants::SYSTEM_DEVICE_LIST) + if (oneTouchConnect && oneTouchConnectFirst) { + *systemDeviceListPtr = internal::readSystemDeviceList(); + for (int i = 0; i < 32; i++) { + if (!systemDeviceListPtr->systemDevices[i].deviceIndiName.empty()) { + LOG_F(INFO, "DeviceConnect | {}: {}", + systemDeviceListPtr->systemDevices[i].deviceIndiName, + systemDeviceListPtr->systemDevices[i].description); + + messageBusPtr->publish( + "main", + "updateDevices_:{}:{}"_fmt( + i, + systemDeviceListPtr->systemDevices[i].deviceIndiName)); + } + } + oneTouchConnectFirst = false; + return; + } + + if (internal::getTotalDeviceFromSystemDeviceList(*systemDeviceListPtr) == + 0) { + LOG_F(ERROR, "DeviceConnect | No device found"); + messageBusPtr->publish( + "main", "ConnectFailed:no device in system device list."); + return; + } + // systemDeviceListPtr->systemDevicescleanSystemDeviceListConnect(*systemDeviceListPtr); + internal::printSystemDeviceList(*systemDeviceListPtr); + + // qApp->processEvents(); + // connect all camera on the list + std::string driverName; + + std::vector nameCheck; + // disconnectIndiServer(indi_Client); + + std::shared_ptr driversListPtr; + GET_OR_CREATE_PTR(driversListPtr, device::DriversList, + Constants::DRIVERS_LIST) + + internal::stopIndiDriverAll(*driversListPtr); + int k = 3; + while (k--) { + LOG_F(INFO, "DeviceConnect | Wait stopIndiDriverAll..."); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + for (const auto& device : systemDeviceListPtr->systemDevices) { + driverName = device.driverIndiName; + if (!driverName.empty()) { + if (std::find(nameCheck.begin(), nameCheck.end(), driverName) != + nameCheck.end()) { + LOG_F(INFO, + "DeviceConnect | found one duplicate driver, do not " + "start it again: {}", + driverName); + + } else { + internal::startIndiDriver(driverName); + for (int k = 0; k < 3; ++k) { + LOG_F(INFO, "DeviceConnect | Wait startIndiDriver..."); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + nameCheck.push_back(driverName); + } + } + } + + // Max: Our logic is not same as QHYCCD, in our logic, one device will + // handle an INDI CLient + // connectIndiServer(indi_Client); + + // if (indi_Client->isServerConnected() == false) { + // qDebug() << "System Connect | ERROR:can not find server"; + // return; + // } + + std::this_thread::sleep_for(std::chrono::seconds( + 3)); // connect server will generate the callback of newDevice and + // then put the device into list. this need take some time and it + // is non-block + + // wait the client device list's device number match the system device + // list's device number + int totalDevice = + internal::getTotalDeviceFromSystemDeviceList(*systemDeviceListPtr); + atom::utils::ElapsedTimer timer; + int timeoutMs = 10000; + timer.start(); + while (timer.elapsed() < timeoutMs) { + int connectedDevice = std::count_if( + systemDeviceListPtr->systemDevices.begin(), + systemDeviceListPtr->systemDevices.end(), + [](const auto& device) { return device.driver != nullptr; }); + if (connectedDevice >= totalDevice) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + LOG_F(INFO, "DeviceConnect | Wait for device connection..."); + } + if (timer.elapsed() > timeoutMs) { + LOG_F(ERROR, "DeviceConnect | Device connection timeout"); + messageBusPtr->publish( + "main", + "ConnectFailed:Device connected less than system device list."); + } else { + LOG_F(INFO, "DeviceConnect | Device connection success"); + } + + internal::printDevices(); + + if (systemDeviceListPtr->systemDevices.empty()) { + LOG_F(ERROR, "DeviceConnect | No device found"); + messageBusPtr->publish("main", "ConnectFailed:No device found."); + return; + } + LOG_F(INFO, "DeviceConnect | Device connection complete"); + int index; + int total_errors = 0; + + int connectedDevice = std::count_if( + systemDeviceListPtr->systemDevices.begin(), + systemDeviceListPtr->systemDevices.end(), + [](const auto& device) { return device.driver != nullptr; }); + for (int i = 0; i < connectedDevice; i++) { + LOG_F(INFO, "DeviceConnect | Device: {}", + systemDeviceListPtr->systemDevices[i].deviceIndiName); + + // take one device from indi_Client detected devices and get the index + // number in pre-selected systemDeviceListPtr->systemDevices + auto ret = internal::getIndexFromSystemDeviceList( + *systemDeviceListPtr, internal::getDeviceNameFromList(index), + index); + if (ret) { + LOG_F(INFO, "DeviceConnect | Device: {} is connected", + systemDeviceListPtr->systemDevices[index].deviceIndiName); + systemDeviceListPtr->systemDevices[index].isConnect = true; + systemDeviceListPtr->systemDevices[index].driver->connect( + systemDeviceListPtr->systemDevices[index].deviceIndiName, 60, + 5); + + systemDeviceListPtr->systemDevices[index].isConnect = false; + if (index == 1) { + internal::callPHDWhichCamera( + systemDeviceListPtr->systemDevices[i] + .driver->getName()); // PHD2 Guider Connect + } else { + systemDeviceListPtr->systemDevices[index].driver->connect( + systemDeviceListPtr->systemDevices[index].deviceIndiName, + 60, 5); + } + // guider will be control by PHD2, so that the watch device should + // exclude the guider + // indi_Client->StartWatch(systemDeviceListPtr->systemDevices[index].dp); + } else { + total_errors++; + } + } + if (total_errors > 0) { + LOG_F(ERROR, + "DeviceConnect | Error: There is some detected list is not in " + "the pre-select system list, total mismatch device: {}", + total_errors); + // return; + } + + // connecting..... + // QElapsedTimer t; + timer.start(); + timeoutMs = 20000 * connectedDevice; + while (timer.elapsed() < timeoutMs) { + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + int totalConnected = 0; + for (int i = 0; i < connectedDevice; i++) { + int index; + auto ret = internal::getIndexFromSystemDeviceList( + *systemDeviceListPtr, internal::getDeviceNameFromList(index), + index); + if (ret) { + if (systemDeviceListPtr->systemDevices[index].driver && + systemDeviceListPtr->systemDevices[index] + .driver->isConnected()) { + systemDeviceListPtr->systemDevices[index].isConnect = true; + totalConnected++; + } + } else { + LOG_F(ERROR, + "DeviceConnect |Warn: {} is found in the client list but " + "not in pre-select system list", + internal::getDeviceNameFromList(index)); + } + } + + if (totalConnected >= connectedDevice) + break; + // qApp->processEvents(); + } + + if (timer.elapsed() > timeoutMs) { + LOG_F(ERROR, "DeviceConnect | ERROR: Connect time exceed (ms): {}", + timeoutMs); + messageBusPtr->publish("main", + "ConnectFailed:Device connected timeout."); + } else { + LOG_F(INFO, "DeviceConnect | Device connected success"); + } + if (systemDeviceListPtr->systemDevices[0].isConnect) { + AddPtr( + Constants::MAIN_TELESCOPE, + std::static_pointer_cast( + systemDeviceListPtr->systemDevices[0].driver)); + } + if (systemDeviceListPtr->systemDevices[1].isConnect) { + AddPtr(Constants::MAIN_GUIDER, + std::static_pointer_cast( + systemDeviceListPtr->systemDevices[1].driver)); + } + if (systemDeviceListPtr->systemDevices[2].isConnect) { + AddPtr( + Constants::MAIN_FILTERWHEEL, + std::static_pointer_cast( + systemDeviceListPtr->systemDevices[2].driver)); + } + if (systemDeviceListPtr->systemDevices[20].isConnect) { + AddPtr(Constants::MAIN_CAMERA, + std::static_pointer_cast( + systemDeviceListPtr->systemDevices[20].driver)); + } + if (systemDeviceListPtr->systemDevices[22].isConnect) { + AddPtr(Constants::MAIN_FOCUSER, + std::static_pointer_cast( + systemDeviceListPtr->systemDevices[22].driver)); + } + // printSystemDeviceList(systemDeviceListPtr->systemDevicesiceConnect(); +} + +void initINDIServer() { + atom::system::executeCommandSimple("pkill indiserver"); + atom::system::executeCommandSimple("rm -f /tmp/myFIFO"); + atom::system::executeCommandSimple("mkfifo /tmp/myFIFO"); + std::shared_ptr processManager; + GET_OR_CREATE_PTR(processManager, atom::system::ProcessManager, + Constants::PROCESS_MANAGER) + processManager->createProcess("indiserver -v -p 7624 -f /tmp/myFIFO", + "indiserver"); +} + +void usbCheck() { + std::string base = "/media/"; + std::shared_ptr env; + GET_OR_CREATE_PTR(env, atom::utils::Env, Constants::ENVIRONMENT) + std::string username = env->getEnv("USER"); + std::string basePath = base + username; + std::string usbMountPoint; + + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + + if (!fs::exists(basePath)) { + LOG_F(ERROR, "Base directory does not exist."); + return; + } + + std::vector folderList; + for (const auto& entry : fs::directory_iterator(basePath)) { + if (entry.is_directory() && entry.path().filename() != "CDROM") { + folderList.push_back(entry.path().filename().string()); + } + } + + if (folderList.size() == 1) { + usbMountPoint = basePath + "/" + folderList.at(0); + LOG_F(INFO, "USB mount point: {}", usbMountPoint); + std::string usbName = folderList.at(0); + std::string message = "USBCheck"; + auto disks = atom::system::getDiskUsage(); + long long remainingSpace; + for (const auto& disk : disks) { + if (disk.first == usbMountPoint) { + remainingSpace = disk.second; + } + } + if (remainingSpace == -1) { + LOG_F(ERROR, "Remaining space is -1. Check the USB drive."); + return; + } + message += ":" + usbName + "," + std::to_string(remainingSpace); + LOG_F(INFO, "USBCheck: {}", message); + messageBusPtr->publish("main", message); + + } else if (folderList.empty()) { + LOG_F(INFO, "No USB drive found."); + messageBusPtr->publish("main", "USBCheck:Null, Null"); + + } else { + LOG_F(INFO, "Multiple USB drives found."); + messageBusPtr->publish("main", "USBCheck:Multiple, Multiple"); + } +} + +void getGPIOsStatus() { + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + + const std::vector> gpioPins = {{1, GPIO_PIN_1}, + {2, GPIO_PIN_2}}; + + for (const auto& [id, pin] : gpioPins) { + atom::system::GPIO gpio(pin); + int value = static_cast(gpio.getValue()); + messageBusPtr->publish("main", + "OutPutPowerStatus:{}:{}"_fmt(id, value)); + } +} + +void switchOutPutPower(int id) { + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + + const std::vector> gpioPins = {{1, GPIO_PIN_1}, + {2, GPIO_PIN_2}}; + + auto it = std::find_if(gpioPins.begin(), gpioPins.end(), + [id](const auto& pair) { return pair.first == id; }); + + if (it != gpioPins.end()) { + atom::system::GPIO gpio(it->second); + bool newValue = !gpio.getValue(); + gpio.setValue(newValue); + messageBusPtr->publish("main", + "OutPutPowerStatus:{}:{}"_fmt(id, newValue)); + } +} +} // namespace lithium::middleware \ No newline at end of file diff --git a/src/server/middleware/indi_server.hpp b/src/server/middleware/indi_server.hpp new file mode 100644 index 00000000..16d17ef2 --- /dev/null +++ b/src/server/middleware/indi_server.hpp @@ -0,0 +1,25 @@ +#ifndef LITHIUM_SERVER_MIDDLEWARE_INDI_SERVER_HPP +#define LITHIUM_SERVER_MIDDLEWARE_INDI_SERVER_HPP + +#include + +#include "device/basic.hpp" + +namespace lithium::middleware { +auto indiDriverConfirm(const std::string& driverName) -> bool; +void indiDeviceConfirm(const std::string& deviceName, + const std::string& driverName); +void printDevGroups2(const device::DriversList& driversList, int ListNum, + const std::string& group); +void indiCapture(int expTime); +void indiAbortCapture(); +auto setFocusSpeed(int speed) -> int; +auto focusMoveAndCalHFR(bool isInward, int steps) -> double; +void autofocus(); +void usbCheck(); +void deviceConnect(); +void getGPIOsStatus(); +void switchOutPutPower(int id); +} // namespace lithium::middleware + +#endif \ No newline at end of file diff --git a/src/server/rooms/Peer.cpp b/src/server/rooms/Peer.cpp index b777b12f..59ebff7d 100644 --- a/src/server/rooms/Peer.cpp +++ b/src/server/rooms/Peer.cpp @@ -1,13 +1,27 @@ #include "Peer.hpp" +#include #include #include "Room.hpp" -#include "base/Log.hpp" +#include "async/message_bus.hpp" +#include "config/configor.hpp" #include "dto/DTOs.hpp" #include "oatpp/encoding/Base64.hpp" +#include "middleware/indi_server.hpp" + +#include "matchit/matchit.h" + +#include "atom/error/exception.hpp" +#include "atom/function/global_ptr.hpp" +#include "atom/log/loguru.hpp" #include "atom/type/json.hpp" +#include "atom/utils/print.hpp" +#include "atom/utils/string.hpp" + +#include "utils/constant.hpp" + using json = nlohmann::json; void Peer::sendMessageAsync(const oatpp::Object& message) { @@ -218,258 +232,232 @@ auto Peer::handleFileChunkMessage(const oatpp::Object& message) auto Peer::handleQTextMessage(const std::string& message) -> oatpp::async::CoroutineStarter { - std::vector parts; - std::stringstream ss(message); - std::string part; - while (std::getline(ss, part, ':')) { - parts.push_back(part); - } - - auto trim = [](std::string& s) -> std::string { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { - return !std::isspace(ch); - })); - s.erase(std::find_if(s.rbegin(), s.rend(), - [](int ch) { return !std::isspace(ch); }) - .base(), - s.end()); - return s; - }; - - /* - if (parts.size() == 2 && trim(parts[0]) == "ConfirmIndiDriver") { - std::string driverName = trim(parts[1]); - indi_Driver_Confirm(driverName); - } else if (parts.size() == 2 && trim(parts[0]) == "ConfirmIndiDevice") { - std::string deviceName = trim(parts[1]); - indi_Device_Confirm(deviceName); - } else if (parts.size() == 3 && trim(parts[0]) == "SelectIndiDriver") { - std::string Group = trim(parts[1]); - int ListNum = std::stoi(trim(parts[2])); - printDevGroups2(drivers_list, ListNum, Group); - } else if (parts.size() == 2 && trim(parts[0]) == "takeExposure") { - int ExpTime = std::stoi(trim(parts[1])); - std::cout << ExpTime << std::endl; - INDI_Capture(ExpTime); - glExpTime = ExpTime; - } else if (parts.size() == 2 && trim(parts[0]) == "focusSpeed") { - int Speed = std::stoi(trim(parts[1])); - std::cout << Speed << std::endl; - int Speed_ = FocuserControl_setSpeed(Speed); - wsThread->sendMessageToClient("FocusChangeSpeedSuccess:" + - std::to_string(Speed_)); } else if (parts.size() == 3 && trim(parts[0]) == - "focusMove") { std::string LR = trim(parts[1]); int Steps = - std::stoi(trim(parts[2])); if (LR == "Left") { FocusMoveAndCalHFR(true, - Steps); } else if (LR == "Right") { FocusMoveAndCalHFR(false, Steps); } else - if (LR == "Target") { FocusGotoAndCalFWHM(Steps); - } - } else if (parts.size() == 5 && trim(parts[0]) == "RedBox") { - int x = std::stoi(trim(parts[1])); - int y = std::stoi(trim(parts[2])); - int width = std::stoi(trim(parts[3])); - int height = std::stoi(trim(parts[4])); - glROI_x = x; - glROI_y = y; - CaptureViewWidth = width; - CaptureViewHeight = height; - std::cout << "RedBox:" << glROI_x << glROI_y << CaptureViewWidth << - CaptureViewHeight << std::endl; } else if (parts.size() == 2 && - trim(parts[0]) == "RedBoxSizeChange") { BoxSideLength = - std::stoi(trim(parts[1])); std::cout << "BoxSideLength:" << BoxSideLength << - std::endl; wsThread->sendMessageToClient("MainCameraSize:" + - std::to_string(glMainCCDSizeX) + ":" + std::to_string(glMainCCDSizeY)); } - else if (message == "AutoFocus") { AutoFocus(); } else if (message == - "StopAutoFocus") { StopAutoFocus = true; } else if (message == - "abortExposure") { INDI_AbortCapture(); } else if (message == - "connectAllDevice") { DeviceConnect(); } else if (message == "CS") { - // std::string Dev = connectIndiServer(); - // websocket->messageSend("AddDevice:" + Dev); - } else if (message == "DS") { - disconnectIndiServer(); - } else if (message == "MountMoveWest") { - if (dpMount != NULL) { - indi_Client->setTelescopeMoveWE(dpMount, "WEST"); - } - } else if (message == "MountMoveEast") { - if (dpMount != NULL) { - indi_Client->setTelescopeMoveWE(dpMount, "EAST"); - } - } else if (message == "MountMoveNorth") { - if (dpMount != NULL) { - indi_Client->setTelescopeMoveNS(dpMount, "NORTH"); - } - } else if (message == "MountMoveSouth") { - if (dpMount != NULL) { - indi_Client->setTelescopeMoveNS(dpMount, "SOUTH"); - } - } else if (message == "MountMoveAbort") { - if (dpMount != NULL) { - indi_Client->setTelescopeAbortMotion(dpMount); - } - } else if (message == "MountPark") { - if (dpMount != NULL) { - bool isPark = TelescopeControl_Park(); - if (isPark) { - wsThread->sendMessageToClient("TelescopePark:ON"); - } else { - wsThread->sendMessageToClient("TelescopePark:OFF"); - } - } - } else if (message == "MountTrack") { - if (dpMount != NULL) { - bool isTrack = TelescopeControl_Track(); - if (isTrack) { - wsThread->sendMessageToClient("TelescopeTrack:ON"); - } else { - wsThread->sendMessageToClient("TelescopeTrack:OFF"); - } - } - } else if (message == "MountHome") { - if (dpMount != NULL) { - indi_Client->setTelescopeHomeInit(dpMount, "SLEWHOME"); - } - } else if (message == "MountSYNC") { - if (dpMount != NULL) { - indi_Client->setTelescopeHomeInit(dpMount, "SYNCHOME"); - } - } else if (parts.size() == 2 && trim(parts[0]) == "MountSpeedSet") { - int Speed = std::stoi(trim(parts[1])); - std::cout << "MountSpeedSet:" << Speed << std::endl; - if (dpMount != NULL) { - indi_Client->setTelescopeSlewRate(dpMount, Speed - 1); - int Speed_; - indi_Client->getTelescopeSlewRate(dpMount, Speed_); - wsThread->sendMessageToClient("MountSetSpeedSuccess:" + - std::to_string(Speed_)); - } - } else if (parts.size() == 2 && trim(parts[0]) == "ImageGainR") { - ImageGainR = std::stod(trim(parts[1])); - std::cout << "GainR is set to " << ImageGainR << std::endl; - } else if (parts.size() == 2 && trim(parts[0]) == "ImageGainB") { - ImageGainB = std::stod(trim(parts[1])); - std::cout << "GainB is set to " << ImageGainB << std::endl; - } else if (trim(parts[0]) == "ScheduleTabelData") { - ScheduleTabelData(message); - } else if (parts.size() == 4 && trim(parts[0]) == "MountGoto") { - std::vector RaDecList; - std::stringstream ss2(message); - std::string part2; - while (std::getline(ss2, part2, ',')) { - RaDecList.push_back(part2); - } - std::vector RaList; - std::stringstream ss3(RaDecList[0]); - while (std::getline(ss3, part2, ':')) { - RaList.push_back(part2); - } - std::vector DecList; - std::stringstream ss4(RaDecList[1]); - while (std::getline(ss4, part2, ':')) { - DecList.push_back(part2); - } - - double Ra_Rad = std::stod(trim(RaList[2])); - double Dec_Rad = std::stod(trim(DecList[1])); - - std::cout << "RaDec(Rad):" << Ra_Rad << "," << Dec_Rad << std::endl; - - double Ra_Hour = Tools::RadToHour(Ra_Rad); - double Dec_Degree = Tools::RadToDegree(Dec_Rad); - - MountGoto(Ra_Hour, Dec_Degree); - } else if (message == "StopSchedule") { - StopSchedule = true; - } else if (message == "CaptureImageSave") { - CaptureImageSave(); - } else if (message == "getConnectedDevices") { - getConnectedDevices(); - } else if (message == "getStagingImage") { - getStagingImage(); - } else if (trim(parts[0]) == "StagingScheduleData") { - isStagingScheduleData = true; - StagingScheduleData = message; - } else if (message == "getStagingScheduleData") { - getStagingScheduleData(); - } else if (trim(parts[0]) == "ExpTimeList") { - Tools::saveExpTimeList(message); - } else if (message == "getExpTimeList") { - std::string expTimeList = Tools::readExpTimeList(); - if (!expTimeList.empty()) { - wsThread->sendMessageToClient(expTimeList); - } - } else if (message == "getCaptureStatus") { - std::cout << "MainCameraStatu: " << glMainCameraStatu << std::endl; - if (glMainCameraStatu == "Exposuring") { - wsThread->sendMessageToClient("CameraInExposuring:True"); - } - } else if (parts.size() == 2 && trim(parts[0]) == "SetCFWPosition") { - int pos = std::stoi(trim(parts[1])); - if (dpCFW != NULL) { - indi_Client->setCFWPosition(dpCFW, pos); - wsThread->sendMessageToClient("SetCFWPositionSuccess:" + - std::to_string(pos)); std::cout << "Set CFW Position to " << pos << " - Success!!!" << std::endl; - } - } else if (parts.size() == 2 && trim(parts[0]) == "CFWList") { - if (dpCFW != NULL) { - Tools::saveCFWList(std::string(dpCFW->getDeviceName()), parts[1]); - } - } else if (message == "getCFWList") { - if (dpCFW != NULL) { - int min, max, pos; - indi_Client->getCFWPosition(dpCFW, pos, min, max); - wsThread->sendMessageToClient("CFWPositionMax:" + - std::to_string(max)); std::string cfwList = - Tools::readCFWList(std::string(dpCFW->getDeviceName())); if - (!cfwList.empty()) { wsThread->sendMessageToClient("getCFWList:" + cfwList); - } - } - } else if (message == "ClearCalibrationData") { - ClearCalibrationData = true; - std::cout << "ClearCalibrationData: " << ClearCalibrationData << - std::endl; } else if (message == "GuiderSwitch") { if (isGuiding) { - isGuiding = false; - call_phd_StopLooping(); - wsThread->sendMessageToClient("GuiderStatus:false"); - } else { - isGuiding = true; - if (ClearCalibrationData) { - ClearCalibrationData = false; - call_phd_ClearCalibration(); - } - call_phd_StartLooping(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - call_phd_AutoFindStar(); - call_phd_StartGuiding(); - wsThread->sendMessageToClient("GuiderStatus:true"); - } - } else if (parts.size() == 2 && trim(parts[0]) == "GuiderExpTimeSwitch") { - call_phd_setExposureTime(std::stoi(trim(parts[1]))); - } else if (message == "getGuiderStatus") { - if (isGuiding) { - wsThread->sendMessageToClient("GuiderStatus:true"); - } else { - wsThread->sendMessageToClient("GuiderStatus:false"); - } - } else if (parts.size() == 4 && trim(parts[0]) == "SolveSYNC") { - glFocalLength = std::stoi(trim(parts[1])); - glCameraSize_width = std::stod(trim(parts[2])); - glCameraSize_height = std::stod(trim(parts[3])); - TelescopeControl_SolveSYNC(); - } else if (message == "ClearDataPoints") { - dataPoints.clear(); - } else if (message == "ShowAllImageFolder") { - std::string allFile = GetAllFile(); - std::cout << allFile << std::endl; - wsThread->sendMessageToClient("ShowAllImageFolder:" + allFile); - } else if (parts.size() == 2 && trim(parts[0]) == "MoveFileToUSB") { - std::vector ImagePath = parseString(parts[1], - ImageSaveBasePath); RemoveImageToUsb(ImagePath); } else if (parts.size() == - 2 && trim(parts[0]) == "DeleteFile") { std::vector ImagePath = - parseString(parts[1], ImageSaveBasePath); DeleteImage(ImagePath); } else if - (message == "USBCheck") { USBCheck(); + std::vector parts = atom::utils::splitString(message, ':'); + // Check if the message is in the correct format + if (parts.size() != 2 || parts.size() != 3) { + LOG_F(ERROR, "Invalid message format. {}", message); + return onApiError("Invalid message format."); } - */ + parts[0] = atom::utils::trim(parts[0]); + + using namespace matchit; + using namespace lithium::middleware; + match(parts[0])( + pattern | "ConfirmIndiDriver" = + [parts] { + std::string driverName = atom::utils::trim(parts[1]); + indiDriverConfirm(driverName); + }, + pattern | "ConfirmIndiDevice" = + [parts] { + std::string deviceName = atom::utils::trim(parts[1]); + std::string driverName = atom::utils::trim(parts[2]); + indiDeviceConfirm(deviceName, driverName); + }, + pattern | "SelectIndiDriver" = + [parts] { + std::string driverName = atom::utils::trim(parts[1]); + int listNum = std::stoi(atom::utils::trim(parts[2])); + std::shared_ptr driversList; + GET_OR_CREATE_PTR(driversList, lithium::device::DriversList, + Constants::DRIVERS_LIST) + printDevGroups2(*driversList, listNum, driverName); + }, + pattern | "takeExposure" = + [parts] { + int expTime = std::stoi(atom::utils::trim(parts[1])); + LOG_F(INFO, "takeExposure: {}", expTime); + indiCapture(expTime); + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, lithium::ConfigManager, + Constants::CONFIG_MANAGER) + configManager->setValue( + "/lithium/device/camera/current_exposure", expTime); + }, + pattern | "focusSpeed" = + [parts] { + int speed = std::stoi(atom::utils::trim(parts[1])); + LOG_F(INFO, "focusSpeed: {}", speed); + int result = setFocusSpeed(speed); + LOG_F(INFO, "focusSpeed result: {}", result); + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + messageBusPtr->publish( + "main", "FocusChangeSpeedSuccess:{}"_fmt(result)); + }, + pattern | "focusMove" = + [parts] { + std::string direction = atom::utils::trim(parts[1]); + int steps = std::stoi(atom::utils::trim(parts[2])); + LOG_F(INFO, "focusMove: {} {}", direction, steps); + match(direction)( + pattern | "Left" = + [steps] { + LOG_F(INFO, "focusMove: Left {}", steps); + focusMoveAndCalHFR(true, steps); + }, + pattern | "Right" = + [steps] { + LOG_F(INFO, "focusMove: Right {}", steps); + focusMoveAndCalHFR(false, steps); + }, + pattern | "Target" = + [steps] { + LOG_F(INFO, "focusMove: Up {}", steps); + // TODO: Implement FocusGotoAndCalFWHM + }); + }, + pattern | "RedBox" = + [parts] { + int x = std::stoi(atom::utils::trim(parts[1])); + int y = std::stoi(atom::utils::trim(parts[2])); + int w = std::stoi(atom::utils::trim(parts[3])); + int h = std::stoi(atom::utils::trim(parts[4])); + LOG_F(INFO, "RedBox: {} {} {} {}", x, y, w, h); + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, lithium::ConfigManager, + Constants::CONFIG_MANAGER) + configManager->setValue("/lithium/device/camera/roi", + std::array({x, y})); + configManager->setValue("/lithium/device/camera/frame", + std::array({w, h})); + }, + pattern | "RedBoxSizeChange" = + [parts] { + int boxSideLength = std::stoi(atom::utils::trim(parts[1])); + LOG_F(INFO, "RedBoxSizeChange: {}", boxSideLength); + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, lithium::ConfigManager, + Constants::CONFIG_MANAGER) + configManager->setValue( + "/lithium/device/camera/box_side_length", boxSideLength); + auto [x, y] = + configManager->getValue("/lithium/device/camera/frame") + ->get>(); + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + messageBusPtr->publish("main", + "MainCameraSize:{}:{}"_fmt(x, y)); + }, + pattern | "AutoFocus" = + [parts] { + LOG_F(INFO, "Start AutoFocus"); + autofocus(); + }, + pattern | "StopAutoFocus" = + [parts] { + LOG_F(INFO, "Stop AutoFocus"); + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, lithium::ConfigManager, + Constants::CONFIG_MANAGER) + configManager->setValue("/lithium/device/focuser/auto_focus", + false); + }, + pattern | "abortExposure" = + [parts] { + LOG_F(INFO, "abortExposure"); + indiAbortCapture(); + }, + pattern | "connectAllDevice" = + [parts] { + LOG_F(INFO, "connectAllDevice"); + deviceConnect(); + }, + pattern | "CS" = [parts] { LOG_F(INFO, "CS"); }, + pattern | "disconnectAllDevice" = + [parts] { LOG_F(INFO, "disconnectAllDevice"); }, + pattern | "MountMoveWest" = [this, parts] {}, + pattern | "MountMoveEast" = [this, parts] {}, + pattern | "MountMoveNorth" = [this, parts] {}, + pattern | "MountMoveSouth" = [this, parts] {}, + pattern | "MountMoveAbort" = [this, parts] {}, + pattern | "MountPark" = [this, parts] {}, + pattern | "MountTrack" = [this, parts] {}, + pattern | "MountHome" = [this, parts] {}, + pattern | "MountSYNC" = [this, parts] {}, + pattern | "MountSpeedSwitch" = [this, parts] {}, + pattern | "ImageGainR" = [this, parts] {}, + pattern | "ImageGainB" = [this, parts] {}, + pattern | "ScheduleTabelData" = [this, parts] {}, + pattern | "MountGoto" = [this, parts] {}, + pattern | "StopSchedule" = [this, parts] {}, + pattern | "CaptureImageSave" = [this, parts] {}, + pattern | "getConnectedDevices" = [this, parts] {}, + pattern | "getStagingImage" = [this, parts] {}, + pattern | "StagingScheduleData" = [this, parts] {}, + pattern | "getStagingGuiderData" = [this, parts] {}, + pattern | "ExpTimeList" = [this, parts] {}, + pattern | "getExpTimeList" = [this, parts] {}, + pattern | "getCaptureStatus" = [this, parts] {}, + pattern | "SetCFWPosition" = [this, parts] {}, + pattern | "CFWList" = [this, parts] {}, + pattern | "getCFWList" = [this, parts] {}, + pattern | "ClearCalibrationData" = [this, parts] {}, + pattern | "GuiderSwitch" = [this, parts] {}, + pattern | "GuiderLoopExpSwitch" = [this, parts] {}, + pattern | "PHD2Recalibrate" = [this, parts] {}, + pattern | "GuiderExpTimeSwitch" = [this, parts] {}, + pattern | "SolveSYNC" = [this, parts] {}, + pattern | "ClearDataPoints" = [this, parts] {}, + pattern | "ShowAllImageFolder" = [this, parts] {}, + pattern | "MoveFileToUSB" = [this, parts] {}, + pattern | "DeleteFile" = [this, parts] {}, + pattern | "USBCheck" = + [parts] { + LOG_F(INFO, "USBCheck"); + usbCheck(); + }, + pattern | "SolveImage" = [this, parts] {}, + pattern | "startLoopSolveImage" = [this, parts] {}, + pattern | "stopLoopSolveImage" = [this, parts] {}, + pattern | "StartLoopCapture" = [this, parts] {}, + pattern | "StopLoopCapture" = [this, parts] {}, + pattern | "getStagingSolveResult" = [this, parts] {}, + pattern | "ClearSloveResultList" = [this, parts] {}, + pattern | "getOriginalImage" = [this, parts] {}, + pattern | "saveCurrentLocation" = + [parts] { + LOG_F(INFO, "saveCurrentLocation"); + double lat = std::stod(atom::utils::trim(parts[1])); + double lng = std::stod(atom::utils::trim(parts[2])); + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, lithium::ConfigManager, + Constants::CONFIG_MANAGER) + configManager->setValue("/lithium/location/lat", lat); + configManager->setValue("/lithium/location/lng", lng); + }, + pattern | "getCurrentLocation" = + [parts] { + LOG_F(INFO, "getCurrentLocation"); + std::shared_ptr configManager; + GET_OR_CREATE_PTR(configManager, lithium::ConfigManager, + Constants::CONFIG_MANAGER) + double lat = configManager->getValue("/lithium/location/lat") + ->get(); + double lng = configManager->getValue("/lithium/location/lng") + ->get(); + std::shared_ptr messageBusPtr; + GET_OR_CREATE_PTR(messageBusPtr, atom::async::MessageBus, + Constants::MESSAGE_BUS) + messageBusPtr->publish( + "main", "SetCurrentLocation:{}:{}"_fmt(lat, lng)); + }, + pattern | "getGPIOsStatus" = + [parts] { + LOG_F(INFO, "getGPIOsStatus"); + getGPIOsStatus(); + }, + pattern | "SwitchOutPutPower" = + [parts] { + LOG_F(INFO, "SwitchOutPutPower: {}", parts[1]); + int gpio = std::stoi(atom::utils::trim(parts[1])); + switchOutPutPower(gpio); + }, + pattern | "SetBinning" = [this, parts] {}, + pattern | "GuiderCanvasClick" = [this, parts] {}, + pattern | "getQTClientVersion" = [this, parts] {}); } auto Peer::handleTextMessage(const oatpp::Object& message) diff --git a/src/target/engine.cpp b/src/target/engine.cpp index 245bcf79..c42fe645 100644 --- a/src/target/engine.cpp +++ b/src/target/engine.cpp @@ -1,171 +1,253 @@ #include "engine.hpp" + #include +#include + +#include "atom/log/loguru.hpp" +#include "atom/search/lru.hpp" namespace lithium::target { -constexpr int CACHE_CAPACITY = 100; // 定义 CACHE_CAPACITY +constexpr int CACHE_CAPACITY = 100; + +/** + * @brief A Trie (prefix tree) for storing and searching strings. + * + * The Trie is used for efficient storage and retrieval of strings, particularly + * useful for tasks like auto-completion. + */ +class Trie { + struct alignas(128) TrieNode { + std::unordered_map children; ///< Children nodes. + bool isEndOfWord = false; ///< Flag indicating the end of a word. + }; -Trie::Trie() : root_(new TrieNode()) {} +public: + /** + * @brief Constructs an empty Trie. + */ + Trie(); + + /** + * @brief Destroys the Trie and frees allocated memory. + */ + ~Trie(); + + // Deleted copy constructor and copy assignment operator + Trie(const Trie&) = delete; + Trie& operator=(const Trie&) = delete; + + // Defaulted move constructor and move assignment operator + Trie(Trie&&) noexcept = default; + Trie& operator=(Trie&&) noexcept = default; + + /** + * @brief Inserts a word into the Trie. + * + * @param word The word to insert. + */ + void insert(const std::string& word); + + /** + * @brief Provides auto-complete suggestions based on a given prefix. + * + * @param prefix The prefix to search for. + * @return std::vector A vector of auto-complete suggestions. + */ + [[nodiscard]] auto autoComplete(const std::string& prefix) const + -> std::vector; + +private: + /** + * @brief Depth-first search to collect all words in the Trie starting with + * a given prefix. + * + * @param node The current TrieNode being visited. + * @param prefix The current prefix being formed. + * @param suggestions A vector to collect the suggestions. + */ + void dfs(TrieNode* node, const std::string& prefix, + std::vector& suggestions) const; + + /** + * @brief Recursively frees the memory allocated for Trie nodes. + * + * @param node The current TrieNode being freed. + */ + void clear(TrieNode* node); + + TrieNode* root_; ///< The root node of the Trie. +}; + +class SearchEngine::Impl { +public: + Impl() : queryCache_(CACHE_CAPACITY) { + LOG_F(INFO, "SearchEngine initialized with cache capacity {}", + CACHE_CAPACITY); + } -Trie::~Trie() { clear(root_); } + ~Impl() { LOG_F(INFO, "SearchEngine destroyed."); } -void Trie::insert(const std::string& word) { - TrieNode* node = root_; - for (char character : word) { - if (!node->children.contains(character)) { - node->children[character] = new TrieNode(); + void addStarObject(const StarObject& starObject) { + std::unique_lock lock(indexMutex_); + try { + starObjectIndex_.emplace(starObject.getName(), starObject); + trie_.insert(starObject.getName()); + for (const auto& alias : starObject.getAliases()) { + trie_.insert(alias); + } + LOG_F(INFO, "Added StarObject: {}", starObject.getName()); + } catch (const std::exception& e) { + LOG_F(ERROR, "Exception in addStarObject: {}", e.what()); } - node = node->children[character]; } - node->isEndOfWord = true; -} -auto Trie::autoComplete(const std::string& prefix) const - -> std::vector { - std::vector suggestions; - TrieNode* node = root_; - for (char character : prefix) { - if (!node->children.contains(character)) { - return suggestions; // 前缀不存在 - } - node = node->children[character]; - } - dfs(node, prefix, suggestions); - return suggestions; -} + std::vector searchStarObject(const std::string& query) const { + std::shared_lock lock(indexMutex_); + try { + if (auto cached = queryCache_.get(query)) { + LOG_F(INFO, "Cache hit for query: {}", query); + return *cached; + } -void Trie::dfs(TrieNode* node, const std::string& prefix, - std::vector& suggestions) const { - if (node->isEndOfWord) { - suggestions.push_back(prefix); - } - for (const auto& [character, childNode] : node->children) { - dfs(childNode, prefix + character, suggestions); + std::vector results; + for (const auto& [name, starObject] : starObjectIndex_) { + if (name == query || + std::any_of(starObject.getAliases().begin(), + starObject.getAliases().end(), + [&query](const std::string& alias) { + return alias == query; + })) { + results.push_back(starObject); + } + } + + queryCache_.put(query, results); + LOG_F(INFO, "Search completed for query: {}", query); + return results; + } catch (const std::exception& e) { + LOG_F(ERROR, "Exception in searchStarObject: {}", e.what()); + return {}; + } } -} -void Trie::clear(TrieNode* node) { - for (auto& [_, child] : node->children) { - clear(child); + std::vector fuzzySearchStarObject(const std::string& query, + int tolerance) const { + std::shared_lock lock(indexMutex_); + std::vector results; + try { + for (const auto& [name, starObject] : starObjectIndex_) { + if (levenshteinDistance(query, name) <= tolerance || + std::any_of(starObject.getAliases().begin(), + starObject.getAliases().end(), + [&query, tolerance](const std::string& alias) { + return levenshteinDistance(query, alias) <= + tolerance; + })) { + results.push_back(starObject); + } + } + LOG_F(INFO, + "Fuzzy search completed for query: {} with tolerance: {}", + query, tolerance); + return results; + } catch (const std::exception& e) { + LOG_F(ERROR, "Exception in fuzzySearchStarObject: {}", e.what()); + return {}; + } } - delete node; -} -SearchEngine::SearchEngine() : queryCache_(CACHE_CAPACITY) {} + std::vector autoCompleteStarObject( + const std::string& prefix) const { + try { + auto suggestions = trie_.autoComplete(prefix); + std::vector filteredSuggestions; -void SearchEngine::addStarObject(const StarObject& starObject) { - std::unique_lock lock(indexMutex_); - starObjectIndex_.emplace(starObject.getName(), starObject); + for (const auto& suggestion : suggestions) { + if (starObjectIndex_.find(suggestion) != + starObjectIndex_.end()) { + filteredSuggestions.push_back(suggestion); + } + } - trie_.insert(starObject.getName()); - for (const auto& alias : starObject.getAliases()) { - trie_.insert(alias); + LOG_F(INFO, "Auto-complete completed for prefix: {}", prefix); + return filteredSuggestions; + } catch (const std::exception& e) { + LOG_F(ERROR, "Exception in autoCompleteStarObject: {}", e.what()); + return {}; + } } -} -auto SearchEngine::searchStarObject(const std::string& query) const - -> std::vector { - std::shared_lock lock(indexMutex_); - if (auto cached = queryCache_.get(query)) { - return *cached; + static std::vector getRankedResultsStatic( + std::vector& results) { + std::sort(results.begin(), results.end(), + [](const StarObject& a, const StarObject& b) { + return a.getClickCount() > b.getClickCount(); + }); + LOG_F(INFO, "Results ranked by click count."); + return results; } - std::vector results; - auto searchFn = [&results, &query](const auto& pair) { - const auto& [name, starObject] = pair; - if (name == query || std::ranges::any_of(starObject.getAliases(), - [&query](const auto& alias) { - return alias == query; - })) { - results.push_back(starObject); - } - }; - - std::ranges::for_each(starObjectIndex_, searchFn); - - queryCache_.put(query, results); - return results; -} + static int levenshteinDistance(const std::string& str1, + const std::string& str2) { + const size_t len1 = str1.size(); + const size_t len2 = str2.size(); + std::vector> distanceMatrix( + len1 + 1, std::vector(len2 + 1)); -auto SearchEngine::fuzzySearchStarObject( - const std::string& query, int tolerance) const -> std::vector { - std::shared_lock lock(indexMutex_); - std::vector results; + for (size_t i = 0; i <= len1; ++i) { + distanceMatrix[i][0] = static_cast(i); + } + for (size_t j = 0; j <= len2; ++j) { + distanceMatrix[0][j] = static_cast(j); + } - auto searchFn = [&](const auto& pair) { - const auto& [name, starObject] = pair; - if (levenshteinDistance(query, name) <= tolerance) { - results.push_back(starObject); - } else { - for (const auto& alias : starObject.getAliases()) { - if (levenshteinDistance(query, alias) <= tolerance) { - results.push_back(starObject); - break; - } + for (size_t i = 1; i <= len1; ++i) { + for (size_t j = 1; j <= len2; ++j) { + int cost = (str1[i - 1] == str2[j - 1]) ? 0 : 1; + distanceMatrix[i][j] = std::min( + {distanceMatrix[i - 1][j] + 1, distanceMatrix[i][j - 1] + 1, + distanceMatrix[i - 1][j - 1] + cost}); } } - }; + return distanceMatrix[len1][len2]; + } - std::ranges::for_each(starObjectIndex_, searchFn); +private: + std::unordered_map starObjectIndex_; + Trie trie_; + mutable atom::search::ThreadSafeLRUCache> + queryCache_; + mutable std::shared_mutex indexMutex_; +}; - return results; -} +SearchEngine::SearchEngine() : pImpl_(std::make_unique()) {} -auto SearchEngine::autoCompleteStarObject(const std::string& prefix) const - -> std::vector { - auto suggestions = trie_.autoComplete(prefix); - - std::vector filteredSuggestions; - - auto filterFn = [&](const auto& suggestion) { - for (const auto& [name, starObject] : starObjectIndex_) { - if (name == suggestion || - std::ranges::any_of(starObject.getAliases(), - [&suggestion](const auto& alias) { - return alias == suggestion; - })) { - filteredSuggestions.push_back(suggestion); - break; - } - } - }; - - std::ranges::for_each(suggestions, filterFn); +SearchEngine::~SearchEngine() = default; - return filteredSuggestions; +void SearchEngine::addStarObject(const StarObject& starObject) { + pImpl_->addStarObject(starObject); } -auto SearchEngine::getRankedResults(std::vector& results) - -> std::vector { - std::ranges::sort(results, std::ranges::greater{}, - &StarObject::getClickCount); - return results; +std::vector SearchEngine::searchStarObject( + const std::string& query) const { + return pImpl_->searchStarObject(query); } -auto levenshteinDistance(const std::string& str1, - const std::string& str2) -> int { - const auto STR1_SIZE = str1.size(); // 将 size1 改为 str1Size - const auto STR2_SIZE = str2.size(); // 将 size2 改为 str2Size - std::vector> distanceMatrix( - STR1_SIZE + 1, std::vector(STR2_SIZE + 1)); +std::vector SearchEngine::fuzzySearchStarObject( + const std::string& query, int tolerance) const { + return pImpl_->fuzzySearchStarObject(query, tolerance); +} - for (size_t i = 0; i <= STR1_SIZE; i++) { - distanceMatrix[i][0] = static_cast(i); - } - for (size_t j = 0; j <= STR2_SIZE; j++) { - distanceMatrix[0][j] = static_cast(j); - } +std::vector SearchEngine::autoCompleteStarObject( + const std::string& prefix) const { + return pImpl_->autoCompleteStarObject(prefix); +} - for (size_t i = 1; i <= STR1_SIZE; i++) { - for (size_t j = 1; j <= STR2_SIZE; j++) { - const int EDIT_COST = - (str1[i - 1] == str2[j - 1]) ? 0 : 1; // 将 cost 改为 editCost - distanceMatrix[i][j] = std::min( - {distanceMatrix[i - 1][j] + 1, distanceMatrix[i][j - 1] + 1, - distanceMatrix[i - 1][j - 1] + EDIT_COST}); - } - } - return distanceMatrix[STR1_SIZE][STR2_SIZE]; +std::vector SearchEngine::getRankedResults( + std::vector& results) { + return Impl::getRankedResultsStatic(results); } -} // namespace lithium::target +} // namespace lithium::target \ No newline at end of file diff --git a/src/target/engine.hpp b/src/target/engine.hpp index 7cc7a970..c4a63557 100644 --- a/src/target/engine.hpp +++ b/src/target/engine.hpp @@ -1,200 +1,32 @@ #ifndef STAR_SEARCH_SEARCH_HPP #define STAR_SEARCH_SEARCH_HPP -#include -#include -#include -#include -#include +#include #include -#include #include -#include "atom/macro.hpp" - namespace lithium::target { -/** - * @brief A Least Recently Used (LRU) cache implementation. - * - * This class provides a thread-safe LRU cache that stores key-value pairs. - * When the cache reaches its capacity, the least recently used item is evicted. - * - * @tparam Key The type of keys used to access values in the cache. - * @tparam Value The type of values stored in the cache. - * @requires Key must be equality comparable. - */ -template - requires std::equality_comparable -class LRUCache { -private: - int capacity_; ///< The maximum number of elements the cache can hold. - std::list> - cacheList_; ///< List to maintain the order of items. - std::unordered_map>::iterator> - cacheMap_; ///< Map to store iterators pointing to elements in the - ///< list. - std::mutex cacheMutex_; ///< Mutex for thread-safe access. - -public: - /** - * @brief Constructs an LRUCache with the specified capacity. - * - * @param capacity The maximum number of elements the cache can hold. - */ - explicit LRUCache(int capacity) : capacity_(capacity) {} - - /** - * @brief Retrieves a value from the cache. - * - * If the key exists in the cache, the corresponding value is returned and - * the key is moved to the front of the list to mark it as recently used. - * If the key is not found, an empty optional is returned. - * - * @param key The key to search for. - * @return std::optional The value associated with the key, or - * std::nullopt if not found. - */ - auto get(const Key& key) -> std::optional { - std::lock_guard lock(cacheMutex_); - if (auto iter = cacheMap_.find(key); iter != cacheMap_.end()) { - cacheList_.splice(cacheList_.begin(), cacheList_, iter->second); - return iter->second->second; - } - return std::nullopt; - } - - /** - * @brief Inserts a key-value pair into the cache. - * - * If the key already exists, its value is updated and the key is moved to - * the front of the list. If the cache is full, the least recently used - * item is removed before inserting the new key-value pair. - * - * @param key The key to insert. - * @param value The value to insert. - */ - void put(const Key& key, const Value& value) { - std::lock_guard lock(cacheMutex_); - if (auto iter = cacheMap_.find(key); iter != cacheMap_.end()) { - cacheList_.splice(cacheList_.begin(), cacheList_, iter->second); - iter->second->second = value; - return; - } - - if (static_cast(cacheList_.size()) == capacity_) { - cacheMap_.erase(cacheList_.back().first); - cacheList_.pop_back(); - } - - cacheList_.emplace_front(key, value); - cacheMap_[key] = cacheList_.begin(); - } -}; - -/** - * @brief A Trie (prefix tree) for storing and searching strings. - * - * The Trie is used for efficient storage and retrieval of strings, particularly - * useful for tasks like auto-completion. - */ -class Trie { - struct alignas(128) TrieNode { - std::unordered_map children; ///< Children nodes. - bool isEndOfWord = false; ///< Flag indicating the end of a word. - }; - -public: - /** - * @brief Constructs an empty Trie. - */ - Trie(); - - /** - * @brief Destroys the Trie and frees allocated memory. - */ - ~Trie(); - - // Deleted copy constructor and copy assignment operator - Trie(const Trie&) = delete; - Trie& operator=(const Trie&) = delete; - - // Defaulted move constructor and move assignment operator - Trie(Trie&&) noexcept = default; - Trie& operator=(Trie&&) noexcept = default; - - /** - * @brief Inserts a word into the Trie. - * - * @param word The word to insert. - */ - void insert(const std::string& word); - - /** - * @brief Provides auto-complete suggestions based on a given prefix. - * - * @param prefix The prefix to search for. - * @return std::vector A vector of auto-complete suggestions. - */ - [[nodiscard]] auto autoComplete(const std::string& prefix) const - -> std::vector; - -private: - /** - * @brief Depth-first search to collect all words in the Trie starting with - * a given prefix. - * - * @param node The current TrieNode being visited. - * @param prefix The current prefix being formed. - * @param suggestions A vector to collect the suggestions. - */ - void dfs(TrieNode* node, const std::string& prefix, - std::vector& suggestions) const; - - /** - * @brief Recursively frees the memory allocated for Trie nodes. - * - * @param node The current TrieNode being freed. - */ - void clear(TrieNode* node); - - TrieNode* root_; ///< The root node of the Trie. -}; - /** * @brief Represents a star object with a name, aliases, and a click count. - * - * This structure is used to store information about celestial objects, - * including their name, possible aliases, and a click count which can be used - * to adjust search result rankings. */ -struct alignas(64) StarObject { +struct StarObject { private: - std::string name_; ///< The name of the star object. - std::vector - aliases_; ///< A list of aliases for the star object. - int clickCount_; ///< The number of times this object has been clicked, - ///< used for ranking. + std::string name_; + std::vector aliases_; + int clickCount_; public: - /** - * @brief Constructs a StarObject with a name, aliases, and an optional - * click count. - * - * @param name The name of the star object. - * @param aliases A list of aliases for the star object. - * @param clickCount The initial click count (default is 0). - */ StarObject(std::string name, std::initializer_list aliases, int clickCount = 0) : name_(std::move(name)), aliases_(aliases), clickCount_(clickCount) {} // Accessor methods - [[nodiscard]] auto getName() const -> const std::string& { return name_; } - [[nodiscard]] auto getAliases() const -> const std::vector& { + [[nodiscard]] const std::string& getName() const { return name_; } + [[nodiscard]] const std::vector& getAliases() const { return aliases_; } - [[nodiscard]] auto getClickCount() const -> int { return clickCount_; } + [[nodiscard]] int getClickCount() const { return clickCount_; } // Mutator methods void setName(const std::string& name) { name_ = name; } @@ -206,97 +38,26 @@ struct alignas(64) StarObject { /** * @brief A search engine for star objects. - * - * This class provides functionality to add star objects, search for them by - * name or alias, perform fuzzy searches, provide auto-complete suggestions, and - * rank search results by click count. */ class SearchEngine { -private: - std::unordered_map - starObjectIndex_; ///< Index of star objects by name. - Trie trie_; ///< Trie used for auto-completion. - mutable LRUCache> - queryCache_; ///< LRU cache to store recent search results. - mutable std::shared_mutex - indexMutex_; ///< Mutex to protect the star object index. - public: - /** - * @brief Constructs an empty SearchEngine. - */ SearchEngine(); + ~SearchEngine(); - /** - * @brief Adds a StarObject to the search engine's index. - * - * @param starObject The star object to add. - */ void addStarObject(const StarObject& starObject); - - /** - * @brief Searches for star objects by name or alias. - * - * The search is case-sensitive and returns all star objects whose name or - * aliases match the query. - * - * @param query The name or alias to search for. - * @return std::vector A vector of matching star objects. - */ - auto searchStarObject(const std::string& query) const - -> std::vector; - - /** - * @brief Performs a fuzzy search for star objects. - * - * The fuzzy search allows for a specified tolerance in the difference - * between the query and star object names/aliases using the Levenshtein - * distance. - * - * @param query The name or alias to search for. - * @param tolerance The maximum allowed Levenshtein distance for a match. - * @return std::vector A vector of matching star objects. - */ - auto fuzzySearchStarObject(const std::string& query, - int tolerance) const -> std::vector; - - /** - * @brief Provides auto-complete suggestions for star objects based on a - * prefix. - * - * @param prefix The prefix to search for. - * @return std::vector A vector of auto-complete suggestions. - */ - auto autoCompleteStarObject(const std::string& prefix) const - -> std::vector; - - /** - * @brief Sorts star objects by click count in descending order. - * - * This method is used to rank search results based on their popularity. - * - * @param results The vector of star objects to rank. - * @return std::vector A vector of ranked star objects. - */ - static auto getRankedResults(std::vector& results) - -> std::vector; + std::vector searchStarObject(const std::string& query) const; + std::vector fuzzySearchStarObject(const std::string& query, + int tolerance) const; + std::vector autoCompleteStarObject( + const std::string& prefix) const; + static std::vector getRankedResults( + std::vector& results); private: - /** - * @brief Calculates the Levenshtein distance between two strings. - * - * The Levenshtein distance is a measure of the similarity between two - * strings, defined as the minimum number of single-character edits required - * to change one word into the other. - * - * @param str1 The first string. - * @param str2 The second string. - * @return int The Levenshtein distance between the two strings. - */ - static auto levenshteinDistance(const std::string& str1, - const std::string& str2) -> int; + class Impl; + std::unique_ptr pImpl_; }; } // namespace lithium::target -#endif +#endif \ No newline at end of file diff --git a/src/target/reader.cpp b/src/target/reader.cpp index fc7299c2..bacdc256 100644 --- a/src/target/reader.cpp +++ b/src/target/reader.cpp @@ -35,10 +35,10 @@ class DictReader::Impl { encoding_(encoding), delimiter_(dialect_.delimiter) { // 初始化 delimiter_ if (fieldnames_.empty()) { - throw std::invalid_argument("字段名不能为空。"); + THROW_INVALID_ARGUMENT("字段名不能为空。"); } if (!detectDialect(input)) { - throw std::runtime_error("方言检测失败。"); + THROW_RUNTIME_ERROR("方言检测失败。"); } // 如果提供了字段名,跳过第一行头部 @@ -97,7 +97,8 @@ class DictReader::Impl { return false; } - [[nodiscard]] auto parseLine(const std::string& line) const -> std::vector { + [[nodiscard]] auto parseLine(const std::string& line) const + -> std::vector { std::vector result; std::string cell; bool insideQuotes = false; @@ -195,9 +196,8 @@ class DictWriter::Impl { } [[nodiscard]] auto needsQuotes(const std::string& field) const -> bool { - return field.find(dialect_.delimiter) != std::string::npos || - field.find(dialect_.quotechar) != std::string::npos || - field.find('\n') != std::string::npos; + return field.contains(dialect_.delimiter) || + field.contains(dialect_.quotechar) || field.contains('\n'); } [[nodiscard]] auto escape(const std::string& field) const -> std::string { diff --git a/src/utils/constant.hpp b/src/utils/constant.hpp index b8f602a4..d474e007 100644 --- a/src/utils/constant.hpp +++ b/src/utils/constant.hpp @@ -75,6 +75,17 @@ class Constants { DEFINE_LITHIUM_CONSTANT(DEVICE_LOADER) DEFINE_LITHIUM_CONSTANT(DEVICE_MANAGER) + // QHY Compatibility + DEFINE_LITHIUM_CONSTANT(DRIVERS_LIST) + DEFINE_LITHIUM_CONSTANT(SYSTEM_DEVICE_LIST) + DEFINE_LITHIUM_CONSTANT(IS_FOCUSING_LOOPING) + DEFINE_LITHIUM_CONSTANT(MAIN_TIMER) + DEFINE_LITHIUM_CONSTANT(MAIN_CAMERA) + DEFINE_LITHIUM_CONSTANT(MAIN_FOCUSER) + DEFINE_LITHIUM_CONSTANT(MAIN_FILTERWHEEL) + DEFINE_LITHIUM_CONSTANT(MAIN_GUIDER) + DEFINE_LITHIUM_CONSTANT(MAIN_TELESCOPE) + DEFINE_LITHIUM_CONSTANT(TASK_CONTAINER) DEFINE_LITHIUM_CONSTANT(TASK_SCHEDULER) DEFINE_LITHIUM_CONSTANT(TASK_POOL) diff --git a/tests/target/reader.cpp b/tests/target/reader.cpp new file mode 100644 index 00000000..369d60f4 --- /dev/null +++ b/tests/target/reader.cpp @@ -0,0 +1,104 @@ + +#include +#include + +#include "target/reader.hpp" + +using namespace lithium::target; + +// Test writing a CSV file +TEST(DictWriterTest, WriteCSV) { + std::ostringstream oss; + Dialect dialect; + DictWriter writer(oss, {"Name", "Age", "City"}, dialect, true); + + std::unordered_map row1 = { + {"Name", "Alice"}, {"Age", "30"}, {"City", "New York"}}; + std::unordered_map row2 = { + {"Name", "Bob"}, {"Age", "25"}, {"City", "Los Angeles"}}; + + writer.writeRow(row1); + writer.writeRow(row2); + + std::string expected = + "\"Name\",\"Age\",\"City\"\n\"Alice\",\"30\",\"New " + "York\"\n\"Bob\",\"25\",\"Los Angeles\"\n"; + ASSERT_EQ(oss.str(), expected); +} + +// Test reading a CSV file +TEST(DictReaderTest, ReadCSV) { + std::istringstream iss( + "\"Name\",\"Age\",\"City\"\n\"Alice\",\"30\",\"New " + "York\"\n\"Bob\",\"25\",\"Los Angeles\"\n"); + Dialect dialect; + DictReader reader(iss, {"Name", "Age", "City"}, dialect, Encoding::UTF8); + + std::unordered_map row; + ASSERT_TRUE(reader.next(row)); + ASSERT_EQ(row["Name"], "Alice"); + ASSERT_EQ(row["Age"], "30"); + ASSERT_EQ(row["City"], "New York"); + + ASSERT_TRUE(reader.next(row)); + ASSERT_EQ(row["Name"], "Bob"); + ASSERT_EQ(row["Age"], "25"); + ASSERT_EQ(row["City"], "Los Angeles"); + + ASSERT_FALSE(reader.next(row)); +} + +// Test writing and reading UTF16 encoded CSV file +TEST(DictWriterReaderTest, WriteReadUTF16CSV) { + std::ostringstream oss; + Dialect dialect; + DictWriter writer(oss, {"Name", "Age", "City"}, dialect, true, + Encoding::UTF16); + + std::unordered_map row1 = { + {"Name", "Alice"}, {"Age", "30"}, {"City", "New York"}}; + std::unordered_map row2 = { + {"Name", "Bob"}, {"Age", "25"}, {"City", "Los Angeles"}}; + + writer.writeRow(row1); + writer.writeRow(row2); + + std::string utf16_csv = oss.str(); + + std::istringstream iss(utf16_csv); + DictReader reader(iss, {"Name", "Age", "City"}, dialect, Encoding::UTF16); + + std::unordered_map row; + ASSERT_TRUE(reader.next(row)); + ASSERT_EQ(row["Name"], "Alice"); + ASSERT_EQ(row["Age"], "30"); + ASSERT_EQ(row["City"], "New York"); + + ASSERT_TRUE(reader.next(row)); + ASSERT_EQ(row["Name"], "Bob"); + ASSERT_EQ(row["Age"], "25"); + ASSERT_EQ(row["City"], "Los Angeles"); + + ASSERT_FALSE(reader.next(row)); +} + +// Test detecting dialect +TEST(DictReaderTest, DetectDialect) { + std::istringstream iss( + "Name;Age;City\nAlice;30;New York\nBob;25;Los Angeles\n"); + Dialect dialect; + DictReader reader(iss, {"Name", "Age", "City"}, dialect, Encoding::UTF8); + + std::unordered_map row; + ASSERT_TRUE(reader.next(row)); + ASSERT_EQ(row["Name"], "Alice"); + ASSERT_EQ(row["Age"], "30"); + ASSERT_EQ(row["City"], "New York"); + + ASSERT_TRUE(reader.next(row)); + ASSERT_EQ(row["Name"], "Bob"); + ASSERT_EQ(row["Age"], "25"); + ASSERT_EQ(row["City"], "Los Angeles"); + + ASSERT_FALSE(reader.next(row)); +} \ No newline at end of file