diff --git a/CMakeLists.txt b/CMakeLists.txt index a49f6e5..876d098 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,27 +18,31 @@ find_package(GTest REQUIRED) find_package(cppcheck REQUIRED) find_package(magic_enum REQUIRED) +if (CMAKE_SYSTEM_NAME MATCHES "Windows" OR CMAKE_SYSTEM_NAME MATCHES "Linux" AND NOT BUILD_SHARED_LIBS) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-allow-multiple-definition") +endif() if (CMAKE_SYSTEM_NAME MATCHES "Windows") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-allow-multiple-definition") set(CMAKE_CROSSCOMPILING_EMULATOR "wine") endif() set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) add_library(${LIBRARY_NAME}) add_subdirectory(source) #add_subdirectory(test) -option(BUILD_EXAMPLES "Build examples" ON) +option(BUILD_EXAMPLES "Build examples" OFF) +if(BUILD_EXAMPLES) add_subdirectory(examples) +endif() target_include_directories(${LIBRARY_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_BINARY_DIR} ) - target_link_libraries(${LIBRARY_NAME} $<$>,$>:PahoMqttCpp::paho-mqttpp3-static> $<$,$>>:PahoMqttCpp::paho-mqttpp3> @@ -47,8 +51,7 @@ target_link_libraries(${LIBRARY_NAME} magic_enum::magic_enum ) - set_target_properties(${LIBRARY_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} -) \ No newline at end of file +) diff --git a/conanfile.py b/conanfile.py index 3e51033..4932759 100644 --- a/conanfile.py +++ b/conanfile.py @@ -12,7 +12,7 @@ class PzaCxx(ConanFile): } default_options = { "shared": True, - "build_examples": True + "build_examples": False } generators = "CMakeDeps", "CMakeToolchain", "virtualrunenv" exports_sources = "CMakeLists.txt", "source/*", "version.h.in", "CHANGELOG.md", "test/*", "cmake/*", "examples/*", "LICENSE" diff --git a/examples/bps.cxx b/examples/bps.cxx index 4135c70..ff1b6b2 100644 --- a/examples/bps.cxx +++ b/examples/bps.cxx @@ -4,7 +4,7 @@ int main(void) { - pza::core::set_log_level(pza::core::log_level::debug); + pza::core::set_log_level(pza::core::log_level::trace); pza::client::ptr cli = std::make_shared("localhost", 1883); @@ -17,38 +17,26 @@ int main(void) if (cli->register_device(bps) == -1) return -1; - for (size_t i = 0; i < bps->get_num_channels(); i++) { + for (size_t i = 0; i < 1; i++) { auto bps_channel = bps->channel[i]; - auto mdr = [&](double value) { - spdlog::info("Voltage: {}", value); - }; - - bps_channel->voltmeter.add_measure_callback(mdr); - - bps_channel->voltmeter.remove_measure_callback(mdr); - - auto lol = [&](double value) { - spdlog::info("double: {}", value); - }; - - bps_channel->voltmeter.add_measure_callback(lol); - - bps_channel->voltmeter.remove_measure_callback(lol); - - spdlog::info("Channel {}:", i); - bps_channel->ctrl.set_voltage(-7.3); - bps_channel->ctrl.set_current(1.0); - bps_channel->ctrl.set_enable(false); - spdlog::info(" Voltage: {}", bps_channel->voltmeter.get_measure()); - spdlog::info(" Current: {}", bps_channel->ampermeter.get_measure()); - spdlog::info(" Enabled: {}", bps_channel->ctrl.get_enable()); - bps_channel->ctrl.set_enable(true); - bps_channel->ctrl.set_voltage(3.3); - bps_channel->ctrl.set_current(5.0); - spdlog::info(" Voltage: {}", bps_channel->voltmeter.get_measure()); - spdlog::info(" Current: {}", bps_channel->ampermeter.get_measure()); - spdlog::info(" Enabled: {}", bps_channel->ctrl.get_enable()); +// spdlog::info("Channel {}:", i); +// bps_channel->ctrl.set_voltage(-7.3); +// bps_channel->ctrl.set_current(1.0); +// bps_channel->ctrl.set_enable(false); +// spdlog::info(" Voltage: {}", bps_channel->voltmeter.get_measure()); +// spdlog::info(" Current: {}", bps_channel->ampermeter.get_measure()); +// spdlog::info(" Enabled: {}", bps_channel->ctrl.get_enable()); +// bps_channel->ctrl.set_enable(true); +// bps_channel->ctrl.set_voltage(3.3); +// bps_channel->ctrl.set_current(5.0); +// spdlog::info(" Voltage: {}", bps_channel->voltmeter.get_measure()); +// spdlog::info(" Current: {}", bps_channel->ampermeter.get_measure()); +// spdlog::info(" Enabled: {}", bps_channel->ctrl.get_enable()); + + //bps_channel->voltmeter.set_measure_polling_cycle(2); + //bps_channel->ampermeter.set_measure_polling_cycle(2); + bps_channel->ctrl.set_enable_polling_cycle(2); } spdlog::info("\n\nOK\n\n"); diff --git a/scripts/build.sh b/scripts/build.sh index 5777cd8..64d2cf1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -3,6 +3,7 @@ BUILD_DIR_LINUX="build" BUILD_DIR_WINDOWS="buildwin" BUILD_TYPE="Debug" +BUILD_EXAMPLES="False" SHARED="True" TARGET="Linux" BUILD_DIR=$BUILD_DIR_LINUX @@ -23,7 +24,7 @@ function install_deps { fi mkdir -p $BUILD_DIR cd $BUILD_DIR - conan install .. -o shared=$SHARED -s build_type=$BUILD_TYPE --build=missing -pr:b ../conan_profiles/x86_64_Linux $EXTRA_CONAN_ARGS + conan install .. -o shared=$SHARED -o build_examples=$BUILD_EXAMPLES -s build_type=$BUILD_TYPE --build=missing -pr:b ../conan_profiles/x86_64_Linux $EXTRA_CONAN_ARGS } function build { @@ -39,17 +40,18 @@ function clean { } function usage { - echo "Usage: $0 [-t ] [-r] [-s] [-d] [-c] [-h]" + echo "Usage: $0 [-t ] [-r] [-s] [-d] [-e] [-c] [-h]" echo " -t Target platform (Windows or Linux). Default is Linux" echo " -r Build in Release mode. Default is Debug" echo " -s Build in Static mode. Default is Shared" echo " -d Install dependencies" + echo " -e Build examples" echo " -c Clean build directory" echo " -h Display this help message" } # Parse command line arguments -while getopts "t:rscdbh" opt; do +while getopts "t:rscdebh" opt; do case $opt in t) TARGET=$OPTARG @@ -71,6 +73,9 @@ while getopts "t:rscdbh" opt; do d) DEPS="True" ;; + e) + BUILD_EXAMPLES="True" + ;; h) usage exit 0 diff --git a/source/pza/core/attribute.hxx b/source/pza/core/attribute.hxx index 4dfc2a2..142852c 100644 --- a/source/pza/core/attribute.hxx +++ b/source/pza/core/attribute.hxx @@ -18,13 +18,16 @@ namespace pza explicit attribute(const std::string &name); - template - void add_field(const std::string &name, bool readonly) + template + void add_ro_field(const std::string &name) { - field field(name, readonly); + add_field(name, access_mode::readonly); + } - field._callback = std::bind(&attribute::data_from_field, this, std::placeholders::_1); - _fields[name] = field; + template + void add_rw_field(const std::string &name) + { + add_field(name, access_mode::readwrite); } void on_message(const mqtt::const_message_ptr &message); @@ -44,6 +47,15 @@ namespace pza int data_from_field(const nlohmann::json &data); + template + void add_field(const std::string &name, access_mode mode) + { + field field(name, mode); + + field._callback = std::bind(&attribute::data_from_field, this, std::placeholders::_1); + _fields[name] = field; + } + template void _assign_value(std::any &elem, const nlohmann::json &data) { diff --git a/source/pza/core/client.cxx b/source/pza/core/client.cxx index 2e7da91..4095c79 100644 --- a/source/pza/core/client.cxx +++ b/source/pza/core/client.cxx @@ -28,7 +28,7 @@ int client::connect(void) { int ret; - spdlog::trace("connecting to {}...", _addr); + spdlog::debug("Attempting connection to {}...", _addr); mqtt::connect_options connOpts; @@ -55,7 +55,7 @@ int client::connect(void) int client::disconnect(void) { - spdlog::trace("disconnecting from {}...", _addr); + spdlog::debug("Attempting to disconnect from {}...", _addr); try { _paho_client->disconnect()->wait_for(std::chrono::seconds(CONN_TIMEOUT)); @@ -117,7 +117,6 @@ bool client::_topic_matches(const std::string &str, const std::string &fnmatchPa return std::regex_match(str, pattern); } - int client::_subscribe(const std::string &topic, const std::function &cb) { std::string t; @@ -135,7 +134,6 @@ int client::_subscribe(const std::string &topic, const std::function &lock, const device::p const std::string &scan_payload = _scan_device_results[device->_get_base_topic()]; if (json::get_unsigned_int(scan_payload, "info", "number_of_interfaces", _scan_itf_count_expected) == -1) { - spdlog::error("Unknown number of interface for device"); + spdlog::error("Unknown number of interfaces for device {}", device->_get_base_topic()); return -1; } diff --git a/source/pza/core/device.cxx b/source/pza/core/device.cxx index cd8bc1c..0ec8b7c 100644 --- a/source/pza/core/device.cxx +++ b/source/pza/core/device.cxx @@ -38,6 +38,9 @@ int device::_set_identity(const std::string &payload) return -1; } + // Convert to lowercase + std::transform(family.begin(), family.end(), family.begin(), ::tolower); + if (family != get_family()) { spdlog::error("Device is not compatible {} != {}", family, get_family()); return -1; diff --git a/source/pza/core/field.hxx b/source/pza/core/field.hxx index 9e27769..c2538a9 100644 --- a/source/pza/core/field.hxx +++ b/source/pza/core/field.hxx @@ -8,15 +8,22 @@ namespace pza { + enum class access_mode + { + readonly, + readwrite + }; + template class field { public: friend class attribute; - explicit field(const std::string &name, bool readonly) - : _name(name), - _readonly(readonly) + explicit field(const std::string &name, access_mode mode = access_mode::readonly) + : _value(_type()), + _name(name), + _mode(mode) { _setJsonType(); } @@ -48,17 +55,17 @@ namespace pza bool is_readonly(void) const { - return _readonly; + return (_mode == access_mode::readonly); } using get_callback_type = std::function; - void add_get_callback(const get_callback_type& callback) + void add_get_callback(const get_callback_type &callback) { _get_callbacks.push_back(std::make_shared(callback)); } - void remove_get_callback(const get_callback_type& callback) + void remove_get_callback(const get_callback_type &callback) { _get_callbacks.remove_if([&](const std::shared_ptr& ptr) { return callback.target_type() == ptr->target_type(); @@ -107,11 +114,11 @@ namespace pza } } - _type _value = _type(); + _type _value; std::string _name; nlohmann::json::value_t _json_type; std::list> _get_callbacks; - bool _readonly; + access_mode _mode; std::function _callback; }; }; \ No newline at end of file diff --git a/source/pza/core/united_interface.hxx b/source/pza/core/grouped_interface.hxx similarity index 63% rename from source/pza/core/united_interface.hxx rename to source/pza/core/grouped_interface.hxx index 3e86ce6..df36b8f 100644 --- a/source/pza/core/united_interface.hxx +++ b/source/pza/core/grouped_interface.hxx @@ -11,22 +11,22 @@ namespace pza { class device; - class united_interface + class grouped_interface { public: - using ptr = std::shared_ptr; + using ptr = std::shared_ptr; - united_interface() = delete; - united_interface(const united_interface&) = delete; - united_interface(united_interface&&) = delete; - ~united_interface() = delete; + grouped_interface() = delete; + grouped_interface(const grouped_interface&) = delete; + grouped_interface(grouped_interface&&) = delete; + ~grouped_interface() = delete; template static int register_interfaces(device *device, const std::string &name, const std::map &map, std::vector> &channels) { int ret = 0; - size_t pos; - size_t chan_id; + size_t pos = 0; + int chan_id = -1; for (auto const &elem : map) { if (pza::string::starts_with(elem.first, ":" + name + "_") == true) { @@ -35,8 +35,13 @@ namespace pza } } + if (chan_id == -1) { + spdlog::error("No {} channels found", name); + return -1; + } + channels.reserve(chan_id + 1); - for (size_t i = 0; i < chan_id + 1; i++) { + for (int i = 0; i < chan_id + 1; i++) { channels.push_back(std::make_shared(device, ":" + name + "_" + std::to_string(i) + ":")); } diff --git a/source/pza/devices/bps.cxx b/source/pza/devices/bps.cxx index d9494aa..e7bcdfb 100644 --- a/source/pza/devices/bps.cxx +++ b/source/pza/devices/bps.cxx @@ -20,7 +20,7 @@ int bps::_register_interfaces(const std::map &map) { int ret; - ret = united_interface::register_interfaces(this, "channel", map, channel); + ret = grouped_interface::register_interfaces(this, "channel", map, channel); if (ret < 0) return ret; diff --git a/source/pza/devices/bps.hxx b/source/pza/devices/bps.hxx index 461ad80..b108084 100644 --- a/source/pza/devices/bps.hxx +++ b/source/pza/devices/bps.hxx @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include diff --git a/source/pza/interfaces/bps_chan_ctrl.cxx b/source/pza/interfaces/bps_chan_ctrl.cxx index 1965d59..d28737a 100644 --- a/source/pza/interfaces/bps_chan_ctrl.cxx +++ b/source/pza/interfaces/bps_chan_ctrl.cxx @@ -10,17 +10,18 @@ bps_chan_ctrl::bps_chan_ctrl(device *device, const std::string &name) _amps("amps"), _enable("enable") { - _volts.add_field("goal", false); - _volts.add_field("min", true); - _volts.add_field("max", true); - _volts.add_field("decimals", true); + _volts.add_rw_field("goal"); + _volts.add_ro_field("min"); + _volts.add_ro_field("max"); + _volts.add_ro_field("decimals"); - _amps.add_field("goal", false); - _amps.add_field("min", true); - _amps.add_field("max", true); - _amps.add_field("decimals", true); + _amps.add_rw_field("goal"); + _amps.add_ro_field("min"); + _amps.add_ro_field("max"); + _amps.add_ro_field("decimals"); - _enable.add_field("value", false); + _enable.add_rw_field("value"); + _enable.add_rw_field("polling_cycle"); register_attributes({&_volts, &_amps, &_enable}); } @@ -61,6 +62,11 @@ bool bps_chan_ctrl::get_enable() return _enable.get_field("value").get(); } +int bps_chan_ctrl::set_enable_polling_cycle(double seconds) +{ + return _enable.get_field("polling_cycle").set(seconds); +} + void bps_chan_ctrl::add_enable_callback(const std::function &callback) { _enable.get_field("value").add_get_callback(callback); diff --git a/source/pza/interfaces/bps_chan_ctrl.hxx b/source/pza/interfaces/bps_chan_ctrl.hxx index 5c6e272..3ae6f7c 100644 --- a/source/pza/interfaces/bps_chan_ctrl.hxx +++ b/source/pza/interfaces/bps_chan_ctrl.hxx @@ -21,6 +21,7 @@ namespace pza int set_voltage(double volts); int set_current(double amps); int set_enable(bool enable); + int set_enable_polling_cycle(double seconds); bool get_enable(); void add_enable_callback(const std::function &callback); diff --git a/source/pza/interfaces/meter.cxx b/source/pza/interfaces/meter.cxx index 3114f3f..cb25e3c 100644 --- a/source/pza/interfaces/meter.cxx +++ b/source/pza/interfaces/meter.cxx @@ -6,7 +6,9 @@ meter::meter(device *device, const std::string &name) : interface(device, name), _measure("measure") { - _measure.add_field("value", true); + _measure.add_ro_field("value"); + _measure.add_ro_field("polling_cycle"); + register_attributes({&_measure}); } @@ -16,6 +18,11 @@ double meter::get_measure() return _measure.get_field("value").get(); } +int meter::set_measure_polling_cycle(double seconds) +{ + return _measure.get_field("polling_cycle").set(seconds); +} + void meter::add_measure_callback(const std::function &callback) { _measure.get_field("value").add_get_callback(callback); diff --git a/source/pza/interfaces/meter.hxx b/source/pza/interfaces/meter.hxx index 38244d3..07894b4 100644 --- a/source/pza/interfaces/meter.hxx +++ b/source/pza/interfaces/meter.hxx @@ -9,6 +9,7 @@ namespace pza { meter(device *device, const std::string &name); double get_measure(); + int set_measure_polling_cycle(double seconds); void add_measure_callback(const std::function &callback); void remove_measure_callback(const std::function &callback);