diff --git a/src/plugins/score-plugin-protocols/CMakeLists.txt b/src/plugins/score-plugin-protocols/CMakeLists.txt index 01cc3978d3..9f40ad9ee0 100644 --- a/src/plugins/score-plugin-protocols/CMakeLists.txt +++ b/src/plugins/score-plugin-protocols/CMakeLists.txt @@ -207,6 +207,20 @@ set(SIMPLEIO_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/SimpleIO/SimpleIOSpecificSettingsSerialization.cpp" ) +set(GPS_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSDevice.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSProtocolFactory.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSProtocolSettingsWidget.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSSpecificSettings.hpp" +) + +set(GPS_SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSDevice.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSProtocolFactory.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSProtocolSettingsWidget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSSpecificSettingsSerialization.cpp" +) + set(MAPPER_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Mapper/MapperDevice.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Mapper/MapperDevice.cpp" @@ -299,6 +313,14 @@ if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) list(APPEND SCORE_FEATURES_LIST protocol_simpleio) endif() +find_path(GPS_HEADER gps.h) +if(GPS_HEADER) + target_sources(${PROJECT_NAME} PRIVATE ${GPS_HDRS} ${GPS_SRCS}) + target_compile_definitions(${PROJECT_NAME} PRIVATE OSSIA_PROTOCOL_GPS) + target_link_libraries(${PROJECT_NAME} PRIVATE gps) + list(APPEND SCORE_FEATURES_LIST protocol_gps) +endif() + target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_PREFIX}::Core ${QT_PREFIX}::Widgets ${QT_PREFIX}::Network diff --git a/src/plugins/score-plugin-protocols/Protocols/GPS/GPSDevice.cpp b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSDevice.cpp new file mode 100644 index 0000000000..6e108409d6 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSDevice.cpp @@ -0,0 +1,264 @@ +#if defined(OSSIA_PROTOCOL_GPS) +#include "GPSDevice.hpp" + +#include "GPSSpecificSettings.hpp" + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +W_OBJECT_IMPL(Protocols::GPSDevice) + +namespace ossia::net +{ +class libgpsd +{ +public: + decltype(&::gps_open) open{}; + decltype(&::gps_close) close{}; + decltype(&::gps_send) send{}; + decltype(&::gps_read) read{}; + decltype(&::gps_hexdump) hexdump{}; + decltype(&::gps_hexpack) hexpack{}; + decltype(&::gps_unpack) unpack{}; + decltype(&::gps_waiting) waiting{}; + decltype(&::gps_stream) stream{}; + decltype(&::gps_mainloop) mainloop{}; + decltype(&::gps_data) data{}; + decltype(&::gps_errstr) errstr{}; + + static const libgpsd& instance() + { + static const libgpsd self; + return self; + } + +private: + dylib_loader library; + + libgpsd() + : library("libgps.so.30") + { + open = library.symbol("gps_open"); + close = library.symbol("gps_close"); + send = library.symbol("gps_send"); + read = library.symbol("gps_read"); + hexdump = library.symbol("gps_hexdump"); + hexpack = library.symbol("gps_hexpack"); + unpack = library.symbol("gps_unpack"); + waiting = library.symbol("gps_waiting"); + stream = library.symbol("gps_stream"); + mainloop = library.symbol("gps_mainloop"); + data = library.symbol("gps_data"); + errstr = library.symbol("gps_errstr"); + + assert(open); + assert(close); + assert(send); + assert(read); + assert(hexdump); + assert(hexpack); + assert(unpack); + assert(waiting); + assert(stream); + assert(mainloop); + assert(data); + assert(errstr); + } +}; + +struct gps_protocol : public ossia::net::protocol_base +{ +public: + gps_protocol( + const libgpsd& instance, const Protocols::GPSSpecificSettings& set, + ossia::net::network_context_ptr ctx) + : protocol_base{flags{}} + , gps{instance} + , m_context{std::move(ctx)} + , m_timer{m_context->context} + { + using namespace std::literals; + const int ret = gps.open( + set.host.toStdString().c_str(), std::to_string(set.port).c_str(), &data); + if(ret != 0) + throw std::runtime_error("Could not open gpsd client: "s + gps.errstr(ret)); + + constexpr float frequency = 50; + m_timer.set_delay(std::chrono::milliseconds{ + static_cast(1000.0f / static_cast(frequency))}); + } + + ~gps_protocol() + { + // When you are done... + gps.stream(&data, WATCH_DISABLE, nullptr); + gps.close(&data); + m_timer.stop(); + } + + void set_device(ossia::net::device_base& dev) override + { + m_device = &dev; + + auto& root = m_device->get_root_node(); + + params.fix = ossia::create_parameter(root, "/gps/fix", "bool"); + params.fix_mode = ossia::create_parameter(root, "/gps/fix/mode", "string"); + params.lat = ossia::create_parameter(root, "/gps/lat", "float"); + params.lon = ossia::create_parameter(root, "/gps/lon", "float"); + params.satellites = ossia::create_parameter(root, "/gps/sat", "float"); + params.time = ossia::create_parameter(root, "/gps/time", "time"); + params.time_year = ossia::create_parameter(root, "/gps/time/year", "int"); + params.time_month = ossia::create_parameter(root, "/gps/time/month", "int"); + params.time_day = ossia::create_parameter(root, "/gps/time/day", "int"); + params.time_hour = ossia::create_parameter(root, "/gps/time/hour", "int"); + params.time_minute = ossia::create_parameter(root, "/gps/time/minute", "int"); + params.time_second = ossia::create_parameter(root, "/gps/time/second", "float"); + + (void)gps.stream(&data, WATCH_ENABLE | WATCH_JSON, nullptr); + m_timer.start([this] { update_function(); }); + } + + bool pull(parameter_base& v) override { return false; } + + bool push(const parameter_base& p, const value& v) override { return false; } + bool push_raw(const full_parameter_data&) override { return false; } + bool observe(parameter_base&, bool) override { return false; } + bool update(node_base& node_base) override { return false; } + + void update_function() + { + static constexpr const char* mode_str[4] = {"n/a", "None", "2D", "3D"}; + + while(gps.waiting(&data, 1)) + { + if(-1 == gps.read(&data, nullptr, 0)) + { + // FIXME stop the timer? + return; + } + if(MODE_SET != (MODE_SET & data.set)) + { + // did not even get mode, nothing to see here + return; + } + if(0 > data.fix.mode || 4 <= data.fix.mode) + { + data.fix.mode = 0; + } + params.fix->push_value(data.fix.mode > 1); + params.fix_mode->push_value(std::string(mode_str[data.fix.mode])); + + params.satellites->push_value(data.satellites_visible); + if(TIME_SET == (TIME_SET & data.set)) + { + const auto time = data.fix.time.tv_sec + 1e-9 * data.fix.time.tv_nsec; + params.time->push_value(time); + const auto tm = localtime(&data.fix.time.tv_sec); + params.time_year->push_value(tm->tm_year + 1900); + params.time_month->push_value(tm->tm_mon + 1); + params.time_day->push_value(tm->tm_mday); + params.time_hour->push_value(tm->tm_hour); + params.time_minute->push_value(tm->tm_min); + params.time_second->push_value(tm->tm_sec + 1e-9 * data.fix.time.tv_nsec); + } + + if(std::isfinite(data.fix.latitude) && std::isfinite(data.fix.longitude)) + { + params.lat->push_value(data.fix.latitude); + params.lon->push_value(data.fix.longitude); + } + } + } + + const libgpsd& gps; + ossia::net::network_context_ptr m_context; + ossia::net::device_base* m_device{}; + gps_data_t data; + ossia::timer m_timer; + + struct + { + ossia::net::parameter_base* fix{}; + ossia::net::parameter_base* fix_mode{}; + ossia::net::parameter_base* lat{}; + ossia::net::parameter_base* lon{}; + ossia::net::parameter_base* satellites{}; + ossia::net::parameter_base* time{}; + ossia::net::parameter_base* time_year{}; + ossia::net::parameter_base* time_month{}; + ossia::net::parameter_base* time_day{}; + ossia::net::parameter_base* time_hour{}; + ossia::net::parameter_base* time_minute{}; + ossia::net::parameter_base* time_second{}; + } params; +}; +} +namespace Protocols +{ + +GPSDevice::GPSDevice( + const Device::DeviceSettings& settings, const ossia::net::network_context_ptr& ctx) + : OwningDeviceInterface{settings} + , m_ctx{ctx} +{ + m_capas.canRefreshTree = true; + m_capas.canAddNode = false; + m_capas.canRemoveNode = false; + m_capas.canRenameNode = false; + m_capas.canSetProperties = false; + m_capas.canSerialize = false; +} + +GPSDevice::~GPSDevice() { } + +bool GPSDevice::reconnect() +{ + disconnect(); + + try + { + const auto& set + = m_settings.deviceSpecificSettings.value(); + const auto& gps = ossia::net::libgpsd::instance(); + { + auto pproto = std::make_unique(gps, set, m_ctx); + auto& proto = *pproto; + auto dev = std::make_unique( + std::move(pproto), settings().name.toStdString()); + m_dev = std::move(dev); + } + deviceChanged(nullptr, m_dev.get()); + } + catch(const std::runtime_error& e) + { + qDebug() << "GPS error: " << e.what(); + } + catch(...) + { + qDebug() << "GPS error"; + } + + return connected(); +} + +void GPSDevice::disconnect() +{ + OwningDeviceInterface::disconnect(); +} +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/GPS/GPSDevice.hpp b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSDevice.hpp new file mode 100644 index 0000000000..6eed63467d --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSDevice.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#if defined(OSSIA_PROTOCOL_GPS) +#include + +namespace Protocols +{ +class GPSDevice final : public Device::OwningDeviceInterface +{ + + W_OBJECT(GPSDevice) +public: + GPSDevice( + const Device::DeviceSettings& settings, + const ossia::net::network_context_ptr& ctx); + ~GPSDevice(); + + bool reconnect() override; + void disconnect() override; + +private: + const ossia::net::network_context_ptr& m_ctx; +}; +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolFactory.cpp b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolFactory.cpp new file mode 100644 index 0000000000..bf760e82f7 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolFactory.cpp @@ -0,0 +1,78 @@ +#include +#if defined(OSSIA_PROTOCOL_GPS) +#include "GPSDevice.hpp" +#include "GPSProtocolFactory.hpp" +#include "GPSProtocolSettingsWidget.hpp" +#include "GPSSpecificSettings.hpp" + +#include + +#include + +#include +#include + +#include +#include +#include + +namespace Protocols +{ + +QString GPSProtocolFactory::prettyName() const noexcept +{ + return QObject::tr("GPS"); +} + +QString GPSProtocolFactory::category() const noexcept +{ + return StandardCategories::hardware; +} + +Device::DeviceInterface* GPSProtocolFactory::makeDevice( + const Device::DeviceSettings& settings, const Explorer::DeviceDocumentPlugin& plugin, + const score::DocumentContext& ctx) +{ + return new GPSDevice{settings, plugin.networkContext()}; +} + +const Device::DeviceSettings& GPSProtocolFactory::defaultSettings() const noexcept +{ + static const Device::DeviceSettings& settings = [&]() { + Device::DeviceSettings s; + s.protocol = concreteKey(); + s.name = "gps"; + GPSSpecificSettings settings; + settings.host = "127.0.0.1"; + settings.port = 2947; + s.deviceSpecificSettings = QVariant::fromValue(settings); + return s; + }(); + + return settings; +} + +Device::ProtocolSettingsWidget* GPSProtocolFactory::makeSettingsWidget() +{ + return new GPSProtocolSettingsWidget; +} + +QVariant +GPSProtocolFactory::makeProtocolSpecificSettings(const VisitorVariant& visitor) const +{ + return makeProtocolSpecificSettings_T(visitor); +} + +void GPSProtocolFactory::serializeProtocolSpecificSettings( + const QVariant& data, const VisitorVariant& visitor) const +{ + serializeProtocolSpecificSettings_T(data, visitor); +} + +bool GPSProtocolFactory::checkCompatibility( + const Device::DeviceSettings& a, const Device::DeviceSettings& b) const noexcept +{ + return false; // TODO +} +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolFactory.hpp b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolFactory.hpp new file mode 100644 index 0000000000..d6ef4c48cc --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolFactory.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#if defined(OSSIA_PROTOCOL_GPS) +#include + +namespace Protocols +{ + +class GPSProtocolFactory final : public DefaultProtocolFactory +{ + SCORE_CONCRETE("63cc4e26-b15b-41f9-860f-2b410c86e177") + + QString prettyName() const noexcept override; + QString category() const noexcept override; + + Device::DeviceInterface* makeDevice( + const Device::DeviceSettings& settings, const Explorer::DeviceDocumentPlugin& plug, + const score::DocumentContext& ctx) override; + + const Device::DeviceSettings& defaultSettings() const noexcept override; + + Device::ProtocolSettingsWidget* makeSettingsWidget() override; + + QVariant makeProtocolSpecificSettings(const VisitorVariant& visitor) const override; + + void serializeProtocolSpecificSettings( + const QVariant& data, const VisitorVariant& visitor) const override; + + bool checkCompatibility( + const Device::DeviceSettings& a, + const Device::DeviceSettings& b) const noexcept override; +}; +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolSettingsWidget.cpp b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolSettingsWidget.cpp new file mode 100644 index 0000000000..8b9357d5d0 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolSettingsWidget.cpp @@ -0,0 +1,84 @@ +#include "libsimpleio/libgpio.h" +#include +#if defined(OSSIA_PROTOCOL_GPS) +#include "GPSProtocolFactory.hpp" +#include "GPSProtocolSettingsWidget.hpp" +#include "GPSSpecificSettings.hpp" + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +W_OBJECT_IMPL(Protocols::GPSProtocolSettingsWidget) + +namespace Protocols +{ +GPSProtocolSettingsWidget::GPSProtocolSettingsWidget(QWidget* parent) + : Device::ProtocolSettingsWidget(parent) +{ + m_deviceNameEdit = new State::AddressFragmentLineEdit{this}; + m_deviceNameEdit->setText("GPS"); + + m_host = new QLineEdit{"127.0.0.1", this}; + m_port = new QSpinBox{this}; + m_port->setRange(0, 65535); + m_port->setValue(2947); + + auto layout = new QFormLayout; + layout->addRow(tr("Name"), m_deviceNameEdit); + layout->addRow(tr("Host"), m_host); + layout->addRow(tr("Port"), m_port); + + setLayout(layout); +} + +GPSProtocolSettingsWidget::~GPSProtocolSettingsWidget() { } + +Device::DeviceSettings GPSProtocolSettingsWidget::getSettings() const +{ + // TODO should be = m_settings to follow the other patterns. + Device::DeviceSettings s; + s.name = m_deviceNameEdit->text(); + s.protocol = GPSProtocolFactory::static_concreteKey(); + + GPSSpecificSettings settings{}; + settings.host = this->m_host->text(); + settings.port = this->m_port->value(); + s.deviceSpecificSettings = QVariant::fromValue(settings); + + return s; +} + +void GPSProtocolSettingsWidget::setSettings(const Device::DeviceSettings& settings) +{ + m_deviceNameEdit->setText(settings.name); + const auto& specif = settings.deviceSpecificSettings.value(); + m_host->setText(specif.host); + m_port->setValue(specif.port); +} +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolSettingsWidget.hpp b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolSettingsWidget.hpp new file mode 100644 index 0000000000..be61ea9819 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSProtocolSettingsWidget.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#if defined(OSSIA_PROTOCOL_GPS) + +#include +#include + +#include + +#include + +class QLineEdit; +class QSpinBox; +class QTableWidget; +class QPushButton; + +namespace Protocols +{ + +class GPSProtocolSettingsWidget final : public Device::ProtocolSettingsWidget +{ + W_OBJECT(GPSProtocolSettingsWidget) + +public: + GPSProtocolSettingsWidget(QWidget* parent = nullptr); + virtual ~GPSProtocolSettingsWidget(); + Device::DeviceSettings getSettings() const override; + void setSettings(const Device::DeviceSettings& settings) override; + +private: + QLineEdit* m_deviceNameEdit{}; + QLineEdit* m_host{}; + QSpinBox* m_port{}; +}; +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/GPS/GPSSpecificSettings.hpp b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSSpecificSettings.hpp new file mode 100644 index 0000000000..09f9e87d42 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSSpecificSettings.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#if defined(OSSIA_PROTOCOL_GPS) +#include + +#include + +#include + +#include +#include +#include + +namespace Protocols +{ +struct GPSSpecificSettings +{ + QString host; + int port{}; +}; +} + +Q_DECLARE_METATYPE(Protocols::GPSSpecificSettings) +W_REGISTER_ARGTYPE(Protocols::GPSSpecificSettings) +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/GPS/GPSSpecificSettingsSerialization.cpp b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSSpecificSettingsSerialization.cpp new file mode 100644 index 0000000000..cfed8fca48 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/GPS/GPSSpecificSettingsSerialization.cpp @@ -0,0 +1,36 @@ +#include +#if defined(OSSIA_PROTOCOL_GPS) +#include "GPSSpecificSettings.hpp" + +#include +#include +#include + +template <> +void DataStreamReader::read(const Protocols::GPSSpecificSettings& n) +{ + m_stream << n.host << n.port; + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Protocols::GPSSpecificSettings& n) +{ + m_stream >> n.host >> n.port; + checkDelimiter(); +} + +template <> +void JSONReader::read(const Protocols::GPSSpecificSettings& n) +{ + obj["Host"] = n.host; + obj["Port"] = n.port; +} + +template <> +void JSONWriter::write(Protocols::GPSSpecificSettings& n) +{ + n.host <<= obj["Host"]; + n.port <<= obj["Port"]; +} +#endif diff --git a/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp b/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp index 7ffd9ab493..a32f01d3f1 100644 --- a/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp +++ b/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp @@ -56,6 +56,9 @@ #if defined(OSSIA_PROTOCOL_SIMPLEIO) #include #endif +#if defined(OSSIA_PROTOCOL_GPS) +#include +#endif #include @@ -135,6 +138,10 @@ std::vector score_plugin_protocols::factories( , Protocols::SimpleIOProtocolFactory #endif +#if defined(OSSIA_PROTOCOL_GPS) + , + Protocols::GPSProtocolFactory +#endif #if defined(OSSIA_PROTOCOL_LIBMAPPER) , Protocols::LibmapperClientProtocolFactory