diff --git a/examples/worlds/elevator.sdf b/examples/worlds/elevator.sdf
new file mode 100644
index 0000000000..d2b1238e6b
--- /dev/null
+++ b/examples/worlds/elevator.sdf
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+ 0.001
+ 1.0
+
+
+
+
+ ogre2
+
+
+
+
+
+ true
+ 0 0 10 0 0 0
+ 1 1 1 1
+ 0.5 0.5 0.5 1
+
+ 1000
+ 0.9
+ 0.01
+ 0.001
+
+ -0.5 0.1 -0.9
+
+
+
+ true
+
+
+
+
+ 0 0 1
+
+
+
+
+
+
+ 0 0 1
+ 100 100
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+
+
+
+ https://fuel.ignitionrobotics.org/1.0/nlamprian/models/Elevator
+
+
+
+
diff --git a/src/gui/plugins/shapes/Shapes.cc b/src/gui/plugins/shapes/Shapes.cc
index e69a33c7e8..f14f3187cf 100644
--- a/src/gui/plugins/shapes/Shapes.cc
+++ b/src/gui/plugins/shapes/Shapes.cc
@@ -39,11 +39,6 @@ namespace ignition::gazebo
{
class ShapesPrivate
{
- /// \brief Ignition communication node.
- public: transport::Node node;
-
- /// \brief Transform control service name
- public: std::string service;
};
}
diff --git a/src/systems/CMakeLists.txt b/src/systems/CMakeLists.txt
index 2c84fc2648..afbc1477f7 100644
--- a/src/systems/CMakeLists.txt
+++ b/src/systems/CMakeLists.txt
@@ -21,7 +21,7 @@ function(gz_add_system system_name)
set(options)
set(oneValueArgs)
- set(multiValueArgs SOURCES PUBLIC_LINK_LIBS PRIVATE_LINK_LIBS PRIVATE_COMPILE_DEFS)
+ set(multiValueArgs SOURCES PUBLIC_LINK_LIBS PRIVATE_INCLUDE_DIRS PRIVATE_LINK_LIBS PRIVATE_COMPILE_DEFS)
cmake_parse_arguments(gz_add_system "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
@@ -58,6 +58,13 @@ function(gz_add_system system_name)
ignition-plugin${IGN_PLUGIN_VER}::register
)
+ if(gz_add_system_PRIVATE_INCLUDE_DIRS)
+ target_include_directories(${system_target}
+ PRIVATE
+ ${gz_add_system_PRIVATE_INCLUDE_DIRS}
+ )
+ endif()
+
if(gz_add_system_PRIVATE_COMPILE_DEFS)
target_compile_definitions(${system_target}
PRIVATE
@@ -98,6 +105,10 @@ add_subdirectory(contact)
add_subdirectory(camera_video_recorder)
add_subdirectory(detachable_joint)
add_subdirectory(diff_drive)
+# elevator system causes compile erros on windows
+if (NOT WIN32)
+add_subdirectory(elevator)
+endif()
add_subdirectory(follow_actor)
add_subdirectory(hydrodynamics)
add_subdirectory(imu)
diff --git a/src/systems/elevator/CMakeLists.txt b/src/systems/elevator/CMakeLists.txt
new file mode 100644
index 0000000000..054ac3fc8b
--- /dev/null
+++ b/src/systems/elevator/CMakeLists.txt
@@ -0,0 +1,12 @@
+gz_add_system(elevator
+ SOURCES
+ Elevator.cc
+ utils/DoorTimer.cc
+ utils/JointMonitor.cc
+ PUBLIC_LINK_LIBS
+ ignition-common${IGN_COMMON_VER}::ignition-common${IGN_COMMON_VER}
+ ignition-transport${IGN_TRANSPORT_VER}::ignition-transport${IGN_TRANSPORT_VER}
+ PRIVATE_INCLUDE_DIRS
+ vender/afsm/include
+ vender/metapushkin/include
+)
diff --git a/src/systems/elevator/Elevator.cc b/src/systems/elevator/Elevator.cc
new file mode 100644
index 0000000000..d78bd26bb4
--- /dev/null
+++ b/src/systems/elevator/Elevator.cc
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Elevator.hh"
+#include "ElevatorCommonPrivate.hh"
+#include "ElevatorStateMachine.hh"
+#include "utils/DoorTimer.hh"
+#include "utils/JointMonitor.hh"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "ignition/gazebo/Model.hh"
+#include "ignition/gazebo/components/Joint.hh"
+#include "ignition/gazebo/components/JointAxis.hh"
+#include "ignition/gazebo/components/JointPosition.hh"
+#include "ignition/gazebo/components/JointVelocity.hh"
+#include "ignition/gazebo/components/Link.hh"
+#include "ignition/gazebo/components/Name.hh"
+#include "ignition/gazebo/components/Pose.hh"
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+class ElevatorPrivate : public ElevatorCommonPrivate
+{
+ /// \brief Destructor
+ public: virtual ~ElevatorPrivate() override;
+
+ /// \brief Initializes the cabin of the elevator
+ /// \param[in] _cabinJointName Name of the cabin joint
+ /// \param[in] _floorLinkPrefix Name prefix of the floor links
+ /// \param[in] _topicPrefix Topic prefix for the command publisher
+ /// \param[in] _ecm Entity component manager
+ /// \return True on successful initialization, or false otherwise
+ public: bool InitCabin(const std::string &_cabinJointName,
+ const std::string &_floorLinkPrefix,
+ const std::string &_topicPrefix,
+ EntityComponentManager &_ecm);
+
+ /// \brief Initializes the doors of the elevator
+ /// \param[in] _doorJointPrefix Name prefix of the door joints
+ /// \param[in] _topicPrefix Topic prefix for the command publishers
+ /// \param[in] _ecm Entity component manager
+ /// \return True on successful initialization, or false otherwise
+ public: bool InitDoors(const std::string &_doorJointPrefix,
+ const std::string &_topicPrefix,
+ EntityComponentManager &_ecm);
+
+ // Documentation inherited
+ public: virtual void StartDoorTimer(
+ int32_t _floorTarget,
+ const std::function &_timeoutCallback) override;
+
+ // Documentation inherited
+ public: virtual void SetDoorMonitor(
+ int32_t _floorTarget, double _jointTarget, double _posEps, double _velEps,
+ const std::function &_jointTargetReachedCallback) override;
+
+ // Documentation inherited
+ public: virtual void SetCabinMonitor(
+ int32_t _floorTarget, double _jointTarget, double _posEps, double _velEps,
+ const std::function &_jointTargetReachedCallback) override;
+
+ /// \brief Updates the elevator state based on the current cabin position and
+ /// then publishes the new state
+ /// \param[in] _info Current simulation step info
+ /// \param[in] _ecm Entity component manager
+ public: void UpdateState(const ignition::gazebo::UpdateInfo &_info,
+ const EntityComponentManager &_ecm);
+
+ /// \brief Callback for the door lidar scans
+ /// \param[in] _floorLevel Floor level
+ /// \param[in] _msg Laserscan message
+ public: void OnLidarMsg(size_t _floorLevel, const msgs::LaserScan &_msg);
+
+ /// \brief Callback for the elevator commands
+ /// \param[in] _msg Message that carries the floor level target
+ public: void OnCmdMsg(const msgs::Int32 &_msg);
+
+ /// \brief Ignition communication node
+ public: transport::Node node;
+
+ /// \brief Model to which this system belongs
+ public: Model model;
+
+ /// \brief Joints of the doors of the elevator
+ public: std::vector doorJoints;
+
+ /// \brief Joint of the cabin
+ public: Entity cabinJoint;
+
+ /// \brief State vector that identifies whether the doorway on each floor
+ /// level is blocked
+ public: std::vector isDoorwayBlockedStates;
+
+ /// \brief Timer that keeps the door at the target floor level open
+ public: std::unique_ptr doorTimer;
+
+ /// \brief Monitor that checks whether the door at the target floor level has
+ /// been opened or closed
+ public: JointMonitor doorJointMonitor;
+
+ /// \brief Monitor that checks whether the cabin has reached the target floor
+ /// level
+ public: JointMonitor cabinJointMonitor;
+
+ /// \brief System update period calculated from
+ public: std::chrono::steady_clock::duration updatePeriod{0};
+
+ /// \brief Last system update simulation time
+ public: std::chrono::steady_clock::duration lastUpdateTime{0};
+
+ /// \brief State publish period calculated from
+ public: std::chrono::steady_clock::duration statePubPeriod{0};
+
+ /// \brief Last state publish simulation time
+ public: std::chrono::steady_clock::duration lastStatePubTime{0};
+
+ /// \brief Elevator state publisher
+ public: transport::Node::Publisher statePub;
+
+ /// \brief Elevator state message
+ public: msgs::Int32 stateMsg;
+
+ /// \brief Elevator state machine
+ public: std::unique_ptr stateMachine;
+};
+
+//////////////////////////////////////////////////
+Elevator::Elevator() : dataPtr(std::make_shared()) {}
+
+//////////////////////////////////////////////////
+void Elevator::Configure(const Entity &_entity,
+ const std::shared_ptr &_sdf,
+ EntityComponentManager &_ecm,
+ EventManager & /*_eventMgr*/)
+{
+ this->dataPtr->model = Model(_entity);
+
+ // Initialize system update period
+ double rate = _sdf->Get("update_rate", 10).first;
+ std::chrono::duration period{rate > 0 ? 1 / rate : 0};
+ this->dataPtr->updatePeriod =
+ std::chrono::duration_cast(period);
+
+ // Get floor link prefix
+ std::string floorLinkPrefix =
+ _sdf->Get("floor_link_prefix", "floor_").first;
+
+ // Get door joint prefix
+ std::string doorJointPrefix =
+ _sdf->Get("door_joint_prefix", "door_").first;
+
+ // Get cabin joint name
+ std::string cabinJointName =
+ _sdf->Get("cabin_joint", "lift").first;
+
+ std::string topicPrefix = "/model/" + this->dataPtr->model.Name(_ecm);
+
+ if (!this->dataPtr->InitCabin(cabinJointName, floorLinkPrefix, topicPrefix,
+ _ecm))
+ return;
+
+ if (!this->dataPtr->InitDoors(doorJointPrefix, topicPrefix, _ecm))
+ return;
+
+ // Initialize door timer
+ double duration = _sdf->Get("open_door_wait_duration", 5.0).first;
+ this->dataPtr->doorTimer = std::make_unique(
+ std::chrono::duration_cast(
+ std::chrono::duration(duration)));
+
+ // Initialize state publisher
+ std::string stateTopicName =
+ _sdf->Get("state_topic", topicPrefix + "/state").first;
+ // NOTE Topic should be latched; no latch option so far
+ this->dataPtr->statePub =
+ this->dataPtr->node.Advertise(stateTopicName);
+
+ // Initialize state publish period
+ double stateRate = _sdf->Get("state_publish_rate", 5.0).first;
+ std::chrono::duration statePeriod{stateRate > 0 ? 1 / stateRate : 0};
+ this->dataPtr->statePubPeriod =
+ std::chrono::duration_cast(
+ statePeriod);
+
+ // Initialize state machine
+ this->dataPtr->stateMachine =
+ std::make_unique(this->dataPtr);
+
+ // Subscribe to command topic
+ std::string cmdTopicName =
+ _sdf->Get("cmd_topic", topicPrefix + "/cmd").first;
+ this->dataPtr->node.Subscribe(cmdTopicName, &ElevatorPrivate::OnCmdMsg,
+ this->dataPtr.get());
+ ignmsg << "System " << this->dataPtr->model.Name(_ecm) << " subscribed to "
+ << cmdTopicName << " for command messages" << std::endl;
+}
+
+//////////////////////////////////////////////////
+void Elevator::PostUpdate(const UpdateInfo &_info,
+ const EntityComponentManager &_ecm)
+{
+ IGN_PROFILE("Elevator::PostUpdate");
+ if (_info.paused) return;
+
+ // Throttle update rate
+ auto elapsed = _info.simTime - this->dataPtr->lastUpdateTime;
+ if (elapsed > std::chrono::steady_clock::duration::zero() &&
+ elapsed < this->dataPtr->updatePeriod)
+ return;
+ this->dataPtr->lastUpdateTime = _info.simTime;
+
+ std::lock_guard lock(this->dataPtr->mutex);
+ this->dataPtr->UpdateState(_info, _ecm);
+ this->dataPtr->doorTimer->Update(
+ _info, this->dataPtr->isDoorwayBlockedStates[this->dataPtr->state]);
+ this->dataPtr->doorJointMonitor.Update(_ecm);
+ this->dataPtr->cabinJointMonitor.Update(_ecm);
+}
+
+//////////////////////////////////////////////////
+ElevatorPrivate::~ElevatorPrivate()
+{
+}
+
+//////////////////////////////////////////////////
+bool ElevatorPrivate::InitCabin(const std::string &_cabinJointName,
+ const std::string &_floorLinkPrefix,
+ const std::string &_topicPrefix,
+ EntityComponentManager &_ecm)
+{
+ // Validate and initialize cabin joint
+ this->cabinJoint = this->model.JointByName(_ecm, _cabinJointName);
+ if (this->cabinJoint == kNullEntity)
+ {
+ ignerr << "Failed to find cabin joint " << _cabinJointName << std::endl;
+ return false;
+ }
+ if (!_ecm.EntityHasComponentType(this->cabinJoint,
+ components::JointPosition().TypeId()))
+ _ecm.CreateComponent(this->cabinJoint, components::JointPosition());
+ if (!_ecm.EntityHasComponentType(this->cabinJoint,
+ components::JointVelocity().TypeId()))
+ _ecm.CreateComponent(this->cabinJoint, components::JointVelocity());
+
+ // Initialize cabin floor targets
+ size_t numFloorLinks = 0;
+ std::regex floorLinkRegex(_floorLinkPrefix + "\\d+");
+ std::vector links =
+ _ecm.ChildrenByComponents(this->model.Entity(), components::Link());
+ for (const auto &link : links)
+ {
+ auto name = _ecm.Component(link)->Data();
+ numFloorLinks += std::regex_match(name, floorLinkRegex);
+ }
+ for (size_t i = 0; i < numFloorLinks; ++i)
+ {
+ auto name = _floorLinkPrefix + std::to_string(i);
+ auto link = this->model.LinkByName(_ecm, name);
+ if (link == kNullEntity)
+ {
+ ignerr << "Failed to find floor link " << name << std::endl;
+ return false;
+ }
+ auto z = _ecm.Component(link)->Data().Z();
+ this->cabinTargets.push_back(z);
+ }
+
+ // Initialize cabin joint command publisher
+ std::string cabinJointCmdTopicName =
+ _topicPrefix + "/joint/" + _cabinJointName + "/0/cmd_pos";
+ this->cabinJointCmdPub =
+ this->node.Advertise(cabinJointCmdTopicName);
+
+ return true;
+}
+
+//////////////////////////////////////////////////
+bool ElevatorPrivate::InitDoors(const std::string &_doorJointPrefix,
+ const std::string &_topicPrefix,
+ EntityComponentManager &_ecm)
+{
+ for (size_t i = 0; i < this->cabinTargets.size(); ++i)
+ {
+ // Validate and initialize door joint
+ auto name = _doorJointPrefix + std::to_string(i);
+ auto joint = this->model.JointByName(_ecm, name);
+ if (joint == kNullEntity)
+ {
+ ignerr << "Failed to find door joint " << name << std::endl;
+ return false;
+ }
+ if (!_ecm.EntityHasComponentType(joint,
+ components::JointPosition().TypeId()))
+ _ecm.CreateComponent(joint, components::JointPosition());
+ if (!_ecm.EntityHasComponentType(joint,
+ components::JointVelocity().TypeId()))
+ _ecm.CreateComponent(joint, components::JointVelocity());
+ this->doorJoints.push_back(joint);
+
+ // Initialize open door target
+ auto upper = _ecm.Component(joint)->Data().Upper();
+ this->doorTargets.push_back(upper);
+
+ // Initialize door joint command publisher
+ std::string topicName = _topicPrefix + "/joint/" + name + "/0/cmd_pos";
+ auto pub = this->node.Advertise(topicName);
+ this->doorJointCmdPubs.push_back(pub);
+ }
+
+ // Initialize blocked doorway states
+ this->isDoorwayBlockedStates =
+ std::vector(this->doorJoints.size(), false);
+
+ // Subscribe to lidar topics
+ for (size_t i = 0; i < this->doorJoints.size(); ++i)
+ {
+ auto jointName = _doorJointPrefix + std::to_string(i);
+ std::string topicName = _topicPrefix + "/" + jointName + "/lidar";
+ std::function callback =
+ std::bind(&ElevatorPrivate::OnLidarMsg, this, i, std::placeholders::_1);
+ this->node.Subscribe(topicName, callback);
+ }
+
+ return true;
+}
+
+//////////////////////////////////////////////////
+void ElevatorPrivate::StartDoorTimer(
+ int32_t /*_floorTarget*/, const std::function &_timeoutCallback)
+{
+ std::lock_guard lock(this->mutex);
+ this->doorTimer->Configure(this->lastUpdateTime, _timeoutCallback);
+}
+
+//////////////////////////////////////////////////
+void ElevatorPrivate::SetDoorMonitor(
+ int32_t _floorTarget, double _jointTarget, double _posEps, double _velEps,
+ const std::function &_jointTargetReachedCallback)
+{
+ std::lock_guard lock(this->mutex);
+ this->doorJointMonitor.Configure(this->doorJoints[_floorTarget], _jointTarget,
+ _posEps, _velEps,
+ _jointTargetReachedCallback);
+}
+
+//////////////////////////////////////////////////
+void ElevatorPrivate::SetCabinMonitor(
+ int32_t /*_floorTarget*/, double _jointTarget, double _posEps,
+ double _velEps, const std::function &_jointTargetReachedCallback)
+{
+ std::lock_guard lock(this->mutex);
+ this->cabinJointMonitor.Configure(this->cabinJoint, _jointTarget, _posEps,
+ _velEps, _jointTargetReachedCallback);
+}
+
+//////////////////////////////////////////////////
+void ElevatorPrivate::UpdateState(const ignition::gazebo::UpdateInfo &_info,
+ const EntityComponentManager &_ecm)
+{
+ // Update state
+ double pos =
+ _ecm.ComponentData(this->cabinJoint)->front();
+ std::vector diffs(this->cabinTargets.size());
+ std::transform(this->cabinTargets.begin(), this->cabinTargets.end(),
+ diffs.begin(),
+ [&pos](auto target) { return std::fabs(target - pos); });
+ auto it = std::min_element(diffs.begin(), diffs.end());
+ this->state = static_cast(std::distance(diffs.begin(), it));
+
+ // Throttle publish rate
+ auto elapsed = _info.simTime - this->lastStatePubTime;
+ if (elapsed > std::chrono::steady_clock::duration::zero() &&
+ elapsed < this->statePubPeriod)
+ return;
+ this->lastStatePubTime = _info.simTime;
+
+ this->stateMsg.set_data(this->state);
+ this->statePub.Publish(this->stateMsg);
+}
+
+//////////////////////////////////////////////////
+void ElevatorPrivate::OnLidarMsg(size_t _floorLevel,
+ const msgs::LaserScan &_msg)
+{
+ bool isDoorwayBlocked = _msg.ranges(0) < _msg.range_max() - 0.005;
+ if (isDoorwayBlocked == this->isDoorwayBlockedStates[_floorLevel]) return;
+ std::lock_guard lock(this->mutex);
+ this->isDoorwayBlockedStates[_floorLevel] = isDoorwayBlocked;
+}
+
+//////////////////////////////////////////////////
+void ElevatorPrivate::OnCmdMsg(const msgs::Int32 &_msg)
+{
+ auto target = _msg.data();
+ if (target < 0 || target >= static_cast(this->cabinTargets.size()))
+ {
+ ignwarn << "Invalid target [" << target << "]; command must be in [0,"
+ << this->cabinTargets.size() << ")" << std::endl;
+ return;
+ }
+ this->stateMachine->process_event(events::EnqueueNewTarget(_msg.data()));
+}
+
+IGNITION_ADD_PLUGIN(Elevator, System, Elevator::ISystemConfigure,
+ Elevator::ISystemPostUpdate)
+
+IGNITION_ADD_PLUGIN_ALIAS(Elevator, "ignition::gazebo::systems::Elevator")
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
diff --git a/src/systems/elevator/Elevator.hh b/src/systems/elevator/Elevator.hh
new file mode 100644
index 0000000000..cbb44da5b7
--- /dev/null
+++ b/src/systems/elevator/Elevator.hh
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#ifndef IGNITION_GAZEBO_SYSTEMS_ELEVATOR_HH_
+#define IGNITION_GAZEBO_SYSTEMS_ELEVATOR_HH_
+
+#include
+
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+// Data forward declaration
+class ElevatorPrivate;
+
+/// \brief System that controls an elevator. It closely models the structure
+/// and functionality of a real elevator. It individually controls the cabin
+/// and doors of the elevator, queues commands for the elevator and stops at
+/// intermediate floors if a command is received while the last one is ongoing,
+/// and keeps a door open if the doorway is blocked. The model of the elevator
+/// can have arbitrarily many floor levels and at arbitrary heights each.
+///
+/// ## Assumptions on the Elevator Model
+///
+/// In the default configuration, the cabin is at the ground floor and the
+/// doors are closed
+///
+/// In the default configuration, the cabin and door joints are at zero position
+///
+/// There are reference floor links along the cabin joint that indicate the
+/// cabin joint positions for each floor. The names of links follow the pattern
+/// indicated by ``
+///
+/// There is a door in each floor and the names of the doors follow the pattern
+/// indicated by ``
+///
+/// Each cabin and door joint has an associated joint position controller
+/// system that listens for command to
+/// `/model/{model_name}/joint/{joint_name}/0/cmd_pos`
+///
+/// Each door (optionally) has a lidar that, if intercepted, indicates that the
+/// doorway is blocked. The lidar publishes sensor data on topic
+/// `/model/{model_name}/{door_joint_name}/lidar`
+///
+/// ## System Parameters
+///
+/// ``: System update rate. This element is optional and the
+/// default value is 10Hz. A value of zero gets translated to the simulation
+/// rate (no throttling for the system).
+///
+/// ``: Prefix in the names of the links that function as
+/// a reference for each floor level. When the elevator is requested to move
+/// to a given floor level, the cabin is commanded to move to the height of
+/// the corresponding floor link. The names of the links will be expected to
+/// be `{prerix}i`, where \f$i=[0,N)\f$ and N is the number of floors. This
+/// element is optional and the default value is `floor_`.
+///
+/// ``: Prefix in the names of the joints that control the
+/// doors of the elevator. The names of the joints will be expected to be
+/// `{prerix}i`, where \f$i=[0,N)\f$ and N is the number of floors. This
+/// element is optional and the default value is `door_`.
+///
+/// ``: Name of the joint that controls the position of the
+/// cabin. This element is optional and the default value is `lift`.
+///
+/// ``: Topic to which this system will subscribe in order to
+/// receive command messages. This element is optional and the default value
+/// is `/model/{model_name}/cmd`.
+///
+/// ``: Topic on which this system will publish state (current
+/// floor) messages. This element is optional and the default value is
+/// `/model/{model_name}/state`.
+///
+/// ``: State publication rate. This rate is bounded by
+/// ``. This element is optional and the default value is 5Hz.
+///
+/// ``: Time to wait with a door open before the door
+/// closes. This element is optional and the default value is 5 sec.
+class IGNITION_GAZEBO_VISIBLE Elevator : public System,
+ public ISystemConfigure,
+ public ISystemPostUpdate
+{
+ /// \brief Constructor
+ public: Elevator();
+
+ /// \brief Destructor
+ public: ~Elevator() override = default;
+
+ // Documentation inherited
+ public: void Configure(const Entity &_entity,
+ const std::shared_ptr &_sdf,
+ EntityComponentManager &_ecm,
+ EventManager &_eventMgr) override;
+
+ // Documentation inherited
+ public: void PostUpdate(const UpdateInfo &_info,
+ const EntityComponentManager &_ecm) override;
+
+ /// \brief Private data pointer
+ private: std::shared_ptr dataPtr;
+};
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
+
+#endif // IGNITION_GAZEBO_SYSTEMS_ELEVATOR_HH_
diff --git a/src/systems/elevator/ElevatorCommonPrivate.hh b/src/systems/elevator/ElevatorCommonPrivate.hh
new file mode 100644
index 0000000000..ec787e3b91
--- /dev/null
+++ b/src/systems/elevator/ElevatorCommonPrivate.hh
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#ifndef IGNITION_GAZEBO_SYSTEMS_ELEVATOR_COMMON_PRIVATE_HH_
+#define IGNITION_GAZEBO_SYSTEMS_ELEVATOR_COMMON_PRIVATE_HH_
+
+#include
+#include
+#include
+
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+class ElevatorCommonPrivate
+{
+ /// \brief Destructor
+ public: virtual ~ElevatorCommonPrivate() = default;
+
+ /// \brief Starts the timer that keeps the door at the target floor level open
+ /// \param[in] _floorTarget Target floor level
+ /// \param[in] _timeoutCallback Function to call upon timeout
+ public: virtual void StartDoorTimer(
+ int32_t _floorTarget, const std::function &_timeoutCallback) = 0;
+
+ /// \brief Configures the monitor that checks the state of the joint of the
+ /// door at the target floor level
+ /// \param[in] _floorTarget Target floor level
+ /// \param[in] _jointTarget Last joint target (command)
+ /// \param[in] _posEps Position tolerance
+ /// \param[in] _velEps Velocity tolerance
+ /// \param[in] _jointTargetReachedCallback Function to call when the door
+ /// joint reaches its target
+ public: virtual void SetDoorMonitor(
+ int32_t _floorTarget, double _jointTarget, double _posEps, double _velEps,
+ const std::function &_jointTargetReachedCallback) = 0;
+
+ /// \brief Configures the monitor that checks the state of the cabin joint
+ /// \param[in] _floorTarget Target floor level
+ /// \param[in] _jointTarget Last joint target (command)
+ /// \param[in] _posEps Position tolerance
+ /// \param[in] _velEps Velocity tolerance
+ /// \param[in] _jointTargetReachedCallback Function to call when the cabin
+ /// joint reaches its target
+ public: virtual void SetCabinMonitor(
+ int32_t _floorTarget, double _jointTarget, double _posEps, double _velEps,
+ const std::function &_jointTargetReachedCallback) = 0;
+
+ /// \brief Door joint command publishers
+ public: std::vector doorJointCmdPubs;
+
+ /// \brief Cabin joint command publisher
+ public: transport::Node::Publisher cabinJointCmdPub;
+
+ /// \brief Joint commands for opening the door at each floor level
+ public: std::vector doorTargets;
+
+ /// \brief Cabin joint commands for each floor level
+ public: std::vector cabinTargets;
+
+ /// \brief Current floor at which the cabin is
+ public: int32_t state{0};
+
+ /// \brief A mutex to protect access to targets and state
+ public: std::recursive_mutex mutex;
+};
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
+
+#endif // IGNITION_GAZEBO_SYSTEMS_ELEVATOR_COMMON_PRIVATE_HH_
diff --git a/src/systems/elevator/ElevatorStateMachine.hh b/src/systems/elevator/ElevatorStateMachine.hh
new file mode 100644
index 0000000000..6dfa7811f3
--- /dev/null
+++ b/src/systems/elevator/ElevatorStateMachine.hh
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#ifndef IGNITION_GAZEBO_SYSTEMS_ELEVATOR_STATE_MACHINE_HH_
+#define IGNITION_GAZEBO_SYSTEMS_ELEVATOR_STATE_MACHINE_HH_
+
+#include
+
+#include
+#include
+
+#include "afsm/fsm.hpp"
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+// Data forward declarations
+class ElevatorCommonPrivate;
+class ElevatorStateMachinePrivate;
+
+// Event forward declarations
+namespace events
+{
+ struct EnqueueNewTarget;
+ struct NewTarget;
+ struct DoorOpen;
+ struct DoorClosed;
+ struct Timeout;
+ struct CabinAtTarget;
+} // namespace events
+
+// Action forward declarations
+namespace actions
+{
+ template
+ struct EnqueueNewTarget;
+ struct NewTarget;
+ struct CabinAtTarget;
+} // namespace actions
+
+// Guard forward declarations
+namespace guards
+{
+ template
+ struct IsInState;
+ struct CabinAtTarget;
+ struct NoQueuedTarget;
+} // namespace guards
+
+/// \brief Elevator state machine frontend. Defines the transition table and
+/// initial state of the state machine.
+class ElevatorStateMachineDef
+ : public ::afsm::def::state_machine
+{
+ // State forward declarations
+ struct IdleState;
+ template
+ struct DoorState;
+ struct OpenDoorState;
+ struct CloseDoorState;
+ struct WaitState;
+ struct MoveCabinState;
+
+ /// \brief Constructor
+ /// \param[in] _system Data that are common to both the system and the state
+ /// machine.
+ public: ElevatorStateMachineDef(
+ const std::shared_ptr &_system);
+
+ /// \brief Destructor
+ public: ~ElevatorStateMachineDef();
+
+ /// \brief Initial state of the state machine
+ public: using initial_state = IdleState;
+
+ /// \brief Transition transition table
+ public: using internal_transitions = transition_table <
+ in,
+ guards::IsInState >,
+ in,
+ guards::IsInState >
+ >;
+
+ /// \brief Transition table
+ public: using transitions = transition_table<
+ // +--------------------------------------------------------------+
+ tr,
+ tr >,
+ // +--------------------------------------------------------------+
+ tr,
+ // +--------------------------------------------------------------+
+ tr,
+ // +--------------------------------------------------------------+
+ tr,
+ tr >,
+ // +--------------------------------------------------------------+
+ tr
+ >;
+
+ /// \brief Public data pointer
+ public: std::unique_ptr dataPtr;
+};
+
+/// \brief Elevator state machine backend
+using ElevatorStateMachine = ::afsm::state_machine;
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
+
+#include "state_machine/ElevatorStateMachineImpl.hh"
+
+#endif // IGNITION_GAZEBO_SYSTEMS_ELEVATOR_STATE_MACHINE_HH_
diff --git a/src/systems/elevator/state_machine/ActionsImpl.hh b/src/systems/elevator/state_machine/ActionsImpl.hh
new file mode 100644
index 0000000000..e613fc6388
--- /dev/null
+++ b/src/systems/elevator/state_machine/ActionsImpl.hh
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#include "../ElevatorStateMachine.hh"
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+namespace actions
+{
+/// \brief Action that enqueues a new target in the target queue.
+/// \details After the new target has been added in the queue, depending on the
+/// template parameter, an events::NewTarget is triggered.
+template
+struct EnqueueNewTarget
+{
+ /// \brief Function call operator
+ /// \param[in] _event Event that triggered the action
+ /// \param[in] _fsm State machine with which the action is associated
+ /// \param[in] _source Source state
+ /// \param[in] _target Target state
+ public: template
+ void operator()(const Event &_event, Fsm &_fsm, Source & /*_source*/,
+ Target & /*_target*/)
+ {
+ _fsm.dataPtr->EnqueueNewTarget(_event.target);
+ if (trigger)
+ _fsm.process_event(events::NewTarget());
+ }
+};
+
+/// \brief Action that cleans up the target queue when a new target is
+/// processed.
+struct NewTarget
+{
+ /// \brief Function call operator
+ /// \param[in] _event Event that triggered the action
+ /// \param[in] _fsm State machine with which the action is associated
+ /// \param[in] _source Source state
+ /// \param[in] _target Target state
+ public: template
+ void operator()(const Event & /*_event*/, Fsm &_fsm, Source & /*_source*/,
+ Target & /*_target*/)
+ {
+ std::lock_guard lock(_fsm.dataPtr->system->mutex);
+ if (_fsm.dataPtr->targets.front() == _fsm.dataPtr->system->state)
+ _fsm.dataPtr->targets.pop_front();
+ }
+};
+
+/// \brief Action that cleans up the target queue when the cabin reaches the
+/// target floor level.
+struct CabinAtTarget
+{
+ /// \brief Function call operator
+ /// \param[in] _event Event that triggered the action
+ /// \param[in] _fsm State machine with which the action is associated
+ /// \param[in] _source Source state
+ /// \param[in] _target Target state
+ public: template
+ void operator()(const Event & /*_event*/, Fsm &_fsm, Source & /*_source*/,
+ Target & /*_target*/)
+ {
+ std::lock_guard lock(_fsm.dataPtr->system->mutex);
+ _fsm.dataPtr->targets.pop_front();
+ }
+};
+
+} // namespace actions
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
diff --git a/src/systems/elevator/state_machine/ElevatorStateMachineImpl.hh b/src/systems/elevator/state_machine/ElevatorStateMachineImpl.hh
new file mode 100644
index 0000000000..a995a69ecd
--- /dev/null
+++ b/src/systems/elevator/state_machine/ElevatorStateMachineImpl.hh
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "../ElevatorStateMachine.hh"
+
+#include "../ElevatorCommonPrivate.hh"
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+class ElevatorStateMachinePrivate
+{
+ /// \brief Constructor
+ /// \param[in] _system Data of the enclosing system
+ public: ElevatorStateMachinePrivate(
+ const std::shared_ptr &_system);
+
+ /// \brief Adds a new target to the queue
+ /// \details If there is another target in the queue, and the new target is
+ /// an intermediate stop, the new target will take precedence
+ /// \param[in] _target Target to add to the queue
+ public: void EnqueueNewTarget(double _target);
+
+ /// \brief Publishes a command message
+ /// \param[in] _pub Joint command publisher
+ /// \param[in] _cmd Joint command
+ public: void SendCmd(transport::Node::Publisher &_pub, double _cmd);
+
+ /// \brief Data of the enclosing system
+ public: std::shared_ptr system;
+
+ /// \brief Floor target queue
+ public: std::deque targets;
+};
+
+//////////////////////////////////////////////////
+ElevatorStateMachinePrivate::ElevatorStateMachinePrivate(
+ const std::shared_ptr &_system)
+ : system(_system)
+{
+}
+
+//////////////////////////////////////////////////
+void ElevatorStateMachinePrivate::EnqueueNewTarget(double _target)
+{
+ // Ignore duplicate targets
+ auto it = std::find(this->targets.cbegin(), this->targets.cend(), _target);
+ if (it != this->targets.cend())
+ return;
+
+ // Prioritize target in the queue
+ bool enqueued = false;
+ int32_t prevTarget = this->system->state;
+ for (it = this->targets.cbegin(); it != this->targets.cend(); ++it)
+ {
+ int32_t lower = prevTarget;
+ int32_t upper = *it;
+ if (upper < lower) std::swap(lower, upper);
+ if (_target >= lower && _target < upper)
+ {
+ this->targets.insert(it, _target);
+ enqueued = true;
+ break;
+ }
+ prevTarget = *it;
+ }
+ if (!enqueued)
+ this->targets.push_back(_target);
+
+ std::ostringstream ss;
+ ss << "The elevator enqueued target " << _target << " [ ";
+ for (const auto &target : this->targets) ss << target << " ";
+ ignmsg << ss.str() << "]" << std::endl;
+}
+
+//////////////////////////////////////////////////
+void ElevatorStateMachinePrivate::SendCmd(transport::Node::Publisher &_pub,
+ double _cmd)
+{
+ msgs::Double msg;
+ msg.set_data(_cmd);
+ _pub.Publish(msg);
+}
+
+//////////////////////////////////////////////////
+ElevatorStateMachineDef::ElevatorStateMachineDef(
+ const std::shared_ptr &_system)
+ : dataPtr(std::make_unique(_system))
+{
+}
+
+//////////////////////////////////////////////////
+ElevatorStateMachineDef::~ElevatorStateMachineDef() = default;
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
+
+#include "EventsImpl.hh"
+
+#include "ActionsImpl.hh"
+#include "GuardsImpl.hh"
+#include "StatesImpl.hh"
diff --git a/src/systems/elevator/state_machine/EventsImpl.hh b/src/systems/elevator/state_machine/EventsImpl.hh
new file mode 100644
index 0000000000..1aa4d9a781
--- /dev/null
+++ b/src/systems/elevator/state_machine/EventsImpl.hh
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+namespace events
+{
+/// \brief Event that signifies there is a new target that needs to be enqueued.
+struct EnqueueNewTarget
+{
+ /// \brief Constructor
+ /// \param[in] _target New target
+ public: EnqueueNewTarget(double _target) : target(_target)
+ {
+ }
+
+ /// \brief target New target
+ public: double target;
+};
+
+/// \brief Event that signifies a new target will be processed.
+struct NewTarget
+{
+};
+
+/// \brief Event that signifies the door at the target floor level has been
+/// opened.
+struct DoorOpen
+{
+};
+
+/// \brief Event that signifies the door at the target floor level has been
+/// closed.
+struct DoorClosed
+{
+};
+
+/// \brief Event that signifies the door at the target floor level has remained
+/// open for the required amount of time.
+struct Timeout
+{
+};
+
+/// \brief Event that signifies the cabin has reached the target floor level.
+struct CabinAtTarget
+{
+};
+
+} // namespace events
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
diff --git a/src/systems/elevator/state_machine/GuardsImpl.hh b/src/systems/elevator/state_machine/GuardsImpl.hh
new file mode 100644
index 0000000000..ec5ac4c706
--- /dev/null
+++ b/src/systems/elevator/state_machine/GuardsImpl.hh
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+namespace guards
+{
+/// \brief Guard that checks whether the state machine is in a given state.
+/// \note The template parameter generalizes the target state.
+template
+struct IsInState
+{
+ /// \brief Function call operator
+ /// \param[in] _fsm State machine with which the guard is associated
+ public: template
+ bool operator()(const Fsm &_fsm, const State &)
+ {
+ // NOTE Mind the inversion; the internal transition table needs
+ // the guard to return false in order to trigger a transition
+ return !_fsm.template is_in_state();
+ }
+};
+
+/// \brief Guard that checks whether the cabin is at the target floor level.
+struct CabinAtTarget
+{
+ /// \brief Function call operator
+ /// \param[in] _fsm State machine with which the guard is associated
+ public: template
+ bool operator()(const Fsm &_fsm, const State &)
+ {
+ std::lock_guard lock(_fsm.dataPtr->system->mutex);
+ return _fsm.dataPtr->targets.front() == _fsm.dataPtr->system->state;
+ }
+};
+
+/// \brief Guard that checks whether the target queue is empty.
+struct NoQueuedTarget
+{
+ /// \brief Function call operator
+ /// \param[in] _fsm State machine with which the guard is associated
+ public: template
+ bool operator()(const Fsm &_fsm, const State &)
+ {
+ std::lock_guard lock(_fsm.dataPtr->system->mutex);
+ return _fsm.dataPtr->targets.empty();
+ }
+};
+
+} // namespace guards
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
diff --git a/src/systems/elevator/state_machine/StatesImpl.hh b/src/systems/elevator/state_machine/StatesImpl.hh
new file mode 100644
index 0000000000..accd7f4a47
--- /dev/null
+++ b/src/systems/elevator/state_machine/StatesImpl.hh
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#include
+#include
+
+#include "../ElevatorStateMachine.hh"
+
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+/// \brief State at which the elevator is idling.
+struct ElevatorStateMachineDef::IdleState : state
+{
+ /// \brief Logs the entry to the state
+ public: template
+ void on_enter(const Event &, FSM &)
+ {
+ ignmsg << "The elevator is idling" << std::endl;
+ }
+};
+
+/// \brief Virtual state at which the elevator is opening or closing a door.
+template
+struct ElevatorStateMachineDef::DoorState : state>
+{
+ /// \brief Constructor
+ /// \param[in] _posEps Position tolerance when checking if a door has opened
+ /// or closed
+ /// \param[in] _velEps Velocity tolerance when checking if a door has opened
+ /// or closed
+ public: DoorState(double _posEps = 2e-2, double _velEps = 1e-2)
+ : posEps(_posEps), velEps(_velEps)
+ {
+ }
+
+ /// \brief Destructor
+ public: virtual ~DoorState()
+ {
+ }
+
+ /// \brief Sends the opening or closing command to the door joint of the
+ /// target floor and configures the monitor that checks the joint state
+ /// \param[in] _fsm State machine to which the state belongs
+ public: template
+ void on_enter(const Event &, FSM &_fsm)
+ {
+ const auto &data = _fsm.dataPtr;
+ int32_t floorTarget = data->system->state;
+ ignmsg << "The elevator is " << this->Report(data) << std::endl;
+
+ double jointTarget = this->JointTarget(data, floorTarget);
+ data->SendCmd(data->system->doorJointCmdPubs[floorTarget], jointTarget);
+ this->triggerEvent = [&_fsm] { _fsm.process_event(E()); };
+ data->system->SetDoorMonitor(
+ floorTarget, jointTarget, this->posEps, this->velEps,
+ std::bind(&DoorState::OnJointTargetReached, this));
+ }
+
+ /// \brief Gets the message that's being reported when entering the state
+ /// \param[in] _data State machine data
+ /// \return String with the message the state has to report
+ private: virtual std::string Report(
+ const std::unique_ptr &_data) const = 0;
+
+ /// \brief Gets the joint position for opening or closing a door
+ /// \param[in] _data State machine data
+ /// \param[in] _floorTarget Target floor level
+ /// \return Joint position
+ private: virtual double JointTarget(
+ const std::unique_ptr &_data,
+ int32_t _floorTarget) const = 0;
+
+ /// \brief Method that gets called when a door joint reaches its target
+ private: void OnJointTargetReached()
+ {
+ this->triggerEvent();
+ }
+
+ /// \brief Positiion tolerance
+ private: double posEps;
+
+ /// \brief Velocity tolerance
+ private: double velEps;
+
+ /// \brief Triggers the exit event
+ private: std::function triggerEvent;
+};
+
+/// \brief State at which the elevator is opening a door.
+struct ElevatorStateMachineDef::OpenDoorState : DoorState
+{
+ /// \brief This state defers EnqueueNewTargetEvent events
+ public: using deferred_events = type_tuple;
+
+ /// \brief Constructor
+ public: OpenDoorState() = default;
+
+ // Documentation inherited
+ private: virtual std::string Report(
+ const std::unique_ptr &_data) const override
+ {
+ return "opening door " + std::to_string(_data->system->state);
+ }
+
+ // Documentation inherited
+ private: virtual double JointTarget(
+ const std::unique_ptr &_data,
+ int32_t _floorTarget) const override
+ {
+ return _data->system->doorTargets[_floorTarget];
+ }
+};
+
+/// \brief State at which the elevator is closing a door.
+struct ElevatorStateMachineDef::CloseDoorState : DoorState
+{
+ /// \brief Constructor
+ public: CloseDoorState() = default;
+
+ // Documentation inherited
+ private: virtual std::string Report(
+ const std::unique_ptr &_data) const override
+ {
+ return "closing door " + std::to_string(_data->system->state);
+ }
+
+ // Documentation inherited
+ private: virtual double JointTarget(
+ const std::unique_ptr & /*_data*/,
+ int32_t /*_floorTarget*/) const override
+ {
+ return 0.0;
+ }
+};
+
+/// \brief State at which the elevator is waiting with a door open.
+struct ElevatorStateMachineDef::WaitState : state
+{
+ /// \brief This state defers EnqueueNewTargetEvent events
+ public: using deferred_events = type_tuple;
+
+ /// \brief Starts the timer that keeps the door at the target floor level open
+ /// \param[in] _fsm State machine to which the state belongs
+ public: template
+ void on_enter(const Event &, FSM &_fsm)
+ {
+ const auto &data = _fsm.dataPtr;
+ ignmsg << "The elevator is waiting to close door " << data->system->state
+ << std::endl;
+
+ this->triggerEvent = [&_fsm] { _fsm.process_event(events::Timeout()); };
+ data->system->StartDoorTimer(data->system->state,
+ std::bind(&WaitState::OnTimeout, this));
+ }
+
+ /// \brief Method that gets called upon timeout
+ private: void OnTimeout()
+ {
+ this->triggerEvent();
+ }
+
+ /// \brief Triggers the exit event
+ private: std::function triggerEvent;
+};
+
+/// \brief State at which the elevator is moving the cabin to the target floor.
+struct ElevatorStateMachineDef::MoveCabinState : state
+{
+ /// \brief This state defers EnqueueNewTargetEvent events
+ public: using deferred_events = type_tuple;
+
+ /// \brief Constructor
+ /// \param[in] _posEps Position tolerance when checking if the cabin has
+ /// reached the target floor level
+ /// \param[in] _velEps Velocity tolerance when checking if the cabin has
+ /// reached the target floor level
+ public: MoveCabinState(double _posEps = 15e-2, double _velEps = 1e-2)
+ : posEps(_posEps), velEps(_velEps)
+ {
+ }
+
+ /// \brief Sends the command that moves the cabin joint to the target floor
+ /// and configures the monitor that checks the joint state
+ /// \param[in] _fsm State machine to which the state belongs
+ public: template
+ void on_enter(const Event &, FSM &_fsm)
+ {
+ const auto &data = _fsm.dataPtr;
+ int32_t floorTarget = data->targets.front();
+ ignmsg << "The elevator is moving the cabin [ " << data->system->state
+ << " -> " << floorTarget << " ]" << std::endl;
+
+ double jointTarget = data->system->cabinTargets[floorTarget];
+ data->SendCmd(data->system->cabinJointCmdPub, jointTarget);
+ this->triggerEvent = [&_fsm] {
+ _fsm.process_event(events::CabinAtTarget());
+ };
+ data->system->SetCabinMonitor(
+ floorTarget, jointTarget, this->posEps, this->velEps,
+ std::bind(&MoveCabinState::OnJointTargetReached, this));
+ }
+
+ /// \brief Method that gets called when the cabin joint reaches its target
+ private: void OnJointTargetReached()
+ {
+ this->triggerEvent();
+ }
+
+ /// \brief Positiion tolerance
+ private: double posEps;
+
+ /// \brief Velocity tolerance
+ private: double velEps;
+
+ /// \brief Triggers the exit event
+ private: std::function triggerEvent;
+};
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
diff --git a/src/systems/elevator/utils/DoorTimer.cc b/src/systems/elevator/utils/DoorTimer.cc
new file mode 100644
index 0000000000..960712f78a
--- /dev/null
+++ b/src/systems/elevator/utils/DoorTimer.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#include
+
+#include "DoorTimer.hh"
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+class DoorTimerPrivate
+{
+ /// \brief Constructor
+ /// \param[in] _waitDuration Duration
+ public: DoorTimerPrivate(
+ const std::chrono::steady_clock::duration &_waitDuration);
+
+ /// \brief Wait duration
+ public: std::chrono::steady_clock::duration waitDuration;
+
+ /// \brief Timeout time
+ public: std::chrono::steady_clock::duration timeoutTime;
+
+ /// \brief Function that gets called upon timeout
+ public: std::function timeoutCallback;
+
+ /// \brief Flag to indicate whether the doorway was blocked on the last update
+ /// time
+ public: bool wasDoorwayBlocked{false};
+
+ /// \brief Flag to indicate whether the timer is active
+ public: bool isActive{false};
+};
+
+//////////////////////////////////////////////////
+DoorTimerPrivate::DoorTimerPrivate(
+ const std::chrono::steady_clock::duration &_waitDuration)
+ : waitDuration(_waitDuration)
+{
+}
+
+//////////////////////////////////////////////////
+DoorTimer::DoorTimer(const std::chrono::steady_clock::duration &_waitDuration)
+ : dataPtr(std::make_unique(_waitDuration))
+{
+}
+
+//////////////////////////////////////////////////
+DoorTimer::~DoorTimer() = default;
+
+//////////////////////////////////////////////////
+void DoorTimer::Configure(const std::chrono::steady_clock::duration &_startTime,
+ const std::function &_timeoutCallback)
+{
+ this->dataPtr->isActive = true;
+ this->dataPtr->wasDoorwayBlocked = false;
+ this->dataPtr->timeoutTime = _startTime + this->dataPtr->waitDuration;
+ this->dataPtr->timeoutCallback = _timeoutCallback;
+}
+
+//////////////////////////////////////////////////
+void DoorTimer::Update(const UpdateInfo &_info, bool _isDoorwayBlocked)
+{
+ // Reset timeout time when doorway gets unblocked
+ if (!_isDoorwayBlocked && this->dataPtr->wasDoorwayBlocked)
+ this->dataPtr->timeoutTime = _info.simTime + this->dataPtr->waitDuration;
+ this->dataPtr->wasDoorwayBlocked = _isDoorwayBlocked;
+
+ if (!this->dataPtr->isActive || _isDoorwayBlocked ||
+ (_info.dt > std::chrono::steady_clock::duration::zero() &&
+ _info.simTime < this->dataPtr->timeoutTime))
+ return;
+ this->dataPtr->isActive = false;
+ this->dataPtr->timeoutCallback();
+}
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
diff --git a/src/systems/elevator/utils/DoorTimer.hh b/src/systems/elevator/utils/DoorTimer.hh
new file mode 100644
index 0000000000..bcfeb18a2f
--- /dev/null
+++ b/src/systems/elevator/utils/DoorTimer.hh
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#ifndef IGNITION_GAZEBO_SYSTEMS_DOOR_TIMER_HH_
+#define IGNITION_GAZEBO_SYSTEMS_DOOR_TIMER_HH_
+
+#include
+#include
+#include
+
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+// Data forward declaration
+class DoorTimerPrivate;
+
+/// \brief Timer that's used to keep a door open. It has a configurable default
+/// wait duration that when exceeded, it calls a function to let the state
+/// machine know to transition to the next state. The timer also checks whether
+/// the doorway is blocked, in which case it keeps the door open until whatever
+/// blocks the doorway moves out of the way.
+class DoorTimer
+{
+ /// \brief Constructor
+ /// \param[in] _waitDuration Duration
+ public: DoorTimer(const std::chrono::steady_clock::duration &_waitDuration);
+
+ /// \brief Destructor
+ public: ~DoorTimer();
+
+ /// \brief Starts the timer and sets the timeout time based on the given
+ /// start time
+ /// \param[in] _startTime Start time
+ /// \param[in] _timeoutCallback Function to call upon timeout
+ public: void Configure(const std::chrono::steady_clock::duration &_startTime,
+ const std::function &_timeoutCallback);
+
+ /// \brief Checks whether the timer has timed out
+ /// \param[in] _info Current simulation step info
+ /// \param[in] _isDoorwayBlocked Flag that indicates whether the doorway is
+ /// blocked
+ public: void Update(const UpdateInfo &_info, bool _isDoorwayBlocked);
+
+ /// \brief Private data pointer
+ private: std::unique_ptr dataPtr;
+};
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
+
+#endif // IGNITION_GAZEBO_SYSTEMS_DOOR_TIMER_HH_
diff --git a/src/systems/elevator/utils/JointMonitor.cc b/src/systems/elevator/utils/JointMonitor.cc
new file mode 100644
index 0000000000..f2aa68a9a1
--- /dev/null
+++ b/src/systems/elevator/utils/JointMonitor.cc
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#include
+
+#include "JointMonitor.hh"
+
+#include
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+class JointMonitorPrivate
+{
+ /// \brief Joint under monitoring
+ public: Entity joint;
+
+ /// \brief Last joint target
+ public: double target;
+
+ /// \brief Position tolerance
+ public: double posEps;
+
+ /// \brief Velocity tolerance
+ public: double velEps;
+
+ /// \brief Function that gets called when the joint reaches its target
+ public: std::function targetReachedCallback;
+
+ /// \brief Flag to indicate whether the monitor is active
+ public: bool isActive{false};
+};
+
+//////////////////////////////////////////////////
+JointMonitor::JointMonitor() : dataPtr(std::make_unique())
+{
+}
+
+//////////////////////////////////////////////////
+JointMonitor::~JointMonitor() = default;
+
+//////////////////////////////////////////////////
+void JointMonitor::Configure(
+ Entity _joint, double _target, double _posEps, double _velEps,
+ const std::function &_jointTargetReachedCallback)
+{
+ this->dataPtr->isActive = true;
+ this->dataPtr->joint = _joint;
+ this->dataPtr->target = _target;
+ this->dataPtr->posEps = _posEps;
+ this->dataPtr->velEps = _velEps;
+ this->dataPtr->targetReachedCallback = _jointTargetReachedCallback;
+}
+
+//////////////////////////////////////////////////
+void JointMonitor::Update(const EntityComponentManager &_ecm)
+{
+ if (!this->dataPtr->isActive)
+ return;
+ double pos =
+ _ecm.ComponentData(this->dataPtr->joint)
+ ->front();
+ double vel =
+ _ecm.ComponentData(this->dataPtr->joint)
+ ->front();
+ if (std::fabs(this->dataPtr->target - pos) > this->dataPtr->posEps ||
+ vel > this->dataPtr->velEps)
+ return;
+ this->dataPtr->isActive = false;
+ this->dataPtr->targetReachedCallback();
+}
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
diff --git a/src/systems/elevator/utils/JointMonitor.hh b/src/systems/elevator/utils/JointMonitor.hh
new file mode 100644
index 0000000000..a42046b4ab
--- /dev/null
+++ b/src/systems/elevator/utils/JointMonitor.hh
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * \author Nick Lamprianidis
+ * \date January 2021
+ */
+
+#ifndef IGNITION_GAZEBO_SYSTEMS_JOINT_MONITOR_HH_
+#define IGNITION_GAZEBO_SYSTEMS_JOINT_MONITOR_HH_
+
+#include
+#include
+
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+{
+namespace systems
+{
+// Data forward declaration
+class JointMonitorPrivate;
+
+/// \brief Monitor that checks the state of a joint. When the joint reaches the
+/// configured target, it calls a function to let the state machine know to
+/// transition to the next state.
+class JointMonitor
+{
+ /// \brief Constructor
+ public: JointMonitor();
+
+ /// \brief Destructor
+ public: ~JointMonitor();
+
+ /// \brief Starts monitoring of the position and velocity of the given joint
+ /// \param[in] _joint Joint to monitor
+ /// \param[in] _jointTarget Last joint target (command)
+ /// \param[in] _posEps Position tolerance
+ /// \param[in] _velEps Velocity tolerance
+ /// \param[in] _jointTargetReachedCallback Function to call when the joint
+ /// reaches its target
+ public: void Configure(
+ Entity _joint, double _target, double _posEps, double _velEps,
+ const std::function &_jointTargetReachedCallback);
+
+ /// \brief Checks whether the position and velocity of the joint are within
+ /// the configured tolerances
+ /// \param[in] _ecm Entity component manager
+ public: void Update(const EntityComponentManager &_ecm);
+
+ /// \brief Private data pointer
+ private: std::unique_ptr dataPtr;
+};
+
+} // namespace systems
+} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE
+} // namespace gazebo
+} // namespace ignition
+
+#endif // IGNITION_GAZEBO_SYSTEMS_JOINT_MONITOR_HH_
diff --git a/src/systems/elevator/vender/afsm/LICENSE b/src/systems/elevator/vender/afsm/LICENSE
new file mode 100644
index 0000000000..6a3a57fb25
--- /dev/null
+++ b/src/systems/elevator/vender/afsm/LICENSE
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/systems/elevator/vender/afsm/include/afsm/definition.hpp b/src/systems/elevator/vender/afsm/include/afsm/definition.hpp
new file mode 100644
index 0000000000..7a0f2cb730
--- /dev/null
+++ b/src/systems/elevator/vender/afsm/include/afsm/definition.hpp
@@ -0,0 +1,721 @@
+/*
+ * definition.hpp
+ *
+ * Created on: 27 мая 2016 г.
+ * Author: sergey.fedorov
+ */
+
+#ifndef AFSM_DEFINITION_HPP_
+#define AFSM_DEFINITION_HPP_
+
+#include
+#include
+#include
+#include
+
+namespace afsm {
+namespace def {
+
+namespace detail {
+
+template
+struct source_state;
+
+template < typename SourceState, typename Event, typename TargetState,
+ typename Action, typename Guard>
+struct source_state< transition > {
+ using type = SourceState;
+};
+
+template < typename Event, typename Action, typename Guard >
+struct source_state< internal_transition< Event, Action, Guard > > {
+ using type = void;
+};
+
+template
+struct target_state;
+
+template < typename SourceState, typename Event, typename TargetState,
+ typename Action, typename Guard>
+struct target_state< transition > {
+ using type = TargetState;
+};
+
+template < typename Event, typename Action, typename Guard >
+struct target_state< internal_transition< Event, Action, Guard > > {
+ using type = void;
+};
+
+template
+struct event_type;
+
+template < typename SourceState, typename Event, typename TargetState,
+ typename Action, typename Guard>
+struct event_type< transition > {
+ using type = Event;
+};
+template < typename Event, typename Action, typename Guard >
+struct event_type< internal_transition< Event, Action, Guard > > {
+ using type = Event;
+};
+
+template < typename SourceState, typename Event, typename Guard >
+struct transition_key {
+ using source_state_type = SourceState;
+ using event_type = Event;
+ using guard_type = Guard;
+};
+
+} /* namespace detail */
+
+template < typename SourceState, typename Event, typename TargetState,
+ typename Action, typename Guard >
+struct transition {
+ using source_state_type = SourceState;
+ using target_state_type = TargetState;
+ using event_type = Event;
+ using action_type = Action;
+ using guard_type = Guard;
+
+ using key_type = detail::transition_key;
+
+ struct value_type {
+ using action_type = transition::action_type;
+ using target_state_type = transition::target_state_type;
+ };
+};
+
+template < typename Event, typename Action, typename Guard >
+struct internal_transition {
+ using event_type = Event;
+ using action_type = Action;
+ using guard_type = Guard;
+
+ using key_type = detail::transition_key;
+
+ struct value_type {
+ using action_type = internal_transition::action_type;
+ };
+
+ static_assert(!::std::is_same::value,
+ "Internal transition must have a trigger");
+};
+
+template < typename Event >
+struct handles_event {
+ template < typename Transition >
+ struct type : ::std::conditional<
+ ::std::is_same< typename Transition::event_type, Event >::value,
+ ::std::true_type,
+ ::std::false_type
+ >::type {};
+};
+
+template < typename State >
+struct originates_from {
+ template < typename Transition >
+ struct type : ::std::conditional<
+ ::std::is_same< typename Transition::source_state_type, State >::value,
+ ::std::true_type,
+ ::std::false_type
+ >::type {};
+};
+
+template < typename ... T >
+struct transition_table {
+ static_assert(
+ ::std::conditional<
+ (sizeof ... (T) > 0),
+ ::psst::meta::all_match< traits::is_transition, T ... >,
+ ::std::true_type
+ >::type::value,
+ "Transition table can contain only transition or internal_transition template instantiations" );
+ using transitions = ::psst::meta::type_tuple;
+ using transition_map = ::psst::meta::type_map<
+ ::psst::meta::type_tuple,
+ ::psst::meta::type_tuple>;
+ using inner_states = typename ::psst::meta::unique<
+ typename detail::source_state::type ...,
+ typename detail::target_state::type ...
+ >::type;
+ using handled_events = typename ::psst::meta::unique<
+ typename T::event_type ... >::type;
+
+ static constexpr ::std::size_t size = transition_map::size;
+ static constexpr ::std::size_t transition_count = transition_map::size;
+ static constexpr ::std::size_t inner_state_count = inner_states::size;
+ static constexpr ::std::size_t event_count = handled_events::size;
+
+ static_assert(
+ ::std::conditional<
+ (inner_state_count > 0),
+ ::psst::meta::all_match< traits::is_state, inner_states >,
+ ::std::true_type
+ >::type::value,
+ "State types must derive from afsm::def::state");
+ static_assert(transitions::size == transition_count, "Duplicate transition");
+ // TODO Check for different guards for transitions from one state on one event
+};
+
+template < typename StateType, typename ... Tags >
+struct state_def : tags::state, Tags... {
+ using state_type = StateType;
+ using base_state_type = state_def;
+ using internal_transitions = void;
+ using transitions = void;
+ using deferred_events = void;
+ using activity = void;
+
+ template < typename Event, typename Action = none, typename Guard = none >
+ using in = internal_transition< Event, Action, Guard>;
+ template < typename ... T >
+ using transition_table = def::transition_table;
+
+ using none = afsm::none;
+ template < typename Predicate >
+ using not_ = ::psst::meta::not_;
+ template < typename ... Predicates >
+ using and_ = ::psst::meta::and_;
+ template < typename ... Predicates >
+ using or_ = ::psst::meta::or_;
+ template < typename ... T >
+ using type_tuple = ::psst::meta::type_tuple;
+};
+
+template < typename StateType, typename ... Tags >
+struct terminal_state : tags::state, Tags... {
+ using state_type = StateType;
+ using base_state_type = terminal_state;
+ using internal_transitions = void;
+ using transitions = void;
+ using deferred_events = void;
+ using activity = void;
+};
+
+template < typename StateType, typename Machine, typename ... Tags >
+struct pushdown : tags::state, tags::pushdown, Tags... {
+ using state_type = StateType;
+ using base_state_type = pushdown;
+ using internal_transitions = void;
+ using transitions = void;
+ using deferred_events = void;
+ using activity = void;
+};
+
+template < typename StateType, typename Machine, typename ... Tags >
+struct popup : tags::state, tags::popup, Tags... {
+ using state_type = StateType;
+ using base_state_type = popup;
+ using internal_transitions = void;
+ using transitions = void;
+ using deferred_events = void;
+ using activity = void;
+};
+
+template < typename StateMachine, typename ... Tags >
+struct state_machine_def : state_def< StateMachine, Tags... >, tags::state_machine {
+ using state_machine_type = StateMachine;
+ using base_state_type = state_machine_def< state_machine_type, Tags... >;
+ using initial_state = void;
+ using internal_transitions = void;
+ using transitions = void;
+ using deferred_events = void;
+ using activity = void;
+ using orthogonal_regions = void;
+
+ template
+ using tr = transition;
+
+ using inner_states_definition = traits::inner_states_definitions>;
+
+ template < typename T, typename ... TTags>
+ using state = typename inner_states_definition::template state;
+ template < typename T, typename ... TTags >
+ using terminal_state = typename inner_states_definition::template terminal_state;
+ template < typename T, typename ... TTags >
+ using state_machine = typename inner_states_definition::template state_machine;
+ template < typename T, typename M, typename ... TTags>
+ using push = typename inner_states_definition::template push;
+ template < typename T, typename M, typename ... TTags>
+ using pop = typename inner_states_definition::template pop;
+
+ using none = afsm::none;
+ template < typename Predicate >
+ using not_ = ::psst::meta::not_;
+ template < typename ... Predicates >
+ using and_ = ::psst::meta::and_;
+ template < typename ... Predicates >
+ using or_ = ::psst::meta::or_;
+ template < typename ... T >
+ using type_tuple = ::psst::meta::type_tuple;
+};
+
+namespace detail {
+
+template < typename T >
+struct has_inner_states : ::std::false_type {};
+template < typename ... T >
+struct has_inner_states< transition_table >
+ : ::std::integral_constant::inner_state_count > 0)> {};
+
+template < typename T >
+struct inner_states {
+ using type = ::psst::meta::type_tuple<>;
+};
+
+template < typename ... T >
+struct inner_states< transition_table > {
+ using type = typename transition_table::inner_states;
+};
+
+template < typename ... T >
+struct inner_states< ::psst::meta::type_tuple > {
+ using type = ::psst::meta::type_tuple;
+};
+
+template < typename T >
+struct has_transitions : ::std::false_type {};
+template < typename ... T >
+struct has_transitions< transition_table >
+ : ::std::conditional<
+ (transition_table::transition_count > 0),
+ ::std::true_type,
+ ::std::false_type
+ >::type{};
+
+template < typename T >
+struct handled_events
+ : std::conditional<
+ traits::is_state_machine::value,
+ handled_events>,
+ handled_events>
+ >::type {};
+
+template < typename ... T >
+struct handled_events< transition_table > {
+ using type = typename transition_table::handled_events;
+};
+
+template <>
+struct handled_events {
+ using type = ::psst::meta::type_tuple<>;
+};
+
+template < typename T >
+struct handled_events< state > {
+ using type = typename handled_events<
+ typename T::internal_transitions >::type;
+};
+
+template < typename T >
+struct handled_events< state_machine > {
+ using type = typename ::psst::meta::unique<
+ typename handled_events< typename T::internal_transitions >::type,
+ typename handled_events< typename T::transitions >::type
+ >::type;
+};
+
+/**
+ * Events handled by a set of states
+ */
+template < typename ... T >
+struct handled_events< ::psst::meta::type_tuple > {
+ using type = typename ::psst::meta::unique<
+ typename handled_events::type ...
+ >::type;
+};
+
+template < typename T >
+struct recursive_handled_events
+ : ::std::conditional<
+ traits::is_state_machine::value,
+ recursive_handled_events< state_machine >,
+ handled_events< state