diff --git a/meta-smartlock/COPYING.MIT b/meta-smartlock/COPYING.MIT new file mode 100644 index 0000000..fb950dc --- /dev/null +++ b/meta-smartlock/COPYING.MIT @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/meta-smartlock/README b/meta-smartlock/README new file mode 100644 index 0000000..ccb4d58 --- /dev/null +++ b/meta-smartlock/README @@ -0,0 +1,41 @@ +This README file contains information on the contents of the meta-smartlock layer. + +Please see the corresponding sections below for details. + +Dependencies +============ + + URI: + branch: + + URI: + branch: + + . + . + . + +Patches +======= + +Please submit any patches against the meta-smartlock layer to the xxxx mailing list (xxxx@zzzz.org) +and cc: the maintainer: + +Maintainer: XXX YYYYYY + +Table of Contents +================= + + I. Adding the meta-smartlock layer to your build + II. Misc + + +I. Adding the meta-smartlock layer to your build +================================================= + +Run 'bitbake-layers add-layer meta-smartlock' + +II. Misc +======== + +--- replace with specific information about the meta-smartlock layer --- diff --git a/meta-smartlock/conf/layer.conf b/meta-smartlock/conf/layer.conf new file mode 100644 index 0000000..7684ff5 --- /dev/null +++ b/meta-smartlock/conf/layer.conf @@ -0,0 +1,13 @@ +# We have a conf and classes directory, add to BBPATH +BBPATH .= ":${LAYERDIR}" + +# We have recipes-* directories, add to BBFILES +BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \ + ${LAYERDIR}/recipes-*/*/*.bbappend" + +BBFILE_COLLECTIONS += "meta-smartlock" +BBFILE_PATTERN_meta-smartlock = "^${LAYERDIR}/" +BBFILE_PRIORITY_meta-smartlock = "6" + +LAYERDEPENDS_meta-smartlock = "core" +LAYERSERIES_COMPAT_meta-smartlock = "kirkstone" diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock.idl b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock.idl new file mode 100644 index 0000000..597a6f4 --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock.idl @@ -0,0 +1,25 @@ +module SmartLock { + + struct vec2 { + float x; + float y; + }; + + @topic + struct lock_t { + @key string id; + boolean locked; + vec2 position; + }; + + @topic + struct Status { + @key lock_t lock; + }; + + @topic + struct Control { + @key lock_t lock; + }; + +}; diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock_Idl.mpc b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock_Idl.mpc new file mode 100644 index 0000000..cb2fff4 --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock_Idl.mpc @@ -0,0 +1,14 @@ + +project: opendds_cxx11 { + idlflags += -Wb,export_macro=SmartLock_Export -Wb,export_include=SmartLock_export.h + dcps_ts_flags += -Wb,export_macro=SmartLock_Export -Wb,export_include=SmartLock_export.h + dynamicflags += SMARTLOCK_BUILD_DLL + + TypeSupport_Files { + SmartLock.idl + } + + IDL_Files { + SmartLockTypeSupport.idl + } +} diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock_export.h b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock_export.h new file mode 100644 index 0000000..92b3e36 --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/Idl/SmartLock_export.h @@ -0,0 +1,57 @@ + +// -*- C++ -*- +// Definition for Win32 Export directives. +// This file is generated automatically by generate_export_file.pl SmartLock +// ------------------------------ +#ifndef SMARTLOCK_EXPORT_H +#define SMARTLOCK_EXPORT_H + +#include "ace/config-all.h" + +#if defined (ACE_AS_STATIC_LIBS) && !defined (SMARTLOCK_HAS_DLL) +# define SMARTLOCK_HAS_DLL 0 +#endif /* ACE_AS_STATIC_LIBS && SMARTLOCK_HAS_DLL */ + +#if !defined (SMARTLOCK_HAS_DLL) +# define SMARTLOCK_HAS_DLL 1 +#endif /* ! SMARTLOCK_HAS_DLL */ + +#if defined (SMARTLOCK_HAS_DLL) && (SMARTLOCK_HAS_DLL == 1) +# if defined (SMARTLOCK_BUILD_DLL) +# define SmartLock_Export ACE_Proper_Export_Flag +# define SMARTLOCK_SINGLETON_DECLARATION(T) ACE_EXPORT_SINGLETON_DECLARATION (T) +# define SMARTLOCK_SINGLETON_DECLARE(SINGLETON_TYPE, CLASS, LOCK) ACE_EXPORT_SINGLETON_DECLARE(SINGLETON_TYPE, CLASS, LOCK) +# else /* SMARTLOCK_BUILD_DLL */ +# define SmartLock_Export ACE_Proper_Import_Flag +# define SMARTLOCK_SINGLETON_DECLARATION(T) ACE_IMPORT_SINGLETON_DECLARATION (T) +# define SMARTLOCK_SINGLETON_DECLARE(SINGLETON_TYPE, CLASS, LOCK) ACE_IMPORT_SINGLETON_DECLARE(SINGLETON_TYPE, CLASS, LOCK) +# endif /* SMARTLOCK_BUILD_DLL */ +#else /* SMARTLOCK_HAS_DLL == 1 */ +# define SmartLock_Export +# define SMARTLOCK_SINGLETON_DECLARATION(T) +# define SMARTLOCK_SINGLETON_DECLARE(SINGLETON_TYPE, CLASS, LOCK) +#endif /* SMARTLOCK_HAS_DLL == 1 */ + +// Set SMARTLOCK_NTRACE = 0 to turn on library specific tracing even if +// tracing is turned off for ACE. +#if !defined (SMARTLOCK_NTRACE) +# if (ACE_NTRACE == 1) +# define SMARTLOCK_NTRACE 1 +# else /* (ACE_NTRACE == 1) */ +# define SMARTLOCK_NTRACE 0 +# endif /* (ACE_NTRACE == 1) */ +#endif /* !SMARTLOCK_NTRACE */ + +#if (SMARTLOCK_NTRACE == 1) +# define SMARTLOCK_TRACE(X) +#else /* (SMARTLOCK_NTRACE == 1) */ +# if !defined (ACE_HAS_TRACE) +# define ACE_HAS_TRACE +# endif /* ACE_HAS_TRACE */ +# define SMARTLOCK_TRACE(X) ACE_TRACE_IMPL(X) +# include "ace/Trace.h" +#endif /* (SMARTLOCK_NTRACE == 1) */ + +#endif /* SMARTLOCK_EXPORT_H */ + +// End of auto generated file. diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/SmartLock.cpp b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/SmartLock.cpp new file mode 100644 index 0000000..667f1ef --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/SmartLock.cpp @@ -0,0 +1,936 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Idl/SmartLockTypeSupportC.h" +#include "Idl/SmartLockTypeSupportImpl.h" + +std::ostream& operator<< (std::ostream& lhs, const SmartLock::lock_t& rhs) { + lhs << "lock: id: '" << rhs.id() << "', locked: '" << rhs.locked() + << "', position: x: '" << rhs.position().x() + << "', y: '" << rhs.position().y() << "'"; + return lhs; +} + +// The green led (gpio 11) is on when it is unlocked and yellow (gpio 10) when it is locked. Red (gpio 09 is not used) +// 9 GPIO10 (SPI_MOSI) Yellow gpioset gpiochip0 10=1 +// 23 GPIO11 (SPI_CLK) Green gpioset gpiochip0 11=1 + +const unsigned int YELLOW_LED = 10; +const unsigned int GREEN_LED = 11; + +static gpiod_chip *chip = nullptr; +static gpiod_line *lineLOCKED = nullptr; +static gpiod_line *lineUNLOCKED = nullptr; + +void lock_indicator_init() { + const char *chipname = "/dev/gpiochip0"; + chip = gpiod_chip_open_by_name(chipname); + if (chip != nullptr) { + lineLOCKED = gpiod_chip_get_line(chip,YELLOW_LED); + lineUNLOCKED = gpiod_chip_get_line(chip, GREEN_LED); + // Open LED lines for output + if (gpiod_line_request_output(lineLOCKED, "smartlock", 0) < 0) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: gpiod_line_request_output() -") + ACE_TEXT(" request line as output failed!\n"))); + + } + if (gpiod_line_request_output(lineUNLOCKED, "smartlock", 0) < 0) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: gpiod_line_request_output() -") + ACE_TEXT(" request line as output failed!\n"))); + } + } +} + +void lock_indicator_shutdown() { + if (lineLOCKED != nullptr) { + gpiod_line_release(lineLOCKED); + lineLOCKED = nullptr; + } + if (lineUNLOCKED != nullptr) { + gpiod_line_release(lineUNLOCKED); + lineUNLOCKED = nullptr; + } + if (chip != nullptr) { + gpiod_chip_close(chip); + chip = nullptr; + } +} + +void lock_indicator_clear() { + if (lineUNLOCKED != nullptr && lineLOCKED != nullptr) { + if (gpiod_line_set_value(lineUNLOCKED, 0) < 0) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: gpiod_line_set_value() -") + ACE_TEXT(" set line output failed!\n"))); + } + if (gpiod_line_set_value(lineLOCKED, 0) < 0) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: gpiod_line_set_value() -") + ACE_TEXT(" set line output failed!\n"))); + } + } +} + +void lock_indicator_lock_when_locked(const SmartLock::lock_t& lock) { + if (lineUNLOCKED != nullptr && lineLOCKED != nullptr) { + if (lock.locked()) { + if (gpiod_line_set_value(lineUNLOCKED, 0) < 0){ + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: gpiod_line_set_value() -") + ACE_TEXT(" set line output failed!\n"))); + } + if (gpiod_line_set_value(lineLOCKED, 1) < 0){ + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: gpiod_line_set_value() -") + ACE_TEXT(" set line output failed!\n"))); + } + } else { + if (gpiod_line_set_value(lineLOCKED, 0) < 0){ + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: gpiod_line_set_value() -") + ACE_TEXT(" set line output failed!\n"))); + } + if (gpiod_line_set_value(lineUNLOCKED, 1) < 0){ + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: gpiod_line_set_value() -") + ACE_TEXT(" set line output failed!\n"))); + } + } + } +} + +namespace DCPS = OpenDDS::DCPS; + +enum Role { + kUnknown, + kSmartLock, + kUser, + kDealer + +} role = kUnknown; + +void groups_to_partitions(const std::vector& src, DDS::PartitionQosPolicy& dest) { + dest.name.length(src.size()); + for(auto i = 0u; i < dest.name.length(); ++i) { + dest.name[i] = src[i].c_str(); + } +} + +class PartitionedPublisher { +protected: + + PartitionedPublisher(DDS::DomainParticipant_var dp, + const std::vector& groups) : participant_(dp) + { + DDS::PublisherQos qos; + dp->get_default_publisher_qos(qos); + + groups_to_partitions(groups, partition_); + qos.partition = partition_; + + publisher_ = dp->create_publisher(qos, 0, OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (! publisher_) { + ACE_ERROR((LM_ERROR, + "ERROR: %N:%l: PartitionedPublisher::PartitionedPublisher(): " + "create_publisher failed!\n")); + } + } + + virtual ~PartitionedPublisher() = default; + + DDS::DomainParticipant_var participant_; + DDS::Publisher_var publisher_; + DDS::PartitionQosPolicy partition_; +}; + +class PartitionedSubscriber { +protected: + + PartitionedSubscriber(DDS::DomainParticipant_var dp, + const std::vector& groups) : participant_(dp) + { + DDS::SubscriberQos qos; + dp->get_default_subscriber_qos(qos); + + groups_to_partitions(groups, partition_); + qos.partition = partition_; + + subscriber_ = dp->create_subscriber(qos, 0, OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (! subscriber_) { + ACE_ERROR((LM_ERROR, + "ERROR: %N:%l: PartitionedPublisher::PartitionedPublisher(): " + "create_subscriber failed!\n")); + } + } + + virtual ~PartitionedSubscriber() = default; + + DDS::DomainParticipant_var participant_; + DDS::Subscriber_var subscriber_; + DDS::PartitionQosPolicy partition_; +}; + +struct ControlReader : private PartitionedSubscriber { + DDS::Topic_ptr control_topic; + SmartLock::Status& message; + + ControlReader (const std::vector& groups, + DDS::DomainParticipant_var a_participant, + DDS::Topic_ptr a_topic, + SmartLock::Status& a_message) + : PartitionedSubscriber(a_participant, groups), + control_topic(a_topic), + message(a_message) + { + } + + void operator() () { + if (!subscriber_) return; + + DDS::DataReaderQos qos; + subscriber_->get_default_datareader_qos(qos); + + // Create DataReader + DDS::DataReader_var reader = + subscriber_->create_datareader(control_topic, + qos, + NULL, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!reader) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" create_datareader failed!\n"))); + return; + } + + SmartLock::ControlDataReader_var reader_i = + SmartLock::ControlDataReader::_narrow(reader); + + if (!reader_i) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" _narrow failed!\n"))); + return; + } + + DDS::StatusCondition_var cond = reader->get_statuscondition(); + cond->set_enabled_statuses(DDS::DATA_AVAILABLE_STATUS); + DDS::WaitSet_var ws = new DDS::WaitSet; + ws->attach_condition(cond); + + DDS::ConditionSeq active; + DDS::Duration_t forever = {DDS::DURATION_INFINITE_SEC, DDS::DURATION_INFINITE_NSEC}; + + for (;;) { + int result = ws->wait(active, forever); + if (result == DDS::RETCODE_OK) { + SmartLock::Control control_message; + DDS::SampleInfo info; + DDS::ReturnCode_t error = reader_i->take_next_sample(control_message, info); + if (error == DDS::RETCODE_OK) { + + if (info.valid_data) { + if (control_message.lock().id() != message.lock().id()) + continue; + + std::cout << " Reading Control: " << control_message.lock() << "\n"; + + message.lock(control_message.lock()); + + lock_indicator_lock_when_locked(message.lock()); + + } + } else { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: on_data_available() -") + ACE_TEXT(" take_next_sample failed!\n"))); + } + } + } + } +}; + +struct StatusWriter : private PartitionedPublisher { + + const std::string smartlock_id; + DDS::Topic_ptr status_topic; + SmartLock::Status& message; + + StatusWriter(const std::vector& groups, + DDS::DomainParticipant_var a_participant, + DDS::Topic_ptr a_status_topic, + SmartLock::Status& a_message) + : PartitionedPublisher(a_participant, groups), + smartlock_id(a_message.lock().id()), + status_topic(a_status_topic), + message(a_message) + { + } + + void operator() () { + if (!publisher_) return; + + DDS::DataWriterQos qos; + publisher_->get_default_datawriter_qos(qos); + + // Create DataWriter + DDS::DataWriter_var writer = + publisher_->create_datawriter(status_topic, + qos, + 0, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!writer) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" create_datawriter failed!\n"))); + return; + } + + SmartLock::StatusDataWriter_var message_writer = + SmartLock::StatusDataWriter::_narrow(writer); + + if (!message_writer) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" _narrow failed!\n"))); + return; + } + + // Write samples + for (;;) { + + DDS::ReturnCode_t error = message_writer->write(message, DDS::HANDLE_NIL); + if (error != DDS::RETCODE_OK) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" write returned %d!\n"), error)); + } + std::cout << " Writing Status: " << message.lock() << "\n"; + + ACE_OS::sleep(5); + } + } +}; + +int run_smartlock(const SmartLock::lock_t& start, + const std::vector& groups, + DDS::DomainParticipant_var participant, + DDS::Topic_ptr status_topic, + DDS::Topic_ptr control_topic) { + SmartLock::Status message(start); + + lock_indicator_lock_when_locked(message.lock()); + + // Spawn a thread to read the control. + ControlReader cr(groups, participant, control_topic, message); + std::thread control_reader_thread(cr); + + // Spawn a thread to write the status. + StatusWriter sw(groups, participant, status_topic, message); + std::thread status_writer_thread(sw); + + status_writer_thread.join(); + control_reader_thread.join(); + + return 0; +} + +struct StatusReader : private PartitionedSubscriber { + DDS::Topic_ptr status_topic; + + StatusReader (const std::vector& groups, + DDS::DomainParticipant_var a_participant, + DDS::Topic_ptr a_topic) + : PartitionedSubscriber(a_participant, groups), + status_topic(a_topic) + { + } + + void operator() () { + if (!subscriber_) return; + + DDS::DataReaderQos qos; + subscriber_->get_default_datareader_qos(qos); + + // Create DataReader + DDS::DataReader_var reader = + subscriber_->create_datareader(status_topic, + qos, + NULL, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!reader) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" create_datareader failed!\n"))); + return; + } + + SmartLock::StatusDataReader_var reader_i = + SmartLock::StatusDataReader::_narrow(reader); + + if (!reader_i) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" _narrow failed!\n"))); + return; + } + + DDS::StatusCondition_var cond = reader->get_statuscondition(); + cond->set_enabled_statuses(DDS::DATA_AVAILABLE_STATUS); + DDS::WaitSet_var ws = new DDS::WaitSet; + ws->attach_condition(cond); + + DDS::ConditionSeq active; + DDS::Duration_t forever = {DDS::DURATION_INFINITE_SEC, DDS::DURATION_INFINITE_NSEC}; + + + for (;;) { + int result = ws->wait(active, forever); + if (result == DDS::RETCODE_OK) { + SmartLock::Status status_message; + DDS::SampleInfo info; + DDS::ReturnCode_t error = reader_i->take_next_sample(status_message, info); + if (error == DDS::RETCODE_OK) { + // std::cout << "SampleInfo.sample_rank = " << info.sample_rank << std::endl; + // std::cout << "SampleInfo.instance_state = " << info.instance_state << std::endl; + + if (info.valid_data) { + std::cout << " Reading Status: " << status_message.lock() << "\n"; + } + } else { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: on_data_available() -") + ACE_TEXT(" take_next_sample failed!\n"))); + } + } + } + } +}; + +struct ControlWriter : private PartitionedPublisher { + + const std::string smartlock_id; + DDS::Topic_ptr control_topic; + + ControlWriter(const std::string& a_smartlock_id, + const std::vector& groups, + DDS::DomainParticipant_var a_participant, + DDS::Topic_ptr a_control_topic) + : PartitionedPublisher(a_participant, groups), + smartlock_id(a_smartlock_id), + control_topic(a_control_topic) + { + } + + void operator() () { + if (!publisher_) return; + + DDS::DataWriterQos qos; + publisher_->get_default_datawriter_qos(qos); + + // Create DataWriter + DDS::DataWriter_var writer = + publisher_->create_datawriter(control_topic, + qos, + 0, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!writer) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" create_datawriter failed!\n"))); + return; + } + + + + SmartLock::ControlDataWriter_var message_writer = + SmartLock::ControlDataWriter::_narrow(writer); + + if (!message_writer) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" _narrow failed!\n"))); + return; + } + + // Write samples + SmartLock::Control message(SmartLock::lock_t( + smartlock_id, true, + SmartLock::vec2(20.0f, 10.0f))); + + for (;;) { + + DDS::ReturnCode_t error = message_writer->write(message, DDS::HANDLE_NIL); + if (error != DDS::RETCODE_OK) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" write returned %d!\n"), error)); + } + std::cout << " Writing Control: " << message.lock() << "\n"; + + ACE_OS::sleep(5); + } + } +}; + +int run_user(const std::string& smartlock_id, + const std::vector& groups, + DDS::DomainParticipant_var participant, + DDS::Topic_ptr status_topic, + DDS::Topic_ptr control_topic) { + + // Spawn a thread to read the status. + StatusReader sr(groups, participant, status_topic); + std::thread status_reader_thread(sr); + + // Spawn a thread to control the set point. + ControlWriter cw(smartlock_id, groups, participant, control_topic); + std::thread control_writer_thread(cw); + + control_writer_thread.join(); + status_reader_thread.join(); + + return 0; +} + +void usage(std::ostream& out) { + out << "Usage: smartlock -h | -lock SMARTLOCK_ID [-user | -dealer] -groups GROUP_0 [GROUP_1...GROUP_N] [OPTIONS ...]" << std::endl + << "OPTIONS:" << std::endl + << " SmartLock Options:" << std::endl + << " -x X_POSITION | -y Y_POSITION" << std::endl +#if defined(OPENDDS_SECURITY) + << " Security Options:" << std::endl + << " -ID_CA file | -ID_CERT file | -ID_PKEY file | -PERM_CA file | -PERM_GOV file | -PERM_PERMS file" << std::endl +#endif + ; +} + +#if defined(OPENDDS_SECURITY) +struct SecurityInfo { + std::string id_ca; + std::string id_cert; + std::string id_pkey; + std::string perm_ca; + std::string perm_gov; + std::string perm_perms; + + SecurityInfo(int argc, char* argv[]) : arg_supplied(false) { + ACE_Arg_Shifter args(argc, argv); + + while (args.is_anything_left()) { + const char* arg = nullptr; + + if ((arg = args.get_the_parameter("-ID_CA")) != nullptr) { + id_ca = "file:" + std::string(arg); + arg_supplied = true; + args.consume_arg(); + + } else if ((arg = args.get_the_parameter("-ID_CERT")) != nullptr) { + id_cert = "file:" + std::string(arg); + arg_supplied = true; + args.consume_arg(); + + } else if ((arg = args.get_the_parameter("-ID_PKEY")) != nullptr) { + id_pkey = "file:" + std::string(arg); + arg_supplied = true; + args.consume_arg(); + + } else if ((arg = args.get_the_parameter("-PERM_CA")) != nullptr) { + perm_ca = "file:" + std::string(arg); + arg_supplied = true; + args.consume_arg(); + + } else if ((arg = args.get_the_parameter("-PERM_GOV")) != nullptr) { + perm_gov = "file:" + std::string(arg); + arg_supplied = true; + args.consume_arg(); + + } else if ((arg = args.get_the_parameter("-PERM_PERMS")) != nullptr) { + perm_perms = "file:" + std::string(arg); + arg_supplied = true; + args.consume_arg(); + + } else { + args.ignore_arg(); + } + } + } + ~SecurityInfo() = default; + + bool is_valid() { + return (id_ca != "" && id_cert != "" && id_pkey != "" && perm_ca != "" && perm_gov != "" && perm_perms != ""); + } + + bool was_arg_supplied() { return arg_supplied; } +private: + bool arg_supplied; +}; + +std::ostream& operator<<(std::ostream& lhs, const SecurityInfo& rhs) { + lhs << "ID_CA: '" << rhs.id_ca << "', ID_CERT: '" << rhs.id_cert << "', ID_PKEY: '" << rhs.id_pkey + << "', PERM_CA: '" << rhs.perm_ca << "', PERM_GOV: '" << rhs.perm_gov << "', PERM_PERMS: '" << rhs.perm_perms << "'"; + return lhs; +} +#endif + +bool load_config(const std::string& config_file, + std::string& topic_prefix, int& domain_id) +{ + ACE_Configuration_Heap config; + if (config.open() != 0) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: load_config() -") + ACE_TEXT(" ACE_Configuration_Heap open failed!\n")), + false); + } + + ACE_Ini_ImpExp import(config); + if (import.import_config(config_file.c_str()) != 0) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: load_config() -") + ACE_TEXT(" INI import failed!\n")), + false); + } + + ACE_Configuration_Section_Key section; + if (config.open_section(config.root_section(), + "smartlock", 1, section) != 0) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: load_config() -") + ACE_TEXT(" The smartlock ini does not have a ") + ACE_TEXT("smartlock section!\n")), + false); + } + + ACE_TString topic_prefix_str; + if (config.get_string_value(section, ACE_TEXT("topic_prefix"), + topic_prefix_str) != 0) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: load_config() -") + ACE_TEXT(" The smartlock ini does define a ") + ACE_TEXT("topic prefix!\n")), + false); + } + topic_prefix = topic_prefix_str.c_str(); + + // Every value, when imported by ACE, is a string value. + ACE_TString domain_id_str; + if (config.get_string_value(section, ACE_TEXT("domain_id"), + domain_id_str) != 0) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: load_config() -") + ACE_TEXT(" The smartlock ini does define a ") + ACE_TEXT("domain id!\n")), + false); + } + domain_id = ACE_OS::atoi(domain_id_str.c_str()); + return true; +} + +DDS::DomainParticipantFactory_var dpf = nullptr; +DDS::DomainParticipant_var participant = nullptr; + +void cleanup() { + + if (role == kSmartLock) { + lock_indicator_clear(); + } + lock_indicator_shutdown(); + + if (dpf) { + try { + std::cerr << "Shutting down...\n"; + + if (participant) { + participant->delete_contained_entities(); + dpf->delete_participant(participant); + participant = nullptr; + } + + TheServiceParticipant->shutdown(); + dpf = nullptr; + + std::cerr << "Done\n"; + + } catch (const CORBA::Exception& e) { + e._tao_print_exception("Exception caught in main():"); + exit(1); + } + } +} + +extern "C" void exit_handler(int) { + std::cerr << "Interrupted...\n"; + + cleanup(); + + exit(0); +} + +int ACE_TMAIN(int argc, ACE_TCHAR *argv[]) +{ + std::signal(SIGINT, exit_handler); + + try { + dpf = TheParticipantFactoryWithArgs(argc, argv); + if (!dpf) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" TheParticipantFactoryWithArgs failed!\n")), + -1); + } + + std::vector groups; + SmartLock::lock_t lock; + std::string config_file("smartlock.ini"); + + ACE_Arg_Shifter args(argc, argv); + while (args.is_anything_left()) { + const char* arg = nullptr; + + if ((arg = args.get_the_parameter("-lock")) != nullptr) { + role = kSmartLock; + lock.id(arg); + args.consume_arg(); + + } else if ((arg = args.get_the_parameter("-x")) != nullptr) { + try { + lock.position().x(std::stof(arg)); + + } catch (const std::logic_error&) { + ACE_ERROR((LM_ERROR, "ERROR: Invalid number passed to -x\n")); + return 1; + } + args.consume_arg(); + + } else if ((arg = args.get_the_parameter("-y")) != nullptr) { + try { + lock.position().y(std::stof(arg)); + + } catch (const std::logic_error&) { + ACE_ERROR((LM_ERROR, "ERROR: Invalid number passed to -y\n")); + return 1; + } + args.consume_arg(); + + } else if (std::strcmp(args.get_current(), "-h") == 0) { + usage(std::cout); + return 0; + + } else if (std::strcmp(args.get_current(), "-user") == 0) { + if (role == kDealer) { + ACE_ERROR((LM_ERROR, "ERROR: -dealer and -user cannot both be specified\n")); + usage(std::cerr); + return 1; + } + role = kUser; + args.consume_arg(); + + } else if (std::strcmp(args.get_current(), "-dealer") == 0) { + if (role == kUser) { + ACE_ERROR((LM_ERROR, "ERROR: -dealer and -user cannot both be specified\n")); + usage(std::cerr); + return 1; + } + role = kDealer; + args.consume_arg(); + + } else if ((arg = args.get_the_parameter("-groups")) != nullptr) { + + do { + groups.push_back(std::string(args.get_current())); + args.consume_arg(); + + } while (args.is_parameter_next()); + + } else if ((arg = args.get_the_parameter("-ini")) != nullptr) { + + config_file = args.get_current(); + args.consume_arg(); + + } else { + args.ignore_arg(); + } + } + + if (role == kUnknown || lock.id().empty()) { + usage(std::cerr); + return -1; + } + + std::string topic_prefix; + int domain_id; + if (!load_config(config_file, topic_prefix, domain_id)) { + return -1; + } + + DDS::DomainParticipantQos part_qos; + dpf->get_default_participant_qos(part_qos); + + std::string group_str; + for (auto group : groups) { + if (!group_str.empty()) { + group_str += "," + group; + } else { + group_str += group; + } + } + + std::cout << "group_str=" << group_str << std::endl; + + OpenDDS::DCPS::SequenceBackInsertIterator props(part_qos.property.value); + *props = {"OpenDDS.RtpsRelay.Groups", group_str.c_str(), true}; + +#if defined(OPENDDS_SECURITY) + if (TheServiceParticipant->get_security()) { + SecurityInfo security_info(argc, argv); + + if (security_info.was_arg_supplied()) { + std::cout << "Security Configs: " << security_info << "\n"; + + if (! security_info.is_valid()) { + std::cerr << "ERROR: All security arguments above must be provided\n"; + usage(std::cerr); + return -1; + } + + *props = {"dds.sec.auth.identity_ca", security_info.id_ca.c_str(), false}; + *props = {"dds.sec.auth.identity_certificate", security_info.id_cert.c_str(), false}; + *props = {"dds.sec.auth.private_key", security_info.id_pkey.c_str(), false}; + *props = {"dds.sec.access.permissions_ca", security_info.perm_ca.c_str(), false}; + *props = {"dds.sec.access.governance", security_info.perm_gov.c_str(), false}; + *props = {"dds.sec.access.permissions", security_info.perm_perms.c_str(), false}; + } + } +#endif + + // Create DomainParticipant + participant = + dpf->create_participant(domain_id, + part_qos, + 0, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!participant) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" create_participant failed!\n")), + -1); + } + + // Register TypeSupport + // SmartLock::Status + SmartLock::StatusTypeSupport_var status_ts = + new SmartLock::StatusTypeSupportImpl; + + if (status_ts->register_type(participant, "") != DDS::RETCODE_OK) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" register_type failed!\n")), + -1); + } + + SmartLock::ControlTypeSupport_var control_ts = + new SmartLock::ControlTypeSupportImpl; + + if (control_ts->register_type(participant, "") != DDS::RETCODE_OK) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" register_type failed!\n")), + -1); + } + + // Create Topic + // Status + const std::string status_topic_name = topic_prefix + "SmartLock Status"; + CORBA::String_var type_name = status_ts->get_type_name(); + DDS::Topic_var status_topic = + participant->create_topic(status_topic_name.c_str(), + type_name, + TOPIC_QOS_DEFAULT, + 0, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!status_topic) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" create_topic %s failed!\n"), + status_topic_name.c_str()), + -1); + } + + // Control + const std::string control_topic_name = topic_prefix + "SmartLock Control"; + type_name = control_ts->get_type_name(); + DDS::Topic_var control_topic = + participant->create_topic(control_topic_name.c_str(), + type_name, + TOPIC_QOS_DEFAULT, + 0, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!control_topic) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" create_topic %s failed!\n"), + control_topic_name.c_str()), + -1); + } + + int retval = 0; + switch (role) { + case kUnknown: + break; + case kSmartLock: + lock_indicator_init(); + lock_indicator_clear(); + retval = run_smartlock(lock, groups, participant, status_topic, control_topic); + break; + case kUser: + retval = run_user(lock.id(), groups, participant, status_topic, control_topic); + break; + case kDealer: + std::cout << "TODO: Implement logic for dealer" << std::endl; + break; + } + + cleanup(); + + if (retval != 0) return retval; + + } catch (const CORBA::Exception& e) { + e._tao_print_exception("Exception caught in main():"); + return -1; + } + + return 0; +} diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/SmartLock.mpc b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/SmartLock.mpc new file mode 100644 index 0000000..4ea7761 --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/SmartlockApp/SmartLock.mpc @@ -0,0 +1,16 @@ + +project: dcps { + requires += no_opendds_safety_profile + + libs += gpiod + + after += SmartLock_Idl + libs += SmartLock_Idl + libpaths += Idl + + exename = smartlock + + specific(prop:microsoft) { + link_options += /NODEFAULTLIB:libcmtd.lib + } +} diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/dpm_password b/meta-smartlock/recipes-connectivity/smartlock/files/dpm_password new file mode 100644 index 0000000..b6e21c1 --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/dpm_password @@ -0,0 +1 @@ +WNg97wLeR7Rk5eHz \ No newline at end of file diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/rtps.ini b/meta-smartlock/recipes-connectivity/smartlock/files/rtps.ini new file mode 100644 index 0000000..a49eaae --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/rtps.ini @@ -0,0 +1,16 @@ +[common] +DCPSGlobalTransportConfig=$file +DCPSDefaultDiscovery=DEFAULT_RTPS +DCPSSecurity=1 + +[transport/the_rtps_transport] +transport_type=rtps_udp + +DataRtpsRelayAddress=35.224.27.187:4446 + +[domain/1] +DiscoveryConfig=DiscoveryConfig1 + +[rtps_discovery/DiscoveryConfig1] +SpdpRtpsRelayAddress=35.224.27.187:4444 +SedpRtpsRelayAddress=35.224.27.187:4445 diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/smartlock.ini b/meta-smartlock/recipes-connectivity/smartlock/files/smartlock.ini new file mode 100644 index 0000000..a51c368 --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/smartlock.ini @@ -0,0 +1,5 @@ +[smartlock] +topic_prefix = "C.53." +domain_id = 1 +username = "54" +api_url = "https://dpm.unityfoundation.io/api" diff --git a/meta-smartlock/recipes-connectivity/smartlock/files/smartlock.sh b/meta-smartlock/recipes-connectivity/smartlock/files/smartlock.sh new file mode 100644 index 0000000..bf7901e --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/files/smartlock.sh @@ -0,0 +1,160 @@ +# +# Usage: smartlock.sh --security --lock 1 start-system + +EXEC_PATH="/usr/bin" +CONFIG_PATH="/etc/smartlock" +STATE_PATH="/var/run" + +cert_dir=${CONFIG_PATH}/certs +smartlock_ini=${CONFIG_PATH}/smartlock.ini + +SECURITY=${SMARTLOCK_SECURE:-0} +CMD=start +LOCK= +while (( $# > 0 )); do + case "$1" in + --security) + SECURITY=1 + shift + ;; + -h|--help) + echo "Usage: smartlock.sh [--security] [--lock LOCK_ID] start | stop | restart | start-system" + exit + ;; + --lock) + LOCK="$2" + shift 2 + ;; + start) + CMD=start + shift + ;; + stop) + CMD=stop + shift + ;; + restart) + CMD=restart + shift + ;; + start-system) + CMD=start-system + shift + ;; + *) + echo "ERROR: invalid argument '$1' supplied" + exit 1 + ;; + esac +done + +if [[ -z "$LOCK" ]]; then + if [[ -f ${CONFIG_PATH}/smartlock.id ]]; then + LOCK="$(cat ${CONFIG_PATH}/smartlock.id)" + else + echo "ERROR: must supply a valid lock identifier to --lock" + exit + fi +fi + +ID_CA=${cert_dir}/id_ca/identity_ca.pem +ID_CERT=${cert_dir}/${LOCK}/identity.pem +ID_PKEY=${cert_dir}/${LOCK}/identity_key.pem +PERM_CA=${cert_dir}/perm_ca/permissions_ca.pem +PERM_GOV=${cert_dir}/governance.xml.p7s +PERM_PERMS=${cert_dir}/${LOCK}/permissions.xml.p7s + +if (( $SECURITY )); then + SECURITY_ARGS=" \ + -DCPSSecurityDebug bookkeeping \ + -DCPSSecurity 1 \ + -ID_CA ${ID_CA} \ + -ID_CERT ${ID_CERT} \ + -ID_PKEY ${ID_PKEY} \ + -PERM_CA ${PERM_CA} \ + -PERM_GOV ${PERM_GOV} \ + -PERM_PERMS ${PERM_PERMS} \ + " +fi + +echo "CMD: '$CMD', SECURITY: '$SECURITY', LOCK_ID: '$LOCK', SECURITY_ARGS: '$SECURITY_ARGS'" + +function update_certs { + APP_PASSWORD=$(cat ${CONFIG_PATH}/dpm_password) + APP_NONCE=${LOCK} + API_URL=$(grep api_url ${smartlock_ini} | sed 's/api_url *= *"//; s/".*//') + USERNAME=$(grep username ${smartlock_ini} | sed 's/username *= *"//; s/".*//') + + mkdir -p ${cert_dir}/id_ca ${cert_dir}/${LOCK} ${cert_dir}/perm_ca + + curl -c cookies.txt -H'Content-Type: application/json' -d"{\"username\":\"${USERNAME}\",\"password\":\"$APP_PASSWORD\"}" ${API_URL}/login + + curl --silent -b cookies.txt "${API_URL}/applications/identity_ca.pem" > ${ID_CA} + curl --silent -b cookies.txt "${API_URL}/applications/permissions_ca.pem" > ${PERM_CA} + curl --silent -b cookies.txt "${API_URL}/applications/governance.xml.p7s" > ${PERM_GOV} + curl --silent -b cookies.txt "${API_URL}/applications/key_pair?nonce=${APP_NONCE}" > key-pair + curl --silent -b cookies.txt "${API_URL}/applications/permissions.xml.p7s?nonce=${APP_NONCE}" > ${PERM_PERMS} + + jq -r '.public' key-pair > ${ID_CERT} + jq -r '.private' key-pair > ${ID_PKEY} + + rm -f cookies.txt key-pair +} + +PID_FILE=${STATE_PATH}/smartlock.pid + +start() { + if (( $SECURITY )); then + update_certs + fi + + ${EXEC_PATH}/smartlock \ + -DCPSConfigFile ${CONFIG_PATH}/rtps.ini \ + -DCPSDebugLevel 5 \ + -DCPSTransportDebugLevel 5 \ + -lock ${LOCK} \ + -ini ${smartlock_ini} \ + ${SECURITY_ARGS} & + + echo "$!" > $PID_FILE +} + +stop() { + if [[ -f "$PID_FILE" ]]; then + kill -2 "$(cat $PID_FILE)" + rm $PID_FILE + fi +} + +start_system() { + if (( $SECURITY )); then + update_certs + fi + ${EXEC_PATH}/smartlock \ + -DCPSConfigFile ${CONFIG_PATH}/rtps.ini \ + -DCPSDebugLevel 5 \ + -DCPSTransportDebugLevel 5 \ + -lock ${LOCK} \ + -ini ${smartlock_ini} \ + ${SECURITY_ARGS} +} + +case "$CMD" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + start-system) + start_system + ;; + *) + echo "ERROR: invalid command '$CMD'" + exit 1 + ;; +esac diff --git a/meta-smartlock/recipes-connectivity/smartlock/smartlock_0.3.bb b/meta-smartlock/recipes-connectivity/smartlock/smartlock_0.3.bb new file mode 100644 index 0000000..d57d7d5 --- /dev/null +++ b/meta-smartlock/recipes-connectivity/smartlock/smartlock_0.3.bb @@ -0,0 +1,66 @@ +DESCRIPTION = "OpenDDS Smartlock application" +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" + +inherit logging +inherit autotools + +SRC_URI = "file://SmartlockApp \ +file://rtps.ini \ +file://smartlock.ini \ +file://dpm_password \ +file://smartlock.sh \ +" + +DEPENDS += "opendds opendds-native libgpiod glibc" + +RDEPENDS:${PN} += "opendds libgpiod glibc" + +export CIAO_ROOT="unused" +export DANCE_ROOT="unused" +export DDS_ROOT="${WORKDIR}/recipe-sysroot/usr/share/DDS_ROOT" +export ACE_ROOT="${DDS_ROOT}/ACE_wrappers" +export MPC_ROOT="${ACE_ROOT}/MPC" +export TAO_ROOT="${ACE_ROOT}/TAO" + +export LIBCHECK_PREFIX="${WORKDIR}/recipe-sysroot/usr" +export INSTALL_PREFIX="${WORKDIR}/recipe-sysroot/usr" + +export HOST_DDS="${WORKDIR}/recipe-sysroot-native/usr/bin/DDS_HOST_ROOT" +export SSL_ROOT="${WORKDIR}/recipe-sysroot-native/usr" +export XERCESCROOT="${WORKDIR}/recipe-sysroot-native/usr" +export CPATH="${WORKDIR}/recipe-sysroot/usr/include" +export CCFLAGS="--sysroot=${WORKDIR}/recipe-sysroot" + +S = "${WORKDIR}/SmartlockApp" +B = "${S}" + +do_configure() { + ${WORKDIR}/recipe-sysroot/usr/share/ace/bin/mwc.pl -type gnuace -features 'no_cxx11=0,no_pigpio=1,ssl=1,xerces3=1,openssl11=1,no_opendds_security=0,cross_compile=1' +} + +do_install() { + install -d ${D}${bindir} + install -m 0755 smartlock ${D}${bindir} + install -m 0755 ${WORKDIR}/smartlock.sh ${D}${bindir} + + install -d ${D}${sysconfdir}/smartlock + install -m 0644 ${WORKDIR}/rtps.ini ${D}${sysconfdir}/smartlock + install -m 0644 ${WORKDIR}/smartlock.ini ${D}${sysconfdir}/smartlock + install -m 0644 ${WORKDIR}/dpm_password ${D}${sysconfdir}/smartlock + + install -d ${D}${libdir} + cp Idl/libSmartLock_Idl.so.* ${D}${libdir} + for shared_lib in ${D}${libdir}/*.so.*; do + if [ -f $shared_lib ]; then + baselib=$(basename $shared_lib) + shortlib=$(echo $baselib | sed 's/.so.*//') + extn=$(echo $baselib | sed -n 's/^[^.]*\.so//p') + extn=$(echo $extn | sed 's/[^. 0-9]*//g') + while [ -n "$extn" ]; do + extn=$(echo $extn | sed 's/\.[^.]*$//') + ln -sf $baselib ${D}${libdir}/$shortlib.so$extn + done + fi + done +}