Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions scxml.workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<Workspace>
<Distribution path="/opt/ros/noetic"/>
<DefaultBuildSystem value="1"/>
<WatchDirectories>
<Directory>src</Directory>
</WatchDirectories>
</Workspace>
17 changes: 16 additions & 1 deletion scxml_core/include/scxml_core/scxml_sm_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
#include <QString>
#include <QSet>
#include <QFuture>
#include <set>
#include <scxml_core/scxml_sm_interface.h>

namespace scxml_core
{
/** @brief Container for states and their associated transitions */
using StateTransitionMap = std::map<QString, QSet<QString>>;
using StateTransitionMap = std::map<QString, std::set<std::pair<QString, QString>>>;

/** @brief Creates a map of known states and transition events associated with those states */
StateTransitionMap getStateTransitionMap(const std::string& scxml_file);
Expand All @@ -23,11 +25,24 @@ class ScxmlSMInterface
public:
ScxmlSMInterface(const std::string& scxml_file);

/**
* @brief checks if an event exists
*/
bool eventExists(const QString& event, std::set<std::pair<QString, QString>> events);

/**
* @brief gets the state to which a desired transition occurs
* @param search_text - insert transition text you'd like to match
* @throws if you don't have that transition, it will return itself as it's neighbor
*/

const QString getNeighbor(const QString& state, const QString& search_text);
/**
* @brief Adds a callback to the input state that will be invoked on entry to the state
* @param async - flag for executing the input callback asynchronously
* @throws exception if the state does not exist in the state machine
*/

void addOnEntryCallback(const QString& state, const std::function<void()>& callback, bool async = false);

/**
Expand Down
60 changes: 31 additions & 29 deletions scxml_core/src/demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,45 +38,47 @@ int main(int argc, char** argv)
// Get the active state and available events
QStringList active_states = interface.getSM()->activeStateNames();
const QString& current_state = active_states.at(0);
QSet<QString> available_events = map.at(current_state);
std::set<std::pair<QString, QString>> available_events = map.at(current_state);

std::stringstream ss;
ss << "Available events: [ ";
for (const QString& event : available_events)
for (auto& pair : available_events)
{
const QString& event = pair.first;
ss << event.toStdString() << " ";
}
ss << "]";

// Get user input as to which event to execute
bool done = false;
while (!done)
{
std::cout << ss.str() << std::endl;
// // Get user input as to which event to execute
// bool done = false;
// while (!done)
// {
// std::cout << ss.str() << std::endl;

// Get the character input
auto input = std::cin.get();
// Throw away the enter input
std::cin.get();
// // Get the character input
// auto input = std::cin.get();
// // Throw away the enter input
// std::cin.get();

if (std::isdigit(input))
{
int idx = static_cast<int>(input) - 48;
if (idx < available_events.size())
{
interface.submitEvent(available_events.toList().at(idx));
done = true;
}
else
{
std::cout << "Index " << idx << " was not in range [0, " << available_events.size() - 1 << "]" << std::endl;
}
}
else
{
std::cout << "Input must be numeric" << std::endl;
}
}
// if (std::isdigit(input))
// {
// int idx = static_cast<int>(input) - 48;
// if (idx < available_events.size())
// {
// interface.submitEvent(available_events.toList().at(idx));
// done = true;
// }
// else
// {
// std::cout << "Index " << idx << " was not in range [0, " << available_events.size() - 1 << "]" <<
// std::endl;
// }
// }
// else
// {
// std::cout << "Input must be numeric" << std::endl;
// }
// }
}
}
catch (const std::exception& ex)
Expand Down
56 changes: 50 additions & 6 deletions scxml_core/src/scxml_sm_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ static const char* HISTORY_STATE_ELEMENT = "history";
static const char* HISTORY_STATE_ID_ATTRIBUTE = "id";
static const char* TRANSITION_ELEMENT = "transition";
static const char* EVENT_ATTRIBUTE = "event";
static const char* TARGET_ATTRIBUTE = "target";

/**
* @brief Recursively adds states and transitions to the map
Expand All @@ -18,7 +19,7 @@ static const char* EVENT_ATTRIBUTE = "event";
*/
static void getStateTransitionsRecursive(tinyxml2::XMLElement* state,
scxml_core::StateTransitionMap& map,
QSet<QString> inherited_events)
std::set<std::pair<QString, QString>> inherited_events)
{
using namespace tinyxml2;

Expand Down Expand Up @@ -46,8 +47,14 @@ static void getStateTransitionsRecursive(tinyxml2::XMLElement* state,
throw std::runtime_error("'" + std::string(TRANSITION_ELEMENT) + "' element does not have '" +
std::string(EVENT_ATTRIBUTE) + "' attribute");

const char* name = transition->Attribute(TARGET_ATTRIBUTE);

std::pair<QString, QString> list = std::make_pair(QString(event), QString(name));

inherited_events.insert(list);

// Add the event name to the map
map.at(state_id).insert(event);
map[state_id] = inherited_events;

// Get the next transition element
transition = transition->NextSiblingElement(TRANSITION_ELEMENT);
Expand All @@ -71,7 +78,7 @@ static void getStateTransitionsRecursive(tinyxml2::XMLElement* state,
std::string(HISTORY_STATE_ID_ATTRIBUTE) + "' attribute");

// Add this state to the map
map[QString(id)] = QSet<QString>{};
map[QString(id)] = std::set<std::pair<QString, QString>>{};

// History states do not have transitions or nested states, so no need to recurse into it
history = history->NextSiblingElement(HISTORY_STATE_ELEMENT);
Expand Down Expand Up @@ -102,7 +109,7 @@ StateTransitionMap getStateTransitionMap(const std::string& scxml_file)
StateTransitionMap map;
while (state)
{
getStateTransitionsRecursive(state, map, QSet<QString>{});
getStateTransitionsRecursive(state, map, std::set<std::pair<QString, QString>>{});
state = state->NextSiblingElement(STATE_ELEMENT);
}

Expand Down Expand Up @@ -136,6 +143,43 @@ ScxmlSMInterface::ScxmlSMInterface(const std::string& scxml_file)
}
}

