Subversion Repositories HomeAutomation

Rev

Rev 2037 | Rev 2164 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | SVN | RSS feed

  1. /*
  2.  *
  3.  *  Copyright (C) 2010  Mattias Runge
  4.  *
  5.  *  This program is free software; you can redistribute it and/or modify
  6.  *  it under the terms of the GNU General Public License as published by
  7.  *  the Free Software Foundation; either version 2 of the License, or
  8.  *  (at your option) any later version.
  9.  *
  10.  *  This program is distributed in the hope that it will be useful,
  11.  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  *  GNU General Public License for more details.
  14.  *
  15.  *  You should have received a copy of the GNU General Public License along
  16.  *  with this program; if not, write to the Free Software Foundation, Inc.,
  17.  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18.  *
  19.  */
  20.  
  21. #include "Node.h"
  22.  
  23. #include <boost/date_time/posix_time/posix_time.hpp>
  24. #include <boost/lexical_cast.hpp>
  25.  
  26. #include "Manager.h"
  27. #include "can/Message.h"
  28. #include "broker/Manager.h"
  29.  
  30. namespace atom {
  31. namespace control {
  32.  
  33. Node::TransitionList Node::transitions_;
  34. std::map<Node::State, std::string> Node::state_names_;
  35. std::map<Node::Event, std::string> Node::event_names_;
  36.    
  37. Node::Node(Node::Id id) : LOG("control::Node")
  38. {
  39.     this->state_ = STATE_NORM_OFFLINE;
  40.     //this->id_ = boost::algorithm::to_lower_copy((std::string)id);
  41.     //this->id_ = boost::algorithm::to_upper_copy((std::string)id);
  42.     this->id_ = (std::string)id;
  43.     this->information_.has_application_ = false;
  44.     this->information_.bios_version_ = 0;
  45.     this->information_.device_type_ = "N/A";
  46.     this->information_.last_active_ = 0;
  47.     this->information_.valid_ = false;
  48.    
  49.     this->current_offset_ = 0;
  50.     this->expected_ack_data_ = 0;
  51.     this->program_start_time_ = 0;
  52. }
  53.  
  54. Node::~Node()
  55. {
  56.  
  57. }
  58.  
  59. void Node::ConnectSlots(const SignalOnNewState::slot_type& slot_on_new_state)
  60. {
  61.     this->signal_on_new_state_.connect(slot_on_new_state);
  62. }
  63.  
  64. Node::Id Node::GetId()
  65. {
  66.     return this->id_;
  67. }
  68.  
  69. Node::Information Node::GetInformation()
  70. {
  71.     return this->information_;
  72. }
  73.  
  74. void Node::SetupStateMachine()
  75. {
  76.     // Normal flow
  77.     Node::AddTransition(STATE_NORM_OFFLINE,          EVENT_BIOS_START,        STATE_NORM_ONLINE);
  78.     Node::AddTransition(STATE_NORM_OFFLINE,          EVENT_APP_START,         STATE_NORM_LIST);
  79.     Node::AddTransition(STATE_NORM_OFFLINE,          EVENT_HEARTBEAT,         STATE_NORM_LIST);
  80.     Node::AddTransition(STATE_NORM_OFFLINE,          EVENT_PROGRAM_BIOS,      STATE_BPGM_OFFLINE);
  81.     Node::AddTransition(STATE_NORM_OFFLINE,          EVENT_PROGRAM_APP,       STATE_APGM_OFFLINE);
  82.     Node::AddTransition(STATE_NORM_OFFLINE,          EVENT_RESET,             STATE_NO_CHANGE);
  83.    
  84.     Node::AddTransition(STATE_NORM_ONLINE,           EVENT_BIOS_START,        STATE_NORM_ONLINE);
  85.     Node::AddTransition(STATE_NORM_ONLINE,           EVENT_APP_START,         STATE_NORM_LIST);
  86.     Node::AddTransition(STATE_NORM_ONLINE,           EVENT_HEARTBEAT,         STATE_NORM_LIST);
  87.     Node::AddTransition(STATE_NORM_ONLINE,           EVENT_PROGRAM_BIOS,      STATE_BPGM_OFFLINE);
  88.     Node::AddTransition(STATE_NORM_ONLINE,           EVENT_PROGRAM_APP,       STATE_APGM_OFFLINE);
  89.     Node::AddTransition(STATE_NORM_ONLINE,           EVENT_RESET,             STATE_NORM_OFFLINE);
  90.    
  91.     Node::AddTransition(STATE_NORM_LIST,             EVENT_BIOS_START,        STATE_NORM_ONLINE);
  92.     Node::AddTransition(STATE_NORM_LIST,             EVENT_APP_START,         STATE_NORM_LIST);
  93.     Node::AddTransition(STATE_NORM_LIST,             EVENT_HEARTBEAT,         STATE_NO_CHANGE);
  94.     Node::AddTransition(STATE_NORM_LIST,             EVENT_LIST_DONE,         STATE_NORM_INITIALIZED);
  95.     Node::AddTransition(STATE_NORM_LIST,             EVENT_PROGRAM_BIOS,      STATE_BPGM_OFFLINE);
  96.     Node::AddTransition(STATE_NORM_LIST,             EVENT_PROGRAM_APP,       STATE_APGM_OFFLINE);
  97.     Node::AddTransition(STATE_NORM_LIST,             EVENT_RESET,             STATE_NORM_OFFLINE);
  98.    
  99.     Node::AddTransition(STATE_NORM_INITIALIZED,      EVENT_BIOS_START,        STATE_NORM_ONLINE);
  100.     Node::AddTransition(STATE_NORM_INITIALIZED,      EVENT_APP_START,         STATE_NORM_LIST);
  101.     Node::AddTransition(STATE_NORM_INITIALIZED,      EVENT_HEARTBEAT,         STATE_NO_CHANGE);
  102.     Node::AddTransition(STATE_NORM_INITIALIZED,      EVENT_PROGRAM_BIOS,      STATE_BPGM_OFFLINE);
  103.     Node::AddTransition(STATE_NORM_INITIALIZED,      EVENT_PROGRAM_APP,       STATE_APGM_OFFLINE);
  104.     Node::AddTransition(STATE_NORM_INITIALIZED,      EVENT_RESET,             STATE_NORM_OFFLINE);
  105.    
  106.    
  107.     // Program bios flow
  108.     Node::AddTransition(STATE_BPGM_OFFLINE,          EVENT_BIOS_START,        STATE_BPGM_START);
  109.    
  110.     Node::AddTransition(STATE_BPGM_START,            EVENT_PGM_ACK,           STATE_BPGM_DATA);
  111.     Node::AddTransition(STATE_BPGM_START,            EVENT_PGM_NACK,          STATE_NORM_OFFLINE); // Return to normal flow
  112.  
  113.     Node::AddTransition(STATE_BPGM_DATA,             EVENT_PGM_ACK,           STATE_BPGM_DATA); // If there is no more data to send STATE_PGM_END will be set instead
  114.     Node::AddTransition(STATE_BPGM_DATA,             EVENT_PGM_NACK,          STATE_NORM_OFFLINE); // Return to normal flow
  115.    
  116.     Node::AddTransition(STATE_BPGM_END,              EVENT_PGM_ACK,           STATE_BPGM_COPY);
  117.     Node::AddTransition(STATE_BPGM_END,              EVENT_PGM_NACK,          STATE_NORM_OFFLINE); // Return to normal flow
  118.    
  119.     Node::AddTransition(STATE_BPGM_COPY,             EVENT_BIOS_START,        STATE_APGM_OFFLINE); // Remove application after bios upgrade
  120.        
  121.    
  122.     // Program application flow
  123.     Node::AddTransition(STATE_APGM_OFFLINE,          EVENT_BIOS_START,        STATE_APGM_START);
  124.    
  125.     Node::AddTransition(STATE_APGM_START,            EVENT_PGM_ACK,           STATE_APGM_DATA);
  126.     Node::AddTransition(STATE_APGM_START,            EVENT_PGM_NACK,          STATE_NORM_OFFLINE); // Return to normal flow
  127.    
  128.     Node::AddTransition(STATE_APGM_DATA,             EVENT_PGM_ACK,           STATE_APGM_DATA); // If there is no more data to send STATE_PGM_END will be set instead
  129.     Node::AddTransition(STATE_APGM_DATA,             EVENT_PGM_NACK,          STATE_NORM_OFFLINE); // Return to normal flow
  130.    
  131.     Node::AddTransition(STATE_APGM_END,              EVENT_PGM_ACK,           STATE_NORM_OFFLINE); // Return to normal flow
  132.     Node::AddTransition(STATE_APGM_END,              EVENT_PGM_NACK,          STATE_NORM_OFFLINE); // Return to normal flow
  133.    
  134.    
  135.     Node::state_names_[STATE_INVALID] = "STATE_INVALID";
  136.     Node::state_names_[STATE_NO_CHANGE] = "STATE_NO_CHANGE";
  137.     Node::state_names_[STATE_NORM_OFFLINE] = "STATE_NORM_OFFLINE";
  138.     Node::state_names_[STATE_NORM_ONLINE] = "STATE_NORM_ONLINE";
  139.     Node::state_names_[STATE_NORM_LIST] = "STATE_NORM_LIST";
  140.     Node::state_names_[STATE_NORM_INITIALIZED] = "STATE_NORM_INITIALIZED";
  141.     Node::state_names_[STATE_BPGM_OFFLINE] = "STATE_BPGM_OFFLINE";
  142.     Node::state_names_[STATE_BPGM_START] = "STATE_BPGM_START";
  143.     Node::state_names_[STATE_BPGM_DATA] = "STATE_BPGM_DATA";
  144.     Node::state_names_[STATE_BPGM_END] = "STATE_BPGM_END";
  145.     Node::state_names_[STATE_BPGM_COPY] = "STATE_BPGM_COPY";
  146.     Node::state_names_[STATE_APGM_OFFLINE] = "STATE_APGM_OFFLINE";
  147.     Node::state_names_[STATE_APGM_START] = "STATE_APGM_START";
  148.     Node::state_names_[STATE_APGM_DATA] = "STATE_APGM_DATA";
  149.     Node::state_names_[STATE_APGM_END] = "STATE_APGM_END";
  150.    
  151.     Node::event_names_[EVENT_BIOS_START] = "EVENT_BIOS_START";
  152.     Node::event_names_[EVENT_APP_START] = "EVENT_APP_START";
  153.     Node::event_names_[EVENT_HEARTBEAT] = "EVENT_HEARTBEAT";
  154.     Node::event_names_[EVENT_PGM_ACK] = "EVENT_PGM_ACK";
  155.     Node::event_names_[EVENT_PGM_NACK] = "EVENT_PGM_NACK";
  156.     Node::event_names_[EVENT_LIST_DONE] = "EVENT_LIST_DONE";
  157.     Node::event_names_[EVENT_PROGRAM_BIOS] = "EVENT_PROGRAM_BIOS";
  158.     Node::event_names_[EVENT_PROGRAM_APP] = "EVENT_PROGRAM_APP";
  159.     Node::event_names_[EVENT_RESET] = "EVENT_RESET";
  160. }
  161.  
  162. void Node::AddTransition(Node::State current_state, Node::Event event, State target_state)
  163. {
  164.     Node::transitions_[current_state][event] = target_state;
  165. }
  166.  
  167. Node::State Node::GetTransitionTarget(Node::Event event)
  168. {
  169.     Node::TransitionList::iterator it1 = Node::transitions_.find(this->state_);
  170.    
  171.     if (it1 == Node::transitions_.end())
  172.     {
  173.         LOG.Warning("No transitions found for current state " + boost::lexical_cast<std::string>(this->state_));
  174.         return STATE_INVALID;
  175.     }
  176.    
  177.     Node::Transition::iterator it2 = it1->second.find(event);
  178.    
  179.     if (it2 == it1->second.end())
  180.     {
  181.         LOG.Warning("No transitions found for current state " + boost::lexical_cast<std::string>(this->state_) + " for event " + boost::lexical_cast<std::string>(event));
  182.         return STATE_INVALID;
  183.     }
  184.    
  185.     return it2->second;
  186. }
  187.  
  188. void Node::Trigger(Node::Event event, common::StringMap variables)
  189. {
  190.     if (!this->id_.empty())
  191.     {
  192.         this->ResetTimeout();
  193.        
  194.         State target_state = this->GetTransitionTarget(event);
  195.        
  196.         LOG.Debug("Trigger called on " + this->id_ + ", current state: " + Node::state_names_[this->state_] + ", event: " + Node::event_names_[event] + ", target state: "  + Node::state_names_[target_state]);
  197.        
  198.         if (event == EVENT_BIOS_START)
  199.         {
  200.             this->information_.has_application_ = boost::lexical_cast<int>(variables["HasApplication"]) > 0;
  201.             this->information_.bios_version_ = boost::lexical_cast<unsigned int>(variables["BiosVersion"]);
  202.             this->information_.device_type_ = variables["DeviceType"];
  203.             this->information_.valid_ = true;
  204.         }
  205.        
  206.         if (target_state == STATE_INVALID)
  207.         {
  208.             //this->SendReset();
  209.             target_state = STATE_NORM_OFFLINE;
  210.         }
  211.         else if (target_state == STATE_NO_CHANGE)
  212.         {
  213.             return;
  214.         }
  215.        
  216.         if ((this->state_ == STATE_APGM_END || this->state_ == STATE_BPGM_END) && event == EVENT_PGM_ACK)
  217.         {
  218.             unsigned int checksum = boost::lexical_cast<unsigned int>(variables["Data"]);
  219.            
  220.             if (this->expected_ack_data_ != checksum)
  221.             {
  222.                 LOG.Warning("Checksum received in ACK was incorrect got " + boost::lexical_cast<std::string>(checksum) + " expected " + boost::lexical_cast<std::string>(this->expected_ack_data_) + ", aborting...");
  223.  
  224.                 this->SendReset();
  225.  
  226.                 target_state = STATE_NORM_OFFLINE;
  227.             }
  228.             else if (this->state_ == STATE_BPGM_END)
  229.             {
  230.                 float speed = (float)this->code_->GetLength() / (float)(time(NULL) - this->program_start_time_);
  231.                
  232.                 LOG.Info("Data was successfully transferred to " + this->id_ + ", speed was " + boost::lexical_cast<std::string>(speed) + " B/s");
  233.                
  234.                 LOG.Debug("Sending application programming copy to node " + this->id_);
  235.                 can::Message* payload = new can::Message("nmt", "", "", 0, "Pgm_Copy");
  236.                
  237.                 unsigned int source0 = GET_LOW_BYTE_16(this->start_offset_);
  238.                 unsigned int source1 = GET_HIGH_BYTE_16(this->start_offset_);
  239.                
  240.                 unsigned int destination0 = GET_LOW_BYTE_16(this->code_->GetAddressLower());
  241.                 unsigned int destination1 = GET_HIGH_BYTE_16(this->code_->GetAddressLower());
  242.                
  243.                 unsigned int length0 = GET_LOW_BYTE_16(this->code_->GetLength());
  244.                 unsigned int length1 = GET_HIGH_BYTE_16(this->code_->GetLength());
  245.                
  246.                 payload->SetVariable("Source0", boost::lexical_cast<std::string>(source0));
  247.                 payload->SetVariable("Source1", boost::lexical_cast<std::string>(source1));
  248.                 payload->SetVariable("Destination0", boost::lexical_cast<std::string>(destination0));
  249.                 payload->SetVariable("Destination1", boost::lexical_cast<std::string>(destination1));
  250.                 payload->SetVariable("Length0", boost::lexical_cast<std::string>(length0));
  251.                 payload->SetVariable("Length1", boost::lexical_cast<std::string>(length1));
  252.                
  253.                 broker::Manager::Instance()->Post(broker::Message::Pointer(new broker::Message(broker::Message::CAN_MESSAGE, broker::Message::PayloadPointer(payload), Manager::Instance().get())));
  254.             }
  255.             else
  256.             {
  257.                 float speed = (float)this->code_->GetLength() / (float)(time(NULL) - this->program_start_time_);
  258.                
  259.                 LOG.Info("Programming was completed successfully on " + this->id_ + ", speed was " + boost::lexical_cast<std::string>(speed) + " B/s");
  260.             }
  261.         }
  262.        
  263.         if (target_state == STATE_NORM_LIST)
  264.         {
  265.             this->SendListRequest();
  266.         }
  267.         else if (target_state == STATE_BPGM_OFFLINE || target_state == STATE_APGM_OFFLINE)
  268.         {
  269.             if (event != EVENT_BIOS_START)
  270.             {
  271.                 this->SendReset();
  272.             }
  273.         }
  274.         else if (target_state == STATE_APGM_START || target_state == STATE_BPGM_START)
  275.         {
  276.             if (!this->code_->IsValid())
  277.             {
  278.                 LOG.Error("Code is not valid, aborting...");
  279.                 target_state = STATE_NORM_OFFLINE;
  280.             }
  281.             else
  282.             {
  283.                 LOG.Debug("Sending programming start to node " + this->id_);
  284.                 can::Message* payload = new can::Message("nmt", "", "", 0, "Pgm_Start");
  285.                 payload->SetVariable("HardwareId", boost::lexical_cast<std::string>(common::FromHex(this->id_)));
  286.                
  287.                 this->start_offset_ = target_state == STATE_BPGM_START ? 0 : this->code_->GetAddressLower();
  288.                
  289.                 unsigned int address0 = GET_LOW_BYTE_16(this->start_offset_);
  290.                 unsigned int address1 = GET_HIGH_BYTE_16(this->start_offset_);
  291.                
  292.                 this->expected_ack_data_ = SWAP_BYTE_ORDER_16(this->start_offset_);
  293.                
  294.                 payload->SetVariable("Address0", boost::lexical_cast<std::string>(address0));
  295.                 payload->SetVariable("Address1", boost::lexical_cast<std::string>(address1));
  296.                 payload->SetVariable("Address2", "0"); // Not used?
  297.                 payload->SetVariable("Address3", "0"); // Not used?
  298.                
  299.                 this->current_offset_ = 0;
  300.                
  301.                 broker::Manager::Instance()->Post(broker::Message::Pointer(new broker::Message(broker::Message::CAN_MESSAGE, broker::Message::PayloadPointer(payload), Manager::Instance().get())));
  302.                
  303.                 this->program_start_time_ = time(NULL);
  304.             }
  305.         }
  306.         else if (target_state == STATE_APGM_DATA || target_state == STATE_BPGM_DATA)
  307.         {
  308.             unsigned int offset = boost::lexical_cast<unsigned int>(variables["Data"]);
  309.            
  310.             if ( this->expected_ack_data_ != offset)
  311.             {
  312.                 LOG.Warning("Offset received in ACK was incorrect got " + boost::lexical_cast<std::string>(offset) + " expected " + boost::lexical_cast<std::string>(this->expected_ack_data_) + ", aborting...");
  313.                 target_state = STATE_NORM_OFFLINE;
  314.             }
  315.             else if (this->current_offset_ >= this->code_->GetLength())
  316.             {
  317.                 LOG.Debug("Sending programming end to node " + this->id_);
  318.                
  319.                 unsigned int checksum = this->code_->GetChecksum();
  320.                 this->expected_ack_data_ = SWAP_BYTE_ORDER_16(checksum);
  321.                
  322.                 can::Message* payload = new can::Message("nmt", "", "", 0, "Pgm_End");
  323.                 broker::Manager::Instance()->Post(broker::Message::Pointer(new broker::Message(broker::Message::CAN_MESSAGE, broker::Message::PayloadPointer(payload), Manager::Instance().get())));
  324.                
  325.                 target_state = target_state == STATE_BPGM_DATA ? STATE_BPGM_END : STATE_APGM_END;
  326.             }
  327.             else
  328.             {
  329.                 LOG.Debug("Sending programming data to node " + this->id_);
  330.                 can::Message* payload = new can::Message("nmt", "", "", 0, "Pgm_Data_48");
  331.                
  332.                 unsigned int offset0 = GET_LOW_BYTE_16(this->start_offset_ + this->current_offset_);
  333.                 unsigned int offset1 = GET_HIGH_BYTE_16(this->start_offset_ + this->current_offset_);
  334.                
  335.                 this->expected_ack_data_ = SWAP_BYTE_ORDER_16(this->start_offset_ + this->current_offset_);
  336.                
  337.                 payload->SetVariable("Offset0", boost::lexical_cast<std::string>(offset0));
  338.                 payload->SetVariable("Offset1", boost::lexical_cast<std::string>(offset1));
  339.                
  340.                 for (int n = 0; (this->current_offset_ < this->code_->GetLength()) && (n < 6); n++)
  341.                 {
  342.                     //LOG.Debug("Data" + boost::lexical_cast<std::string>(n) + "=" +  boost::lexical_cast<std::string>((unsigned int)this->code_->GetByte(this->code_->GetAddressLower() + this->current_offset_)));
  343.                     payload->SetVariable("Data" + boost::lexical_cast<std::string>(n), boost::lexical_cast<std::string>((unsigned int)this->code_->GetByte(this->code_->GetAddressLower() + this->current_offset_)));
  344.                     this->current_offset_++;
  345.                 }
  346.                
  347.                 broker::Manager::Instance()->Post(broker::Message::Pointer(new broker::Message(broker::Message::CAN_MESSAGE, broker::Message::PayloadPointer(payload), Manager::Instance().get())));
  348.             }
  349.         }
  350.         else if (target_state == STATE_BPGM_COPY)
  351.         {
  352.             LOG.Debug("Preparing to send null application to node " + this->id_);
  353.            
  354.             this->code_->Reset(false);
  355.            
  356.             this->code_->AddByte(0xFF);
  357.             this->code_->AddByte(0xFF);
  358.         }
  359.         else if (target_state == STATE_NORM_OFFLINE)
  360.         {
  361.             this->information_.valid_ = false;
  362.             this->SendReset();
  363.         }
  364.        
  365.        
  366.         if (this->state_ != target_state)
  367.         {
  368.             this->signal_on_new_state_(this->id_, this->state_, target_state);
  369.         }
  370.        
  371.         this->state_ = target_state;
  372.     }
  373. }
  374.  
  375. bool Node::CheckTimeout()
  376. {
  377.     if (this->state_ == STATE_NORM_OFFLINE)
  378.     {
  379.         return true;
  380.     }
  381.    
  382.     if (this->code_.use_count() == 0 && this->information_.last_active_ + 10 < time(NULL))
  383.     {
  384.         this->state_ = STATE_NORM_OFFLINE;
  385.         return false;
  386.     }
  387.    
  388.     return true;
  389. }
  390.  
  391. void Node::ResetTimeout()
  392. {
  393.     this->information_.last_active_ = time(NULL);
  394. }
  395.  
  396. void Node::Reset()
  397. {
  398.     common::StringMap variables;
  399.    
  400.     this->Trigger(EVENT_RESET, variables);
  401. }
  402.  
  403. void Node::ProgramApplication(Code::Pointer code)
  404. {
  405.     this->code_ = code;
  406.    
  407.     common::StringMap variables;
  408.    
  409.     this->Trigger(EVENT_PROGRAM_APP, variables);
  410. }
  411.  
  412. void Node::ProgramBios(Code::Pointer code)
  413. {
  414.     this->code_ = code;
  415.    
  416.     common::StringMap variables;
  417.    
  418.     this->Trigger(EVENT_PROGRAM_BIOS, variables);
  419. }
  420.  
  421. void Node::SendReset()
  422. {
  423.     if (!this->id_.empty())
  424.     {
  425.         LOG.Info("Sending node reset to " + this->id_);
  426.         can::Message* payload = new can::Message("nmt", "", "", 0, "Reset");
  427.         payload->SetVariable("HardwareId", boost::lexical_cast<std::string>(common::FromHex(this->id_)));
  428.        
  429.         broker::Manager::Instance()->Post(broker::Message::Pointer(new broker::Message(broker::Message::CAN_MESSAGE, broker::Message::PayloadPointer(payload), Manager::Instance().get())));
  430.     }
  431. }
  432.  
  433. void Node::SendListRequest()
  434. {
  435.     if (!this->id_.empty())
  436.     {
  437.         LOG.Debug("Sending module listing on node " + this->id_);
  438.         can::Message* payload = new can::Message("mnmt", "To_Owner", "", 0, "List");
  439.         payload->SetVariable("HardwareId", boost::lexical_cast<std::string>(common::FromHex(this->id_)));
  440.        
  441.         broker::Manager::Instance()->Post(broker::Message::Pointer(new broker::Message(broker::Message::CAN_MESSAGE, broker::Message::PayloadPointer(payload), Manager::Instance().get())));
  442.     }
  443. }
  444.  
  445. }; // namespace control
  446. }; // namespace atom
  447.