SObjectizer  5.5
so_5/intercom_statechart/main.cpp
/*
* An example of implementation of hierarchical state machine by using
* agent's states.
*/
#include <iostream>
#include <string>
#include <cctype>
// Main SObjectizer header file.
#include <so_5/all.hpp>
// Messages to be used for interaction with intercom agents.
struct key_cancel : public so_5::signal_t {};
struct key_bell : public so_5::signal_t {};
struct key_grid : public so_5::signal_t {};
struct key_digit
{
char m_value;
};
// Private messages for intercom implementation.
namespace intercom_messages
{
struct activated : public so_5::signal_t {};
struct deactivate : public so_5::signal_t {};
struct display_text
{
std::string m_what;
};
// Helper function for sending display_text message.
inline void show_on_display(
const so_5::mbox_t & intercom_mbox,
std::string what )
{
so_5::send< display_text >( intercom_mbox, std::move(what) );
}
// Helper function for clearing display.
inline void clear_display(
const so_5::mbox_t & intercom_mbox )
{
so_5::send< display_text >( intercom_mbox, "" );
}
} // namespace intercom_messages
// An agent for controlling inactivity time.
//
// Listen the same user-activity messages as controller agent and reschedules
// delayed message after each of them. This delayed message tells the
// controller to change state from active to inactive.
//
// Please note: this agent switches from inactive to active state by the
// signal from controller agent.
class inactivity_watcher final : public so_5::agent_t
{
state_t inactive{ this, "inactive" };
state_t active{ this, "active" };
const std::chrono::seconds inactivity_time{ 10 };
public :
inactivity_watcher(
context_t ctx,
so_5::mbox_t intercom_mbox )
: so_5::agent_t{ ctx }
, m_intercom_mbox{ std::move(intercom_mbox) }
{
inactive
.on_enter( [this] { m_timer.release(); } )
.just_switch_to< intercom_messages::activated >(
m_intercom_mbox, active );
.on_enter( [this] { reschedule_timer(); } )
.event( m_intercom_mbox,
[this](mhood_t< key_cancel >) { reschedule_timer(); } )
.event( m_intercom_mbox,
[this](mhood_t< key_bell >) { reschedule_timer(); } )
.event( m_intercom_mbox,
[this](mhood_t< key_grid >) { reschedule_timer(); } )
.event( m_intercom_mbox,
[this]( const key_digit & ) { reschedule_timer(); } )
.just_switch_to< intercom_messages::deactivate >(
m_intercom_mbox, inactive );
this >>= inactive;
}
private :
const so_5::mbox_t m_intercom_mbox;
void reschedule_timer()
{
m_timer = so_5::send_periodic< intercom_messages::deactivate >(
m_intercom_mbox,
inactivity_time,
std::chrono::seconds::zero() );
}
};
// An agent for imitation of keyboard's light equipment.
//
// Switching from off to on states and back is done by listening
// activated and deactivate signals.
class keyboard_lights final : public so_5::agent_t
{
state_t off{ this, "off" };
state_t on{ this, "on" };
public :
keyboard_lights(
context_t ctx,
const so_5::mbox_t & intercom_mbox )
: so_5::agent_t{ ctx }
{
.on_enter( []{ std::cout << "keyboard_lights OFF" << std::endl; } )
.just_switch_to< intercom_messages::activated >(
intercom_mbox, on );
.on_enter( []{ std::cout << "keyboard_lights ON" << std::endl; } )
.just_switch_to< intercom_messages::deactivate >(
intercom_mbox, off );
this >>= off;
}
};
// An agent for imitation of intercom's display.
//
// Switching from off to on states and back is done by listening
// activated and deactivate signals.
class display final : public so_5::agent_t
{
state_t off{ this, "off" };
state_t on{ this, "on" };
public :
display(
context_t ctx,
const so_5::mbox_t & intercom_mbox )
: so_5::agent_t{ ctx }
{
.on_enter( []{ std::cout << "display OFF" << std::endl; } )
.just_switch_to< intercom_messages::activated >(
intercom_mbox, on );
.on_enter( []{ std::cout << "display ON" << std::endl; } )
.event( intercom_mbox,
[]( const intercom_messages::display_text & msg ) {
std::cout << "display: '" << msg.m_what << "'" << std::endl;
} )
.just_switch_to< intercom_messages::deactivate >(
intercom_mbox, off );
this >>= off;
}
};
// An agent for imitation of call to an apartment.
//
// Just changes a message on display for every 1.5s.
class ringer final : public so_5::agent_t
{
state_t
off{ this, "off" },
on{ this, "on" },
ringing{ initial_substate_of{ on }, "ringing" },
sleeping{ substate_of{ on }, "sleeping" };
struct timer : public so_5::signal_t {};
public :
struct dial_to { std::string m_number; };
struct stop_dialing : public so_5::signal_t {};
ringer(
context_t ctx,
so_5::mbox_t intercom_mbox )
: so_5::agent_t{ ctx }
, m_intercom_mbox{ std::move(intercom_mbox) }
{
this >>= off;
.on_enter( [this]{ m_timer.release(); } )
.event( m_intercom_mbox, [this]( const dial_to & msg ) {
m_number = msg.m_number;
this >>= on;
} );
.on_enter( [this] {
m_timer = so_5::send_periodic< timer >(
*this,
std::chrono::milliseconds::zero(),
std::chrono::milliseconds{ 1500 } );
} )
.just_switch_to< stop_dialing >( m_intercom_mbox, off );
ringing
.on_enter( [this]{
intercom_messages::show_on_display(
m_intercom_mbox, "RING" );
} )
.just_switch_to< timer >( sleeping );
sleeping
.on_enter( [this]{
intercom_messages::show_on_display(
m_intercom_mbox, m_number );
} )
.just_switch_to< timer >( ringing );
}
private :
const so_5::mbox_t m_intercom_mbox;
std::string m_number;
};
// Main agent of intercom example.
// Imitates intercom's controller.
//
// Receives a messages from user and perform various actions.
//
class controller final : public so_5::agent_t
{
// States of agent's statechart.
state_t
inactive{ this, "inactive" },
active{ this, "active" },
wait_activity{
initial_substate_of{ active }, "wait_activity" },
number_selection{ substate_of{ active }, "number_selection" },
dialling{ substate_of{ active }, "dialling" },
dial_apartment{
initial_substate_of{ dialling }, "dial_apartment" },
no_answer{
substate_of{ dialling }, "no_answer" },
special_code_selection{
substate_of{ active }, "special_code_selection" },
special_code_selection_0{
initial_substate_of{ special_code_selection },
"special_code_selection_0" },
user_code_selection{
substate_of{ special_code_selection },
"user_code_selection" },
user_code_apartment_number{
initial_substate_of{ user_code_selection },
"apartment_number" },
user_code_secret{
substate_of{ user_code_selection },
"secret_code" },
service_code_selection{
substate_of{ special_code_selection },
"service_code" },
door_unlocked{
substate_of{ special_code_selection },
"door_unlocked" }
;
struct apartment_info
{
std::string m_number;
std::string m_secret_key;
apartment_info( std::string n, std::string k )
: m_number( std::move(n) ), m_secret_key( std::move(k) )
{}
};
public :
controller(
context_t ctx,
so_5::mbox_t intercom_mbox )
: so_5::agent_t{ ctx }
, m_intercom_mbox{ std::move(intercom_mbox) }
, m_apartments( make_apartment_info() )
, m_actual_service_code( "12345" )
{
// Setting up statechart.
inactive
.transfer_to_state< key_digit >( m_intercom_mbox, active )
.transfer_to_state< key_grid >( m_intercom_mbox, active )
.transfer_to_state< key_bell >( m_intercom_mbox, active )
.transfer_to_state< key_cancel >( m_intercom_mbox, active );
.on_enter( &controller::active_on_enter )
.event( m_intercom_mbox, &controller::active_on_grid )
.event( m_intercom_mbox, &controller::active_on_cancel )
.just_switch_to< intercom_messages::deactivate >(
m_intercom_mbox, inactive );
wait_activity
.transfer_to_state< key_digit >( m_intercom_mbox, number_selection );
number_selection
.on_enter( &controller::apartment_number_on_enter )
.event( m_intercom_mbox, &controller::apartment_number_on_digit )
.event( m_intercom_mbox, &controller::apartment_number_on_bell )
.suppress< key_grid >( m_intercom_mbox );
dialling
.suppress< key_grid >( m_intercom_mbox )
.suppress< key_bell >( m_intercom_mbox )
.suppress< key_digit >( m_intercom_mbox );
dial_apartment
.time_limit( std::chrono::seconds{ 8 }, no_answer )
.on_enter( &controller::dial_apartment_on_enter )
.on_exit( &controller::dial_apartment_on_exit );
no_answer
.time_limit( std::chrono::milliseconds{ 1500 }, wait_activity )
.on_enter( &controller::no_answer_on_enter )
.on_exit( &controller::no_answer_on_exit )
.suppress< key_cancel >( m_intercom_mbox );
special_code_selection_0
.transfer_to_state< key_digit >( m_intercom_mbox, user_code_selection )
.just_switch_to< key_grid >( m_intercom_mbox, service_code_selection );
user_code_apartment_number
.on_enter( &controller::user_code_apartment_number_on_enter )
.event( m_intercom_mbox, &controller::apartment_number_on_digit )
.just_switch_to< key_grid >( m_intercom_mbox, user_code_secret );
user_code_secret
.on_enter( &controller::user_code_secret_on_enter )
.event( m_intercom_mbox, &controller::user_code_secret_on_digit )
.event( m_intercom_mbox, &controller::user_code_secret_on_bell );
service_code_selection
.on_enter( &controller::service_code_on_enter )
.event( m_intercom_mbox, &controller::service_code_on_digit )
.event( m_intercom_mbox, &controller::service_code_on_grid );
door_unlocked
.time_limit( std::chrono::seconds{ 5 }, wait_activity )
.on_enter( &controller::door_unlocked_on_enter )
.on_exit( &controller::door_unlocked_on_exit )
.suppress< key_grid >( m_intercom_mbox )
.suppress< key_bell >( m_intercom_mbox )
.suppress< key_digit >( m_intercom_mbox )
.suppress< key_cancel >( m_intercom_mbox );
}
virtual void so_evt_start() override
{
// Agent starts in the default state. Switch to the appropriate one.
this >>= inactive;
}
private :
// Limitations for various pieces of user input.
static const std::size_t max_apartment_number_size = 3u;
static const std::size_t max_secret_code_size = 4u;
static const std::size_t service_code_size = 5u;
// Common mbox for all intercom's parts.
const so_5::mbox_t m_intercom_mbox;
// Information about apartments and their secret codes.
const std::vector< apartment_info > m_apartments;
// Accumutator for apartment number.
std::string m_apartment_number;
// Accumutator for user's secret code.
std::string m_user_secret_code;
// Accumutator for service code.
std::string m_service_code;
// Service code value for this intercom instance.
const std::string m_actual_service_code;
// Helper method for generation of apartments' information.
static std::vector< apartment_info > make_apartment_info()
{
std::vector< apartment_info > result;
result.reserve( 10 );
result.emplace_back( "101", "1011" );
result.emplace_back( "102", "1022" );
result.emplace_back( "103", "1033" );
result.emplace_back( "104", "1044" );
result.emplace_back( "105", "1055" );
result.emplace_back( "106", "1066" );
result.emplace_back( "107", "1077" );
result.emplace_back( "108", "1088" );
result.emplace_back( "109", "1099" );
result.emplace_back( "110", "1100" );
return result;
}
void active_on_enter()
{
// Special signal must be send on enter to active state.
// This signal will activate other parts of intercom.
so_5::send< intercom_messages::activated >( m_intercom_mbox );
}
void active_on_cancel( mhood_t< key_cancel > )
{
this >>= wait_activity;
}
void active_on_grid( mhood_t< key_grid > )
{
this >>= special_code_selection;
}
void apartment_number_on_enter()
{
// Old value of accumulator must be dropped to allow to accumulate new
// one.
m_apartment_number.clear();
}
void apartment_number_on_digit( const key_digit & msg )
{
if( m_apartment_number.size() < max_apartment_number_size )
m_apartment_number += msg.m_value;
intercom_messages::show_on_display(
m_intercom_mbox, m_apartment_number );
}
void apartment_number_on_bell( mhood_t< key_bell > )
{
auto apartment = std::find_if( begin(m_apartments), end(m_apartments),
[this]( const apartment_info & info ) {
return info.m_number == m_apartment_number;
} );
if( apartment != end(m_apartments) )
this >>= dialling;
else
{
intercom_messages::show_on_display( m_intercom_mbox, "Err" );
this >>= wait_activity;
}
}
void dial_apartment_on_enter()
{
so_5::send< ringer::dial_to >( m_intercom_mbox, m_apartment_number );
}
void dial_apartment_on_exit()
{
so_5::send< ringer::stop_dialing >( m_intercom_mbox );
}
void no_answer_on_enter()
{
intercom_messages::show_on_display( m_intercom_mbox, "No Answer" );
}
void no_answer_on_exit()
{
intercom_messages::clear_display( m_intercom_mbox );
}
void user_code_apartment_number_on_enter()
{
// Old value of accumulator must be dropped to allow to accumulate new
// one.
m_apartment_number.clear();
}
void user_code_secret_on_enter()
{
// Old value of accumulator must be dropped to allow to accumulate new
// one.
m_user_secret_code.clear();
intercom_messages::clear_display( m_intercom_mbox );
}
void user_code_secret_on_digit( const key_digit & msg )
{
if( m_user_secret_code.size() < max_secret_code_size )
m_user_secret_code += msg.m_value;
intercom_messages::show_on_display(
m_intercom_mbox,
std::string( m_user_secret_code.size(), '*' ) );
}
void user_code_secret_on_bell( mhood_t< key_bell > )
{
auto apartment = std::find_if( begin(m_apartments), end(m_apartments),
[this]( const apartment_info & info ) {
return info.m_number == m_apartment_number;
} );
if( apartment != end(m_apartments) &&
m_user_secret_code == apartment->m_secret_key )
this >>= door_unlocked;
else
{
intercom_messages::show_on_display( m_intercom_mbox, "Err" );
this >>= wait_activity;
}
}
void door_unlocked_on_enter()
{
intercom_messages::show_on_display( m_intercom_mbox, "unlocked" );
}
void door_unlocked_on_exit()
{
intercom_messages::clear_display( m_intercom_mbox );
}
void service_code_on_enter()
{
// Old value of accumulator must be dropped to allow to accumulate new
// one.
m_service_code.clear();
}
void service_code_on_digit( const key_digit & msg )
{
if( m_service_code.size() < service_code_size )
m_service_code += msg.m_value;
intercom_messages::show_on_display(
m_intercom_mbox,
std::string( m_service_code.size(), '#' ) );
}
void service_code_on_grid( mhood_t< key_grid > )
{
if( !m_service_code.empty() )
{
if( m_service_code == m_actual_service_code )
this >>= door_unlocked;
else
{
intercom_messages::show_on_display( m_intercom_mbox, "Err" );
this >>= wait_activity;
}
}
}
};
// Helper for creation of coop with intercom's agents.
so_5::mbox_t create_intercom( so_5::environment_t & env )
{
return env.introduce_coop( [&]( so_5::coop_t & coop ) {
auto intercom_mbox = env.create_mbox();
coop.make_agent< controller >( intercom_mbox );
coop.make_agent< inactivity_watcher >( intercom_mbox );
coop.make_agent< keyboard_lights >( intercom_mbox );
coop.make_agent< display >( intercom_mbox );
coop.make_agent< ringer >( intercom_mbox );
return intercom_mbox;
} );
}
void demo()
{
// A SObjectizer instance.
[]( so_5::environment_t & ) {},
[]( so_5::environment_params_t & params ) {
params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() );
} }*/;
auto intercom = create_intercom( sobj.environment() );
while( true )
{
std::cout << "enter digit or 'c' or 'b' or '#' (or 'exit' to stop): "
<< std::flush;
std::string choice;
std::cin >> choice;
if( "c" == choice ) so_5::send< key_cancel >( intercom );
else if( "b" == choice ) so_5::send< key_bell >( intercom );
else if( "#" == choice ) so_5::send< key_grid >( intercom );
else if( "exit" == choice ) break;
else if( 1 == choice.size() && std::isdigit( choice[ 0 ] ) )
so_5::send< key_digit >( intercom, choice[ 0 ] );
}
// SObjectizer will be stopped automatically.
}
int main()
{
try
{
demo();
}
catch( const std::exception & ex )
{
std::cerr << "Error: " << ex.what() << std::endl;
}
return 0;
}