// use this to determine the next state in the state machine, given the name of the transition you'd like to query
const QString ScxmlSMInterface::getNeighbor(const QString& state, const QString& search_text)
{
QString next_state;

for (auto& pair : state_transition_map_.at(state))
{
if (pair.first == search_text)
{
return pair.second;
}
}
return next_state;
}

bool ScxmlSMInterface::eventExists(const QString& event, std::set<std::pair<QString, QString>> events)
{
int i = 0;
const int j = events.size();
for (auto& pair : events)
{
if (pair.first == event)
{
return true;
}
else
{
i = i + 1;
}

if (i == j)
{
return false;
}
}
}

void ScxmlSMInterface::addOnEntryCallback(const QString& state, const std::function<void()>& callback, bool async)
{
if (state_transition_map_.find(state) == state_transition_map_.end())
Expand Down Expand Up @@ -165,7 +209,7 @@ bool ScxmlSMInterface::submitEvent(const QString& event, bool force)

// Ensure at least one of the active states has the specified transition
auto it = std::find_if(active_states.begin(), active_states.end(), [this, event](const QString& state) -> bool {
return this->state_transition_map_.at(state).contains(event);
return eventExists(event, state_transition_map_.at(state));
});

if (it == active_states.end())
Expand All @@ -185,7 +229,7 @@ bool ScxmlSMInterface::submitEvent(const QString& event, bool force)
{
for (const QString& state : active_states)
{
if (state_transition_map_.at(state).contains(event))
if (eventExists(event, state_transition_map_.at(state)))
{
// Check if the asynchronous callback is finished before submitting the event
if (!future_map_.at(state).isFinished())
Expand Down