354 lines
12 KiB
C++
354 lines
12 KiB
C++
#include "marshconnector.h"
|
|
|
|
#include "godot_cpp/classes/engine.hpp"
|
|
#include "godot_cpp/classes/font_file.hpp"
|
|
#include "godot_cpp/classes/global_constants.hpp"
|
|
#include "godot_cpp/classes/packet_peer_udp.hpp"
|
|
#include "godot_cpp/core/math.hpp"
|
|
#include "godot_cpp/core/memory.hpp"
|
|
#include "godot_cpp/core/print_string.hpp"
|
|
#include "godot_cpp/variant/basis.hpp"
|
|
#include "godot_cpp/variant/packed_byte_array.hpp"
|
|
#include "godot_cpp/variant/quaternion.hpp"
|
|
#include "godot_cpp/variant/transform3d.hpp"
|
|
#include "godot_cpp/variant/vector3.hpp"
|
|
#include "godot_cpp/variant/vector4.hpp"
|
|
#include "mavlink/all/mavlink.h" // IWYU pragma: keep; always include the mavlink.h file for selected dialect
|
|
#include "mavlink/common/mavlink_msg_command_long.h"
|
|
#include "mavlink/common/mavlink_msg_manual_control.h"
|
|
#include "mavlink/common/mavlink_msg_sim_state.h"
|
|
#include "mavlink/mavlink_helpers.h"
|
|
#include "mavlink/mavlink_types.h"
|
|
|
|
using namespace godot;
|
|
|
|
void MarshConnector::_bind_methods() {
|
|
// Data access
|
|
ClassDB::bind_method(D_METHOD("get_aircraft"), &MarshConnector::get_aircraft);
|
|
ClassDB::bind_method(D_METHOD("get_cyclic"), &MarshConnector::get_cyclic);
|
|
ClassDB::bind_method(D_METHOD("get_collective"),
|
|
&MarshConnector::get_collective);
|
|
ClassDB::bind_method(D_METHOD("get_pedals"), &MarshConnector::get_pedals);
|
|
ClassDB::bind_method(D_METHOD("get_model_connected"),
|
|
&MarshConnector::get_model_connected);
|
|
ClassDB::bind_method(D_METHOD("get_manager_connected"),
|
|
&MarshConnector::get_manager_connected);
|
|
|
|
// Timer callbacks
|
|
ClassDB::bind_method(D_METHOD("send_heartbeat"),
|
|
&MarshConnector::send_heartbeat);
|
|
ClassDB::bind_method(D_METHOD("manager_timeout"),
|
|
&MarshConnector::manager_timeout);
|
|
ClassDB::bind_method(D_METHOD("model_timeout"),
|
|
&MarshConnector::model_timeout);
|
|
}
|
|
|
|
MarshConnector::MarshConnector() {
|
|
// Initialize member variables
|
|
time_passed = 0.0;
|
|
|
|
heartbeat_timer = memnew(Timer);
|
|
add_child(heartbeat_timer);
|
|
heartbeat_timer->set_wait_time(1.0);
|
|
heartbeat_timer->set_one_shot(false);
|
|
heartbeat_timer->set_autostart(true);
|
|
heartbeat_timer->connect("timeout", Callable(this, "send_heartbeat"));
|
|
|
|
socket = memnew(PacketPeerUDP);
|
|
|
|
manager_connected = false;
|
|
|
|
manager_timer = memnew(Timer);
|
|
add_child(manager_timer);
|
|
manager_timer->set_wait_time(5.0);
|
|
manager_timer->set_one_shot(true);
|
|
manager_timer->set_autostart(false);
|
|
manager_timer->connect("timeout", Callable(this, "manager_timeout"));
|
|
|
|
model_connected = false;
|
|
|
|
model_timer = memnew(Timer);
|
|
add_child(model_timer);
|
|
model_timer->set_wait_time(1.0);
|
|
model_timer->set_one_shot(true);
|
|
model_timer->set_autostart(false);
|
|
model_timer->connect("timeout", Callable(this, "model_timeout"));
|
|
}
|
|
|
|
MarshConnector::~MarshConnector() {
|
|
// Free only manually managed member variables
|
|
}
|
|
|
|
void MarshConnector::_ready() { print_line("MarshConnector ready"); }
|
|
|
|
void MarshConnector::_process(double delta) {
|
|
time_passed += delta;
|
|
|
|
if (!socket->is_socket_connected()) {
|
|
socket->connect_to_host("127.0.0.1", 24400);
|
|
} else {
|
|
while (socket->get_available_packet_count() > 0) {
|
|
const PackedByteArray data = socket->get_packet();
|
|
receive_data(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
Error MarshConnector::send_heartbeat() {
|
|
// print_line("Sending HEARTBEAT at ", time_passed, " seconds");
|
|
|
|
mavlink_heartbeat_t heartbeat;
|
|
heartbeat.type = MAV_TYPE_HELICOPTER;
|
|
heartbeat.autopilot = MAV_AUTOPILOT_INVALID;
|
|
heartbeat.base_mode = 0; // none of the flags applicable
|
|
heartbeat.custom_mode = 0; // not used so far
|
|
heartbeat.system_status = MAV_STATE_ACTIVE;
|
|
|
|
mavlink_message_t message;
|
|
mavlink_msg_heartbeat_encode_chan(1, MARSH_COMP_ID_VISUALISATION,
|
|
MAVLINK_COMM_0, &message, &heartbeat);
|
|
return send_message(message);
|
|
}
|
|
|
|
Error MarshConnector::send_message(mavlink_message_t message) {
|
|
uint8_t send_buffer[MAVLINK_MAX_PACKET_LEN];
|
|
mavlink_msg_to_send_buffer(send_buffer, &message);
|
|
|
|
PackedByteArray array;
|
|
for (int i = 0; i < sizeof(send_buffer); i++) {
|
|
array.append(send_buffer[i]);
|
|
}
|
|
|
|
const auto result = socket->put_packet(array);
|
|
return result;
|
|
}
|
|
|
|
void MarshConnector::receive_data(const PackedByteArray &data) {
|
|
mavlink_message_t message;
|
|
mavlink_status_t parser_status;
|
|
|
|
for (size_t i = 0; i < data.size(); i++) {
|
|
|
|
if (mavlink_parse_char(MAVLINK_COMM_0, data.ptr()[i], &message,
|
|
&parser_status)) {
|
|
switch (message.msgid) {
|
|
case MAVLINK_MSG_ID_SIM_STATE:
|
|
handle_sim_state(message);
|
|
break;
|
|
case MAVLINK_MSG_ID_LOCAL_POSITION_NED:
|
|
handle_local_position(message);
|
|
break;
|
|
case MAVLINK_MSG_ID_ATTITUDE:
|
|
handle_attitude(message);
|
|
break;
|
|
case MAVLINK_MSG_ID_PARAM_REQUEST_READ:
|
|
case MAVLINK_MSG_ID_PARAM_REQUEST_LIST:
|
|
case MAVLINK_MSG_ID_PARAM_SET:
|
|
handle_param(message);
|
|
break;
|
|
case MAVLINK_MSG_ID_MANUAL_CONTROL:
|
|
handle_manual_control(message);
|
|
break;
|
|
case MAVLINK_MSG_ID_HEARTBEAT:
|
|
handle_heartbeat(message);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MarshConnector::receive_model_data() {
|
|
if (!model_connected) {
|
|
print_line("Started receiving flight model data");
|
|
}
|
|
|
|
model_connected = true;
|
|
model_timer->start();
|
|
}
|
|
|
|
void MarshConnector::model_timeout() {
|
|
print_line("Stopped receiving flight model data");
|
|
model_connected = false;
|
|
}
|
|
|
|
void MarshConnector::receive_location(Vector3 location) {
|
|
last_location = location;
|
|
receive_model_data();
|
|
}
|
|
|
|
void MarshConnector::receive_rotation(Quaternion rotation) {
|
|
last_rotation = rotation;
|
|
receive_model_data();
|
|
}
|
|
|
|
Error MarshConnector::set_parameter(const String &id, float value) {
|
|
print_line("Implement set_parameter");
|
|
return ERR_METHOD_NOT_FOUND;
|
|
}
|
|
|
|
float MarshConnector::get_parameter(const String &id) {
|
|
print_line("Implement get_parameter");
|
|
return Math_NAN;
|
|
}
|
|
|
|
Transform3D MarshConnector::get_aircraft() {
|
|
return Transform3D{Basis{last_rotation}, last_location};
|
|
}
|
|
|
|
Vector2 MarshConnector::get_cyclic() {
|
|
return Vector2{last_controls.x, last_controls.y};
|
|
}
|
|
|
|
float MarshConnector::get_collective() { return last_controls.w; }
|
|
|
|
float MarshConnector::get_pedals() { return last_controls.z; }
|
|
|
|
bool MarshConnector::get_manager_connected() { return manager_connected; }
|
|
|
|
bool MarshConnector::get_model_connected() { return model_connected; }
|
|
|
|
Vector2 MarshConnector::local_meters_from_global_degrees(double latitude,
|
|
double longitude) {
|
|
|
|
// Convert everything to radians
|
|
const double lat = latitude * Math_PI / 180.0;
|
|
const double lon = longitude * Math_PI / 180.0;
|
|
const double lat0 = parameters[LOCAL_FRAME_LAT] * Math_PI / 180.0;
|
|
const double lon0 = parameters[LOCAL_FRAME_LON] * Math_PI / 180.0;
|
|
|
|
double EARTH_RADIUS = 6371008.7714; // mean radius for WGS-84, in meters
|
|
|
|
// X North is just length along the meridian
|
|
double x = (lat - lat0) * EARTH_RADIUS;
|
|
// Y East is just length along the circle of latitude
|
|
double y = (lon - lon0) * (EARTH_RADIUS * cos(lat0));
|
|
|
|
return Vector2(x, y);
|
|
}
|
|
void MarshConnector::handle_sim_state(mavlink_message_t message) {
|
|
if (message.msgid != MAVLINK_MSG_ID_SIM_STATE)
|
|
return;
|
|
|
|
mavlink_sim_state_t sim_state;
|
|
mavlink_msg_sim_state_decode(&message, &sim_state);
|
|
const double lat =
|
|
sim_state.lat_int != 0 ? sim_state.lat_int / 1.0e7 : sim_state.lat;
|
|
const double lon =
|
|
sim_state.lon_int != 0 ? sim_state.lon_int / 1.0e7 : sim_state.lon;
|
|
|
|
const Vector2 local_position = local_meters_from_global_degrees(lat, lon);
|
|
|
|
receive_location(godot_location_from_mavlink(
|
|
local_position.x, local_position.y, sim_state.alt));
|
|
receive_rotation(godot_rotation_from_mavlink(sim_state.roll, sim_state.pitch,
|
|
sim_state.yaw));
|
|
}
|
|
Vector3 MarshConnector::godot_location_from_mavlink(float north_meters,
|
|
float east_meters,
|
|
float alt_meters) {
|
|
// See Godot documentation for axis conventions:
|
|
// https://docs.godotengine.org/en/stable/classes/class_vector3.html#constants
|
|
return Vector3{
|
|
east_meters, // East is RIGHT = Vector3(1, 0, 0)
|
|
alt_meters, // Up is UP = Vector3(0, 1, 0)
|
|
north_meters, // North is FORWARD = Vector3(0, 0, -1)
|
|
};
|
|
}
|
|
Quaternion MarshConnector::godot_rotation_from_mavlink(float roll_rad,
|
|
float pitch_rad,
|
|
float yaw_rad) {
|
|
// Checked manually in Godot editor
|
|
const Vector3 euler = Vector3{
|
|
-pitch_rad,
|
|
-yaw_rad,
|
|
roll_rad,
|
|
};
|
|
return Quaternion::from_euler(euler);
|
|
}
|
|
|
|
void MarshConnector::handle_local_position(mavlink_message_t message) {
|
|
if (message.msgid != MAVLINK_MSG_ID_LOCAL_POSITION_NED)
|
|
return;
|
|
|
|
mavlink_local_position_ned_t local_pos;
|
|
mavlink_msg_local_position_ned_decode(&message, &local_pos);
|
|
receive_location(
|
|
godot_location_from_mavlink(local_pos.x, local_pos.y, -local_pos.z));
|
|
}
|
|
|
|
void MarshConnector::handle_attitude(mavlink_message_t message) {
|
|
if (message.msgid != MAVLINK_MSG_ID_ATTITUDE)
|
|
return;
|
|
|
|
mavlink_attitude_t attitude;
|
|
mavlink_msg_attitude_decode(&message, &attitude);
|
|
receive_rotation(
|
|
godot_rotation_from_mavlink(attitude.roll, attitude.pitch, attitude.yaw));
|
|
}
|
|
|
|
void MarshConnector::handle_param(mavlink_message_t message) {
|
|
print_line("Implement handle_param");
|
|
}
|
|
|
|
void MarshConnector::handle_manual_control(mavlink_message_t message) {
|
|
if (message.msgid != MAVLINK_MSG_ID_MANUAL_CONTROL)
|
|
return;
|
|
|
|
mavlink_manual_control_t manual_control;
|
|
mavlink_msg_manual_control_decode(&message, &manual_control);
|
|
const int16_t invalid = 0x7FFF;
|
|
if (manual_control.y != invalid)
|
|
last_controls.x = manual_control.y / 1000.0f;
|
|
if (manual_control.x != invalid)
|
|
last_controls.y = manual_control.x / -1000.0f;
|
|
if (manual_control.r != invalid)
|
|
last_controls.z = manual_control.r / 1000.0f;
|
|
if (manual_control.z != invalid)
|
|
last_controls.w = manual_control.z / 1000.0f;
|
|
}
|
|
|
|
void MarshConnector::handle_heartbeat(mavlink_message_t message) {
|
|
if (message.msgid != MAVLINK_MSG_ID_HEARTBEAT)
|
|
return;
|
|
|
|
// mavlink_heartbeat_t heartbeat;
|
|
// mavlink_msg_heartbeat_decode(&message, &heartbeat);
|
|
if (message.compid == MARSH_COMP_ID_MANAGER) {
|
|
if (!manager_connected) {
|
|
print_line("Connected to MARSH Manager");
|
|
|
|
// subscribe to messages not sent to visualisation node by default
|
|
mavlink_command_long_t command;
|
|
command.target_system = 1;
|
|
command.target_component = MARSH_COMP_ID_MANAGER;
|
|
command.command = MAV_CMD_SET_MESSAGE_INTERVAL;
|
|
command.confirmation = 0;
|
|
command.param1 = static_cast<float>(MAVLINK_MSG_ID_MANUAL_CONTROL);
|
|
command.param2 = 0; // Default rate
|
|
command.param3 = 0; // Not used
|
|
command.param4 = 0;
|
|
command.param5 = 0;
|
|
command.param6 = 0;
|
|
command.param7 = 1; // Address of requestor
|
|
|
|
mavlink_message_t message_sent;
|
|
mavlink_msg_command_long_encode_chan(1, MARSH_COMP_ID_VISUALISATION,
|
|
MAVLINK_COMM_0, &message_sent,
|
|
&command);
|
|
Error result = send_message(message_sent);
|
|
if (result != OK) {
|
|
print_line("Subscribe send result ", result);
|
|
}
|
|
}
|
|
|
|
manager_connected = true;
|
|
manager_timer->start();
|
|
}
|
|
}
|
|
|
|
void MarshConnector::manager_timeout() {
|
|
print_line("Lost connection to MARSH Manager");
|
|
manager_connected = false;
|
|
}
|