19 #include <condition_variable> 22 #include <std_srvs/Empty.h> 23 #include <mavros_msgs/FileEntry.h> 24 #include <mavros_msgs/FileList.h> 25 #include <mavros_msgs/FileOpen.h> 26 #include <mavros_msgs/FileClose.h> 27 #include <mavros_msgs/FileRead.h> 28 #include <mavros_msgs/FileWrite.h> 29 #include <mavros_msgs/FileRemove.h> 30 #include <mavros_msgs/FileMakeDir.h> 31 #include <mavros_msgs/FileRemoveDir.h> 32 #include <mavros_msgs/FileTruncate.h> 33 #include <mavros_msgs/FileRename.h> 34 #include <mavros_msgs/FileChecksum.h> 40 namespace std_plugins {
48 class FTPRequest :
public mavlink::common::msg::FILE_TRANSFER_PROTOCOL {
107 return payload.data();
119 return reinterpret_cast<char *
>(
header()->
data);
123 return reinterpret_cast<uint32_t *
>(
header()->
data);
135 size_t sz = (s.size() < DATA_MAXSZ - 1) ? s.size() : DATA_MAXSZ - 1;
137 memcpy(
data_c(), s.c_str(), sz);
143 return target_system;
153 ROS_DEBUG_NAMED(
"ftp",
"FTP:rm: SEQ(%u) SESS(%u) OPCODE(%u) RQOP(%u) SZ(%u) OFF(%u)",
154 hdr->seqNumber, hdr->session, hdr->opcode, hdr->req_opcode, hdr->size, hdr->offset);
157 return UAS_FCU(uas)->get_system_id() == target_system;
174 ROS_DEBUG_NAMED(
"ftp",
"FTP:sm: SEQ(%u) SESS(%u) OPCODE(%u) SZ(%u) OFF(%u)",
175 hdr->seqNumber, hdr->session, hdr->opcode, hdr->size, hdr->offset);
178 UAS_FCU(uas)->send_message_ignore_drop(*
this);
182 mavlink::common::
msg::FILE_TRANSFER_PROTOCOL{}
186 mavlink::common::
msg::FILE_TRANSFER_PROTOCOL{}
217 PluginBase::initialize(uas_);
307 static constexpr
int LIST_TIMEOUT_MS = 5000;
308 static constexpr
int OPEN_TIMEOUT_MS = 200;
309 static constexpr
int CHUNK_TIMEOUT_MS = 200;
312 static constexpr
size_t MAX_RESERVE_DIFF = 0x10000;
330 const uint16_t expected_seqnr = last_send_seqnr + 1;
331 if (incoming_seqnr != expected_seqnr) {
333 incoming_seqnr, expected_seqnr);
334 go_idle(
true, EILSEQ);
338 last_send_seqnr = incoming_seqnr;
344 handle_req_nack(req);
347 go_idle(
true, EBADRQC);
354 case OP::IDLE: send_reset();
break;
355 case OP::ACK: go_idle(
false);
break;
356 case OP::LIST: handle_ack_list(req);
break;
357 case OP::OPEN: handle_ack_open(req);
break;
358 case OP::READ: handle_ack_read(req);
break;
359 case OP::WRITE: handle_ack_write(req);
break;
360 case OP::CHECKSUM: handle_ack_checksum(req);
break;
363 go_idle(
true, EBADRQC);
371 auto prev_op = op_state;
377 r_errno = req.
data()[1];
392 list_directory_end();
401 ROS_ERROR_NAMED(
"ftp",
"FTP: NAK: %u Opcode: %u State: %u Errno: %d (%s)",
402 error_code, hdr->req_opcode,
enum_value(prev_op), r_errno, strerror(r_errno));
410 ROS_DEBUG_NAMED(
"ftp",
"FTP:m: ACK List SZ(%u) OFF(%u)", hdr->size, hdr->offset);
411 if (hdr->offset != list_offset) {
413 list_offset, hdr->offset);
414 go_idle(
true, EBADE);
419 uint32_t n_list_entries = 0;
421 while (off < hdr->
size) {
422 const char *ptr = req.
data_c() + off;
423 const size_t bytes_left = hdr->size - off;
425 size_t slen = strnlen(ptr, bytes_left);
429 go_idle(
true, ERANGE);
432 else if (slen == bytes_left) {
434 go_idle(
true, EOVERFLOW);
440 add_dirent(ptr, slen);
453 if (hdr->size == 0) {
455 list_directory_end();
458 ROS_ASSERT_MSG(n_list_entries > 0,
"FTP:List don't parse entries");
460 list_offset += n_list_entries;
473 ROS_INFO_NAMED(
"ftp",
"FTP:Open %s: success, session %u, size %zu",
474 open_path.c_str(), hdr->session, open_size);
475 session_file_map.insert(std::make_pair(open_path, hdr->session));
484 if (hdr->session != active_session) {
486 go_idle(
true, EBADSLT);
490 if (hdr->offset != read_offset) {
492 go_idle(
true, EBADE);
499 const size_t bytes_left = read_size - read_buffer.size();
500 const size_t bytes_to_copy = std::min<size_t>(bytes_left, hdr->size);
502 read_buffer.insert(read_buffer.end(), req.
data(), req.
data() + bytes_to_copy);
506 read_offset += bytes_to_copy;
518 if (hdr->session != active_session) {
520 go_idle(
true, EBADSLT);
524 if (hdr->offset != write_offset) {
526 go_idle(
true, EBADE);
531 const size_t bytes_written = *req.
data_u32();
534 const size_t bytes_left_before_advance = std::distance(write_it, write_buffer.end());
535 ROS_ASSERT_MSG(bytes_written <= bytes_left_before_advance,
"Bad write size");
539 std::advance(write_it, bytes_written);
541 const size_t bytes_to_copy = write_bytes_to_copy();
542 if (bytes_to_copy > 0) {
544 write_offset += bytes_written;
545 send_write_command(bytes_to_copy);
555 ROS_DEBUG_NAMED(
"ftp",
"FTP:m: ACK CalcFileCRC32 OPCODE(%u)", hdr->req_opcode);
559 ROS_DEBUG_NAMED(
"ftp",
"FTP:Checksum: success, crc32: 0x%08x", checksum_crc32);
571 void go_idle(
bool is_error_,
int r_errno_ = 0)
574 is_error = is_error_;
575 if (is_error && r_errno_ != 0) r_errno = r_errno_;
576 else if (!is_error) r_errno = 0;
583 if (!session_file_map.empty()) {
585 session_file_map.size());
586 session_file_map.clear();
591 req.
send(m_uas, last_send_seqnr);
601 req.
send(m_uas, last_send_seqnr);
626 req.
send(m_uas, last_send_seqnr);
636 req.
send(m_uas, last_send_seqnr);
642 ROS_DEBUG_STREAM_NAMED(
"ftp",
"FTP:m: kCmdWriteFile: " << active_session <<
" off: " << write_offset <<
" sz: " << bytes_to_copy);
646 std::copy(write_it, write_it + bytes_to_copy, req.
data());
647 req.
send(m_uas, last_send_seqnr);
656 std::ostringstream os;
661 std::string paths = os.str();
663 ROS_ERROR_NAMED(
"ftp",
"FTP: rename file paths is too long: %zu", paths.size());
664 r_errno = ENAMETOOLONG;
692 mavros_msgs::FileEntry ent;
696 ent.name.assign(ptr + 1, slen - 1);
697 ent.type = mavros_msgs::FileEntry::TYPE_DIRECTORY;
703 std::string name_size(ptr + 1, slen - 1);
705 auto sep_it = std::find(name_size.begin(), name_size.end(),
'\t');
706 ent.name.assign(name_size.begin(), sep_it);
707 ent.type = mavros_msgs::FileEntry::TYPE_FILE;
709 if (sep_it != name_size.end()) {
710 name_size.erase(name_size.begin(), sep_it + 1);
711 if (name_size.size() != 0)
712 ent.size = std::stoi(name_size);
718 list_entries.push_back(ent);
730 list_entries.clear();
742 if (mode == mavros_msgs::FileOpenRequest::MODE_READ)
743 send_open_ro_command();
744 else if (mode == mavros_msgs::FileOpenRequest::MODE_WRITE)
745 send_open_wo_command();
746 else if (mode == mavros_msgs::FileOpenRequest::MODE_CREATE)
747 send_create_command();
760 auto it = session_file_map.find(path);
761 if (it == session_file_map.end()) {
768 send_terminate_command(it->second);
769 session_file_map.erase(it);
780 auto it = session_file_map.find(path);
781 if (it == session_file_map.end()) {
788 active_session = it->second;
792 if (read_buffer.capacity() < len ||
793 read_buffer.capacity() > len + MAX_RESERVE_DIFF) {
795 read_buffer.reserve(len);
809 auto it = session_file_map.find(path);
810 if (it == session_file_map.end()) {
816 op_state = OP::WRITE;
817 active_session = it->second;
819 write_buffer = std::move(data);
820 write_it = write_buffer.begin();
822 send_write_command(write_bytes_to_copy());
828 send_remove_command(path);
831 bool rename_(std::string &old_path, std::string &new_path) {
833 return send_rename_command(old_path, new_path);
838 send_truncate_command(path, length);
843 send_create_dir_command(path);
848 send_remove_dir_command(path);
852 op_state = OP::CHECKSUM;
854 send_calc_file_crc32_command(path);
862 return std::min<size_t>(std::distance(write_it, write_buffer.end()),
868 std::unique_lock<std::mutex> lock(cond_mutex);
870 bool is_timedout = cond.wait_for(lock, std::chrono::milliseconds(msecs))
871 == std::cv_status::timeout;
889 #define SERVICE_IDLE_CHECK() \ 890 if (op_state != OP::IDLE) { \ 891 ROS_ERROR_NAMED("ftp", "FTP: Busy"); \ 895 bool list_cb(mavros_msgs::FileList::Request &req,
896 mavros_msgs::FileList::Response &res)
900 list_directory(req.dir_path);
901 res.success = wait_completion(LIST_TIMEOUT_MS);
902 res.r_errno = r_errno;
904 res.list = std::move(list_entries);
905 list_entries.clear();
911 bool open_cb(mavros_msgs::FileOpen::Request &req,
912 mavros_msgs::FileOpen::Response &res)
917 auto it = session_file_map.find(req.file_path);
918 if (it != session_file_map.end()) {
920 req.file_path.c_str());
924 res.success = open_file(req.file_path, req.mode);
926 res.success = wait_completion(OPEN_TIMEOUT_MS);
927 res.size = open_size;
929 res.r_errno = r_errno;
934 bool close_cb(mavros_msgs::FileClose::Request &req,
935 mavros_msgs::FileClose::Response &res)
939 res.success = close_file(req.file_path);
941 res.success = wait_completion(OPEN_TIMEOUT_MS);
943 res.r_errno = r_errno;
948 bool read_cb(mavros_msgs::FileRead::Request &req,
949 mavros_msgs::FileRead::Response &res)
953 res.success = read_file(req.file_path, req.offset, req.size);
955 res.success = wait_completion(compute_rw_timeout(req.size));
957 res.data = std::move(read_buffer);
960 res.r_errno = r_errno;
965 bool write_cb(mavros_msgs::FileWrite::Request &req,
966 mavros_msgs::FileWrite::Response &res)
970 const size_t data_size = req.data.size();
971 res.success =
write_file(req.file_path, req.offset, req.data);
973 res.success = wait_completion(compute_rw_timeout(data_size));
975 write_buffer.clear();
976 res.r_errno = r_errno;
982 mavros_msgs::FileRemove::Response &res)
986 remove_file(req.file_path);
987 res.success = wait_completion(OPEN_TIMEOUT_MS);
988 res.r_errno = r_errno;
994 mavros_msgs::FileRename::Response &res)
998 res.success = rename_(req.old_path, req.new_path);
1000 res.success = wait_completion(OPEN_TIMEOUT_MS);
1002 res.r_errno = r_errno;
1009 mavros_msgs::FileTruncate::Response &res)
1014 truncate_file(req.file_path, req.length);
1015 res.success = wait_completion(LIST_TIMEOUT_MS * 5);
1016 res.r_errno = r_errno;
1022 mavros_msgs::FileMakeDir::Response &res)
1026 create_directory(req.dir_path);
1027 res.success = wait_completion(OPEN_TIMEOUT_MS);
1028 res.r_errno = r_errno;
1033 bool rmdir_cb(mavros_msgs::FileRemoveDir::Request &req,
1034 mavros_msgs::FileRemoveDir::Response &res)
1038 remove_directory(req.dir_path);
1039 res.success = wait_completion(OPEN_TIMEOUT_MS);
1040 res.r_errno = r_errno;
1046 mavros_msgs::FileChecksum::Response &res)
1050 checksum_crc32_file(req.file_path);
1051 res.success = wait_completion(LIST_TIMEOUT_MS);
1052 res.crc32 = checksum_crc32;
1053 res.r_errno = r_errno;
1058 #undef SERVICE_IDLE_CHECK 1065 std_srvs::Empty::Response &res)
void handle_file_transfer_protocol(const mavlink::mavlink_message_t *msg, FTPRequest &req)
handler for mavlink::common::msg::FILE_TRANSFER_PROTOCOL
bool truncate_cb(mavros_msgs::FileTruncate::Request &req, mavros_msgs::FileTruncate::Response &res)
MAVROS Plugin base class.
int r_errno
store errno from server
ros::ServiceServer truncate_srv
void send_remove_command(std::string &path)
void initialize(UAS &uas_) override
Plugin initializer.
bool rename_cb(mavros_msgs::FileRename::Request &req, mavros_msgs::FileRename::Response &res)
uint8_t opcode
Command opcode.
ros::ServiceServer checksum_srv
#define ROS_INFO_NAMED(name,...)
void send_create_command()
uint8_t data[]
command data, varies by Opcode
void remove_directory(std::string &path)
#define ROS_DEBUG_STREAM_NAMED(name, args)
void send_write_command(const size_t bytes_to_copy)
bool is_error
error signaling flag (timeout/proto error)
void send(UAS *uas, uint16_t seqNumber)
Encode and send message.
void truncate_file(std::string &path, size_t length)
#define ROS_WARN_NAMED(name,...)
void handle_ack_checksum(FTPRequest &req)
uint16_t seqNumber
sequence number for message
bool write_file(std::string &path, size_t off, V_FileData &data)
uint8_t req_opcode
Request opcode returned in kRspAck, kRspNak message.
Creates file at <path> for writing, returns <session>
void send_remove_dir_command(std::string &path)
uint16_t last_send_seqnr
seqNumber for send.
void checksum_crc32_file(std::string &path)
Offset past end of file for List and Read commands.
bool remove_cb(mavros_msgs::FileRemove::Request &req, mavros_msgs::FileRemove::Response &res)
void handle_req_ack(FTPRequest &req)
#define SERVICE_IDLE_CHECK()
uint32_t offset
Offsets for List and Read commands.
bool read_file(std::string &path, size_t off, size_t len)
void list_directory_end()
bool close_file(std::string &path)
void handle_ack_read(FTPRequest &req)
static const char DIRENT_DIR
ros::ServiceServer rename_srv
FTP Request message abstraction class.
Terminates all open Read sessions.
bool read_cb(mavros_msgs::FileRead::Request &req, mavros_msgs::FileRead::Response &res)
Opens file at <path> for writing, returns <session>
FTPRequest(Opcode op, uint8_t session=0)
Subscriptions get_subscriptions() override
Return vector of MAVLink message subscriptions (handlers)
ros::ServiceServer mkdir_srv
V_FileData::iterator write_it
std::condition_variable cond
wait condvar
ros::ServiceServer remove_srv
void set_data_string(std::string &s)
Copy string to payload.
#define ROS_DEBUG_NAMED(name,...)
bool rename_(std::string &old_path, std::string &new_path)
Opens file at <path> for reading, returns <session>
uint8_t get_tgt_system()
Return communication target system.
bool open_file(std::string &path, int mode)
Truncate file at <path> to <offset> length.
Creates directory at <path>
bool write_cb(mavros_msgs::FileWrite::Request &req, mavros_msgs::FileWrite::Response &res)
bool close_cb(mavros_msgs::FileClose::Request &req, mavros_msgs::FileClose::Response &res)
Burst download session file.
#define ROS_ASSERT_MSG(cond,...)
#define UAS_FCU(uasobjptr)
helper accessor to FCU link interface
void send_any_path_command(FTPRequest::Opcode op, const std::string &debug_msg, std::string &path, uint32_t offset)
Send any command with string payload (usually file/dir path)
bool wait_completion(const int msecs)
uint32_t active_session
session id of current operation
Reads <size> bytes from <offset> in <session>
Calculate CRC32 for file at <path>
Rename <path1> to <path2>
bool open_cb(mavros_msgs::FileOpen::Request &req, mavros_msgs::FileOpen::Response &res)
ros::ServiceServer open_srv
ros::ServiceServer rmdir_srv
ErrorCode
Error codes returned in Nak response.
void send_create_dir_command(std::string &path)
bool reset_cb(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
Reset communication on both sides.
uint8_t size
Size of data.
ros::ServiceServer write_srv
All available Sessions in use.
void handle_req_nack(FTPRequest &req)
void handle_ack_write(FTPRequest &req)
Writes <size> bytes to <offset> in <session>
Command failed, errno sent back in PayloadHeader.data[1].
ros::ServiceServer read_srv
uint8_t padding[2]
32 bit aligment padding
List files in <path> from <offset>
std::vector< HandlerInfo > Subscriptions
Subscriptions vector.
ros::ServiceServer list_srv
static constexpr int compute_rw_timeout(size_t len)
uint8_t session
Session id for read and write commands.
bool rmdir_cb(mavros_msgs::FileRemoveDir::Request &req, mavros_msgs::FileRemoveDir::Response &res)
static const char DIRENT_SKIP
bool send_rename_command(std::string &old_path, std::string &new_path)
Session is not currently open.
void go_idle(bool is_error_, int r_errno_=0)
Go to IDLE mode.
void create_directory(std::string &path)
#define ROS_ERROR_NAMED(name,...)
PayloadHeader.size is invalid.
std::vector< uint8_t > V_FileData
This type used in servicies to store 'data' fileds.
void send_terminate_command(uint32_t session)
Removes Directory at <path>, must be empty.
void send_open_ro_command()
void add_dirent(const char *ptr, size_t slen)
static const char DIRENT_FILE
ros::ServiceServer reset_srv
std::vector< mavros_msgs::FileEntry > list_entries
Terminates open Read session.
bool list_cb(mavros_msgs::FileList::Request &req, mavros_msgs::FileList::Response &res)
void send_calc_file_crc32_command(std::string &path)
bool mkdir_cb(mavros_msgs::FileMakeDir::Request &req, mavros_msgs::FileMakeDir::Response &res)
static const uint8_t DATA_MAXSZ
payload.size() - header bytes
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
void send_open_wo_command()
void remove_file(std::string &path)
bool decode_valid(UAS *uas)
Decode and check target system.
uint8_t get_tgt_component()
Return communication target component.
void handle_ack_open(FTPRequest &req)
void list_directory(std::string &path)
def write_file(filename, text)
std::map< std::string, uint32_t > session_file_map
void handle_ack_list(FTPRequest &req)
size_t write_bytes_to_copy()
ros::ServiceServer close_srv
void send_truncate_command(std::string &path, size_t length)
uint8_t get_target_system_id()
constexpr std::underlying_type< _T >::type enum_value(_T e)
std::recursive_mutex mutex
bool checksum_cb(mavros_msgs::FileChecksum::Request &req, mavros_msgs::FileChecksum::Response &res)