services-ros1-xbee_mav/src/PacketsHandler.cpp
2017-02-17 17:05:45 -05:00

779 lines
24 KiB
C++

/* PacketsHandler.cpp -- Packets Handler class for XBee:
Serialize, deserialize, fragment and reassemly mavlink
messages (packets and std messages) -- */
/* ------------------------------------------------------------------------- */
/* February 06, 2017 -- @Copyright Aymen Soussia. All rights reserved. */
/* (aymen.soussia@gmail.com) */
#include"PacketsHandler.h"
namespace Mist
{
namespace Xbee
{
//*****************************************************************************
PacketsHandler::PacketsHandler():
MAX_PACEKT_SIZE(64000),
XBEE_NETWORK_MTU(250),
FRAGMENT_HEADER_SIZE(6),
MAX_TIME_TO_SEND_PACKET(30000),
START_DLIMITER(static_cast<unsigned char>(0x7E)),
device_64_bits_address_("12345678"),
loaded_SL_(false),
loaded_SH_(false),
optimum_MT_NBR_(3),
delay_interframes_(100 * 1000)
{
}
//*****************************************************************************
PacketsHandler::~PacketsHandler()
{
}
//*****************************************************************************
bool PacketsHandler::Init(SerialDevice* serial_device,
Thread_Safe_Deque* in_packets)
{
serial_device_ = serial_device;
if (!Load_Database_Addresses())
return false;
// TO DO node dicov
Send_SL_and_SH_Commands();
in_packets_ = in_packets;
return true;
}
//*****************************************************************************
bool PacketsHandler::Init_Device_ID()
{
if (Get_Local_Address())
return true;
else
return false;
}
//*****************************************************************************
void PacketsHandler::Run()
{
quit_.store(false);
while (!quit_.load())
{
Process_Out_Standard_Messages();
Process_Out_Packets();
}
}
//*****************************************************************************
void PacketsHandler::Handle_Mavlink_Message(const mavros_msgs::Mavlink::ConstPtr& mavlink_msg)
{
std::shared_ptr<std::string> serialized_packet =
std::make_shared<std::string>();
Serialize_Mavlink_Message(mavlink_msg, serialized_packet);
if (serialized_packet->size() > XBEE_NETWORK_MTU && serialized_packet->size() <= MAX_PACEKT_SIZE)
{
std::shared_ptr<std::vector<std::shared_ptr<std::string>>> fragmented_packet =
std::make_shared<std::vector<std::shared_ptr<std::string>>>();
std::size_t offset = 0;
std::size_t NBR_of_bytes = 0;
std::size_t NBR_of_fragments = std::ceil(
static_cast<float>(serialized_packet->size()) / XBEE_NETWORK_MTU);
for (uint8_t i = 0; i < NBR_of_fragments; i++)
{
fragmented_packet->push_back(std::make_shared<std::string>());
NBR_of_bytes = std::min(XBEE_NETWORK_MTU, serialized_packet->size() - offset);
Insert_Fragment_Header(false, fragmented_packet->at(i), mavlink_msg->msgid, i, offset);
fragmented_packet->at(i)->append(serialized_packet->c_str() + offset, NBR_of_bytes);
offset += NBR_of_bytes;
}
out_packets_.Push_Back({mavlink_msg->msgid, fragmented_packet});
}
else if (serialized_packet->size() < XBEE_NETWORK_MTU)
{
serialized_packet->insert(0, 1, 'S');
out_std_messages_.Push_Back(serialized_packet);
}
}
//*****************************************************************************
void PacketsHandler::Process_Fragment(std::shared_ptr<std::string> fragment)
{
uint8_t packet_ID = fragment->at(1);
uint8_t node_8_bits_address = fragment->at(2);
uint8_t fragment_ID = fragment->at(3);
uint16_t offset = static_cast<uint16_t>(
static_cast<uint16_t>(static_cast<unsigned char>(fragment->at(4))) << 8 |
static_cast<uint16_t>(static_cast<unsigned char>(fragment->at(5))));
assembly_map_it_ = packets_assembly_map_.find(node_8_bits_address);
if (assembly_map_it_ != packets_assembly_map_.end())
{
if (assembly_map_it_->second.packet_ID_ == packet_ID)
{
std::set<uint8_t>::iterator it = assembly_map_it_->second.received_fragments_IDs_.find(fragment_ID);
if (it == assembly_map_it_->second.received_fragments_IDs_.end())
{
if (assembly_map_it_->second.received_fragments_IDs_.size() == 0)
assembly_map_it_->second.time_since_creation_ = std::clock();
Insert_Fragment_In_Packet_Buffer(&assembly_map_it_->second.packet_buffer_, fragment->c_str(), offset, fragment->size());
assembly_map_it_->second.received_fragments_IDs_.insert(fragment_ID);
}
}
else
{
assembly_map_it_->second = {};
assembly_map_it_->second.packet_ID_ = packet_ID;
Insert_Fragment_In_Packet_Buffer(&assembly_map_it_->second.packet_buffer_, fragment->c_str(), offset, fragment->size());
assembly_map_it_->second.received_fragments_IDs_.insert(fragment_ID);
assembly_map_it_->second.time_since_creation_ = std::clock();
}
}
else
{
Add_New_Node_To_Network(node_8_bits_address);
assembly_map_it_ = packets_assembly_map_.find(node_8_bits_address);
assembly_map_it_->second.packet_ID_ = packet_ID;
Insert_Fragment_In_Packet_Buffer(&assembly_map_it_->second.packet_buffer_, fragment->c_str(), offset, fragment->size());
assembly_map_it_->second.received_fragments_IDs_.insert(fragment_ID);
assembly_map_it_->second.time_since_creation_ = std::clock();
}
}
//*****************************************************************************
void PacketsHandler::Insert_Fragment_In_Packet_Buffer(std::string* buffer, const char* fragment, const uint16_t offset, const std::size_t length) // TO DO delete length
{
if (offset >= buffer->size())
buffer->append(fragment + FRAGMENT_HEADER_SIZE, length - FRAGMENT_HEADER_SIZE);
else
buffer->insert(offset, fragment + FRAGMENT_HEADER_SIZE, length - FRAGMENT_HEADER_SIZE);
}
//*****************************************************************************
void PacketsHandler::Process_Ping_Or_Acknowledgement(std::shared_ptr<std::string> frame) // TO DO change useless std::shared_ptr<std::string> frame args to ->c_str()
{
uint8_t packet_ID = frame->at(12);
uint8_t node_8_bits_address = frame->at(13);
if (frame->at(11) == 'A')
{
/*std::lock_guard<std::mutex> guard(mutex_);
connected_network_nodes_it_ = connected_network_nodes_.find(node_8_bits_address);
if (connected_network_nodes_it_ == connected_network_nodes_.end())
{
Add_New_Node_To_Network(node_8_bits_address);
connected_network_nodes_it_ = connected_network_nodes_.find(node_8_bits_address);
}
if (packet_ID == current_processed_packet_ID_)
{
if (frame->size() < 15)
connected_network_nodes_it_->second = true;
else
{
for (uint8_t i = 14; i < frame->size(); i++)
fragments_indexes_to_transmit_.insert(frame->at(i));
}
}*/
}
else if (frame->at(11) == 'P')
{
assembly_map_it_ = packets_assembly_map_.find(node_8_bits_address);
if (assembly_map_it_ == packets_assembly_map_.end())
{
Add_New_Node_To_Network(node_8_bits_address);
assembly_map_it_ = packets_assembly_map_.find(node_8_bits_address);
assembly_map_it_->second.packet_ID_ = packet_ID;
assembly_map_it_->second.time_since_creation_ = std::clock();
}
if (assembly_map_it_->second.packet_ID_ == packet_ID)
{
std::string Acknowledgement = "A";
Acknowledgement.push_back(packet_ID);
Acknowledgement.push_back(device_address_);
uint8_t packet_size = frame->at(14);
if (assembly_map_it_->second.received_fragments_IDs_.size() == packet_size)
{
in_packets_->Push_Back(std::make_shared<std::string>(assembly_map_it_->second.packet_buffer_));
assembly_map_it_->second.packet_buffer_.clear();
assembly_map_it_->second.received_fragments_IDs_.clear();
assembly_map_it_->second.time_since_creation_ = 0;
}
else
{
std::set<uint8_t>::iterator it = assembly_map_it_->second.received_fragments_IDs_.begin();
uint8_t j = 0;
while (j <= packet_size - 1)
{
if (j != *it)
Acknowledgement.push_back(j);
else if (it != std::prev(assembly_map_it_->second.received_fragments_IDs_.end()))
it++;
j++;
}
}
std::string Ack_frame;
Generate_Transmit_Request_Frame(Acknowledgement.c_str(), &Ack_frame, Acknowledgement.size()); // TO Do send as unicast
serial_device_->Send_Frame(Ack_frame);
usleep(delay_interframes_);
}
else
{
assembly_map_it_->second = {};
assembly_map_it_->second.packet_ID_ = packet_ID;
}
}
}
//*****************************************************************************
void PacketsHandler::Add_New_Node_To_Network(const uint8_t new_node_address)
{
std::set<uint8_t> empty_set;
packets_assembly_map_.insert(std::pair<uint8_t, Reassembly_Packet_S>(new_node_address, {}));
std::lock_guard<std::mutex> guard(mutex_);
connected_network_nodes_.insert(std::pair<uint8_t, bool>(new_node_address, false));
}
//*****************************************************************************
void PacketsHandler::Process_Command_Response(const char* command_response)
{
if (command_response[0] == 'N' && command_response[1] == 'D')
{
uint64_t new_node_address = 0;
std::lock_guard<std::mutex> guard(mutex_);
if (command_response[2] == static_cast<unsigned char>(0)) // TO DO check this
{
new_node_address = static_cast<uint64_t>( // TO DO correct this
static_cast<unsigned char>(command_response[5]) << 56 |
static_cast<unsigned char>(command_response[6]) << 48 |
static_cast<unsigned char>(command_response[7]) << 40 |
static_cast<unsigned char>(command_response[8]) << 32 |
static_cast<unsigned char>(command_response[9]) << 24 |
static_cast<unsigned char>(command_response[10]) << 16 |
static_cast<unsigned char>(command_response[11]) << 8 |
static_cast<unsigned char>(command_response[12]));
}
database_addresses_it_ = database_addresses_.find(new_node_address);
if (database_addresses_it_ != database_addresses_.end())
device_address_ = database_addresses_it_->second;
else
std::cout << "Remote Node Not in Database" << std::endl;
}
else if (command_response[0] == 'S' && command_response[1] == 'H')
{
if (command_response[2] == static_cast<unsigned char>(0)) // TO DO check this
{
loaded_SH_ = true;
for (std::size_t i = 0; i < 4; i++)
device_64_bits_address_[i] = command_response[3 + i];
}
}
else if (command_response[0] == 'S' && command_response[1] == 'L')
{
if (command_response[2] == static_cast<unsigned char>(0)) // TO DO check this
{
loaded_SL_ = true;
for (std::size_t i = 0; i < 4; i++)
device_64_bits_address_[4 + i] = command_response[3 + i];
}
}
}
//*****************************************************************************
void PacketsHandler::Quit()
{
quit_.store(true);
}
//*****************************************************************************
void PacketsHandler::Serialize_Mavlink_Message(const mavros_msgs::Mavlink::ConstPtr&
mavlink_msg, std::shared_ptr<std::string> serialized_packet)
{
serialized_packet->push_back(mavlink_msg->sysid);
serialized_packet->push_back(mavlink_msg->msgid);
std::string bytes="12345678";
for (std::size_t j = 0; j < mavlink_msg->payload64.size(); j++)
{
for (std::size_t i = 0; i < 8; i++)
bytes[7 - i] = (mavlink_msg->payload64.at(j) >> (i * 8));
serialized_packet->append(bytes);
}
}
//*****************************************************************************
void PacketsHandler::Insert_Fragment_Header(bool single_fragment,
std::shared_ptr<std::string> fragment, const uint8_t packet_ID,
const uint8_t fragment_ID, const uint16_t offset)
{
if (!single_fragment)
{
fragment->push_back('F');
fragment->push_back(packet_ID);
fragment->push_back(device_address_);
fragment->push_back(fragment_ID);
fragment->push_back(offset >> (1 * 8));
fragment->push_back(offset >> (0 * 8));
}
else
fragment->push_back('S');
}
//*****************************************************************************
void PacketsHandler::Delete_Packets_With_Time_Out()
{
for(auto& iterator: packets_assembly_map_)
{
if (std::clock_t() - iterator.second.time_since_creation_ > MAX_TIME_TO_SEND_PACKET && iterator.second.time_since_creation_ != 0)
iterator.second = {};
}
}
//*****************************************************************************
void PacketsHandler::Process_Out_Standard_Messages()
{
std::size_t deque_size = out_std_messages_.Get_Size();
if (deque_size > 0)
{
std::string frame;
std::shared_ptr<std::string> out_message;
for (std::size_t i = 0; i < deque_size; i++)
{
frame.clear();
out_message = out_std_messages_.Pop_Front();
Generate_Transmit_Request_Frame(out_message->c_str(), &frame, out_message->size());
serial_device_->Send_Frame(frame);
usleep(delay_interframes_);
}
}
}
//*****************************************************************************
void PacketsHandler::Process_Out_Packets()
{
std::size_t deque_size = out_packets_.Get_Size();
if (deque_size > 0)
{
Out_Packet_S out_packet;
for (std::size_t i = 0; i < deque_size; i++)
{
Process_Out_Standard_Messages();
out_packet = out_packets_.Pop_Front();
Send_Packet(out_packet);
}
}
}
//*****************************************************************************
void PacketsHandler::Send_Packet(const Out_Packet_S& packet)
{
std::size_t NBR_of_transmission = 0;
std::vector<std::string> frames;
Init_Network_Nodes_For_New_Transmission(packet.packet_ID_, &frames, packet.packet_buffer_);
std::clock_t elapsed_time = std::clock();
//while (NBR_of_transmission <= optimum_MT_NBR_ && !Check_Packet_Transmitted_To_All_Nodes())
//{
//NBR_of_transmission++;
Transmit_Fragments(frames);
// TO DO usleep(flow_control_time)
// TO DO clear fragments_IDs_to_transmit_set
Send_End_Of_Packet_Ping(packet.packet_ID_, packet.packet_buffer_->size());
// TO DO sleep after ping
//}
elapsed_time = std::clock() - elapsed_time;
Adjust_Optimum_MT_Number(elapsed_time, NBR_of_transmission);
}
//*****************************************************************************
void PacketsHandler::Send_End_Of_Packet_Ping(const uint8_t packet_ID, const uint8_t total_NBR_of_fragments)
{
std::string ping_message = "P";
std::string ping_frame;
ping_message.push_back(packet_ID);
ping_message.push_back(device_address_);
ping_message.push_back(total_NBR_of_fragments);
Generate_Transmit_Request_Frame(ping_message.c_str(), &ping_frame, ping_message.size());
serial_device_->Send_Frame(ping_frame);
usleep(delay_interframes_);
}
//*****************************************************************************
bool PacketsHandler::Load_Database_Addresses()
{
const std::string FILE_PATH = "~/ROS_WS/src/xbeemav/Resources/database.xml";
if (!boost::filesystem::exists(FILE_PATH))
{
std::cout << "database.xml Not Found." << std::endl;
return false;
}
ptree pt;
boost::property_tree::read_xml(FILE_PATH, pt);
std::string short_address;
std::string address_64_bits;
unsigned int short_address_int;
uint64_t address_64_bits_int;
BOOST_FOREACH(ptree::value_type const&v, pt.get_child("Addresses"))
{
if (v.first == "Device")
{
short_address = v.second.get<std::string>("<xmlattr>.Address");
address_64_bits = v.second.data();
if (sscanf(short_address.c_str(), "%3u", &short_address_int) < 0)
{
std::cout << "Short Address Error. Please Check database.xml For Possible Errors." << std::endl;
return false;
}
if (sscanf(address_64_bits.c_str(), "%" SCNx64, &address_64_bits_int) < 0)
{
std::cout << "64 bits Address Error. Please Check database.xml For Possible Errors." << std::endl;
return false;
}
database_addresses_.insert(std::pair<uint64_t, uint8_t>(address_64_bits_int, static_cast<uint8_t>(short_address_int)));
}
}
return true;
}
//*****************************************************************************
bool PacketsHandler::Get_Local_Address()
{
const useconds_t ONE_SECOND = 1*1000*1000; /* 1s = 1 * 10⁶ microseconds. */
usleep(ONE_SECOND);
if (loaded_SH_ && loaded_SL_)
{
uint64_t local_64_bits_address = (
static_cast<uint64_t>(static_cast<unsigned char>(device_64_bits_address_[0])) << 56 |
static_cast<uint64_t>(static_cast<unsigned char>(device_64_bits_address_[1])) << 48 |
static_cast<uint64_t>(static_cast<unsigned char>(device_64_bits_address_[2])) << 40 |
static_cast<uint64_t>(static_cast<unsigned char>(device_64_bits_address_[3])) << 32 |
static_cast<uint64_t>(static_cast<unsigned char>(device_64_bits_address_[4])) << 24 |
static_cast<uint64_t>(static_cast<unsigned char>(device_64_bits_address_[5])) << 16 |
static_cast<uint64_t>(static_cast<unsigned char>(device_64_bits_address_[6])) << 8 |
static_cast<uint64_t>(static_cast<unsigned char>(device_64_bits_address_[7])));
database_addresses_it_ = database_addresses_.find(local_64_bits_address);
if (database_addresses_it_ != database_addresses_.end())
{
device_address_ = database_addresses_it_->second;
std::cout << "Loaded Short Device Address : " << static_cast<int>(device_address_) << std::endl;
return true;
}
else
{
std::cout << "Local Address Not Found In Database. Please Add The Xbee Address to database.xml." << std::endl;
return false;
}
}
else
{
Send_SL_and_SH_Commands();
return false;
}
}
//*****************************************************************************
bool PacketsHandler::Check_Packet_Transmitted_To_All_Nodes()
{
std::lock_guard<std::mutex> guard(mutex_);
if (connected_network_nodes_.size() == 0)
return false;
for (auto it : connected_network_nodes_)
{
if (!it.second)
return false;
}
return true;
}
//*****************************************************************************
void PacketsHandler::Init_Network_Nodes_For_New_Transmission(const uint8_t packet_ID, std::vector<std::string>* frames, std::shared_ptr<std::vector<std::shared_ptr<std::string>>> packet)
{
std::lock_guard<std::mutex> guard(mutex_);
fragments_indexes_to_transmit_.clear();
for (auto it : connected_network_nodes_)
it.second = false;
current_processed_packet_ID_ = packet_ID;
for (uint8_t i = 0; i < packet->size(); i++)
{
frames->push_back("");
fragments_indexes_to_transmit_.insert(i);
Generate_Transmit_Request_Frame(packet->at(i)->c_str(), &frames->at(i), packet->at(i)->size());
}
}
//*****************************************************************************
void PacketsHandler::Transmit_Fragments(const std::vector<std::string>& frames)
{
std::lock_guard<std::mutex> guard(mutex_);
for (auto index: fragments_indexes_to_transmit_)
{
serial_device_->Send_Frame(frames.at(index));
usleep(delay_interframes_);
}
}
//*****************************************************************************
void PacketsHandler::Adjust_Optimum_MT_Number(const std::clock_t elapsed_time,
const std::size_t NBR_of_transmission)
{
if (NBR_of_transmission < optimum_MT_NBR_)
optimum_MT_NBR_ = NBR_of_transmission;
else if (NBR_of_transmission == optimum_MT_NBR_)
{
std::lock_guard<std::mutex> guard(mutex_);
if (elapsed_time <= MAX_TIME_TO_SEND_PACKET && fragments_indexes_to_transmit_.size() > 0)
optimum_MT_NBR_++;
}
}
//*****************************************************************************
void PacketsHandler::Send_SL_and_SH_Commands()
{
std::string command;
std::string frame;
command = "SL";
Generate_AT_Command(command.c_str(), &frame);
serial_device_->Send_Frame(frame);
command = "SH";
frame = "";
Generate_AT_Command(command.c_str(), &frame);
serial_device_->Send_Frame(frame);
}
//*****************************************************************************
void PacketsHandler::Deserialize_Mavlink_Message(const char * bytes,
mavros_msgs::Mavlink* mavlink_msg, const std::size_t msg_size)
{
mavlink_msg->sysid = bytes[0];
mavlink_msg->msgid = bytes[1];
uint64_t current_int64 = 0;
for (std::size_t i = 2; i < msg_size; i += 8)
{
current_int64 = (
static_cast<uint64_t>(static_cast<unsigned char>(bytes[i])) << 56 |
static_cast<uint64_t>(static_cast<unsigned char>(bytes[i + 1])) << 48 |
static_cast<uint64_t>(static_cast<unsigned char>(bytes[i + 2])) << 40 |
static_cast<uint64_t>(static_cast<unsigned char>(bytes[i + 3])) << 32 |
static_cast<uint64_t>(static_cast<unsigned char>(bytes[i + 4])) << 24 |
static_cast<uint64_t>(static_cast<unsigned char>(bytes[i + 5])) << 16 |
static_cast<uint64_t>(static_cast<unsigned char>(bytes[i + 6])) << 8 |
static_cast<uint64_t>(static_cast<unsigned char>(bytes[i + 7])));
mavlink_msg->payload64.push_back(current_int64);
}
}
//*****************************************************************************
inline void PacketsHandler::Generate_Transmit_Request_Frame(
const char* message,
std::string * frame,
const std::size_t message_size,
const unsigned char frame_ID,
const std::string& destination_adssress,
const std::string& short_destination_adress,
const std::string& broadcast_radius,
const std::string& options)
{
const unsigned char FAME_TYPE = static_cast<unsigned char>(0x10); /* Transmit Request Frame */
std::string frame_parameters;
std::string bytes_frame_parameters;
frame_parameters.append(destination_adssress);
frame_parameters.append(short_destination_adress);
frame_parameters.append(broadcast_radius);
frame_parameters.append(options);
Convert_HEX_To_Bytes(frame_parameters, &bytes_frame_parameters);
frame->push_back(FAME_TYPE);
frame->push_back(frame_ID);
frame->append(bytes_frame_parameters);
frame->append(message, message_size);
Calculate_and_Append_Checksum(frame);
Add_Length_and_Start_Delimiter(frame);
}
//*****************************************************************************
inline void PacketsHandler::Add_Length_and_Start_Delimiter(
std::string* frame)
{
const unsigned short FIVE_BYTES = 5;
char temporary_buffer[FIVE_BYTES];
unsigned char temporary_char;
int Hex_frame_length_1;
int Hex_frame_length_2;
std::string header;
int frame_length = frame->size() - 1; /* frame_length = frame_size - checksum byte */
header.push_back(START_DLIMITER);
sprintf(temporary_buffer, "%04X", frame_length);
sscanf(temporary_buffer, "%02X%02X", &Hex_frame_length_1,
&Hex_frame_length_2);
temporary_char = static_cast<unsigned char>(Hex_frame_length_1);
header.push_back(temporary_char);
temporary_char = static_cast<unsigned char>(Hex_frame_length_2);
header.push_back(temporary_char);
frame->insert(0, header);
}
//*****************************************************************************
inline void PacketsHandler::Calculate_and_Append_Checksum(
std::string* frame)
{
unsigned short bytes_sum = 0;
unsigned lowest_8_bits = 0;
unsigned short checksum = 0;
unsigned char checksum_byte;
for (unsigned short i = 0; i < frame->size(); i++)
bytes_sum += static_cast<unsigned short>(frame->at(i));
lowest_8_bits = bytes_sum & 0xFF;
checksum = 0xFF - lowest_8_bits;
checksum_byte = static_cast<unsigned char>(checksum);
frame->push_back(checksum_byte);
}
//*****************************************************************************
inline void PacketsHandler::Convert_HEX_To_Bytes(
const std::string& HEX_data, std::string* converted_data)
{
const unsigned short TWO_BYTES = 2;
char temporary_buffer[TWO_BYTES];
unsigned char temporary_char;
int temporary_HEX;
for (unsigned short i = 0; i <= HEX_data.size() - TWO_BYTES;
i += TWO_BYTES)
{
memcpy(temporary_buffer, HEX_data.c_str() + i, TWO_BYTES);
sscanf(temporary_buffer, "%02X", &temporary_HEX);
temporary_char = static_cast<unsigned char>(temporary_HEX);
converted_data->push_back(temporary_char);
}
}
//*****************************************************************************
void PacketsHandler::Generate_AT_Command(const char* command,
std::string* frame,
const unsigned char frame_ID)
{
const unsigned char FAME_TYPE = static_cast<unsigned char>(0x09);/* AT Command Frame */
std::string temporary_parameter_value;
frame->push_back(FAME_TYPE);
frame->push_back(frame_ID);
frame->append(command);
Calculate_and_Append_Checksum(frame);
Add_Length_and_Start_Delimiter(frame);
}
}
}