FlightAxis Link Q&A

Jeremy Babbar-Sebens

Administrator
Staff member
How do I connect my Flight Controller?
The FlightAxis toolkit enables Hardware- and Software-in-the-Loop testing for flight controllers.
The toolkit allows RealFlight to communicate using SOAP. Using the ExchangeData SOAP method, the flight controller sends a control vector to RealFlight. In response, RealFlight sends a state vector. After processing, the controller sends an updated control vector and receives a state update, forming a feedback loop as this cycle repeats. A flowchart is provided at the end of this document.

What state data is sent?
The state data sent by RealFlight includes:
Simulation time (s)
Airspeed (m/s)
Altitude, ASL (m)
Altitude, AGL (m)
Ground Speed, (m/s)
Pitch, Roll, Yaw rates (deg/s)
Orientation (Azimuth, Inclination, Roll; deg)
Orientation (quaternion)
Position (x, y, z; m) - can be converted to Latitude and Longitude
Velocity, world-referenced (x,y,z; m/s)
Velocity, body-referenced (x,y,z; m/s)
Acceleration, world-referenced (x,y,z; m/s^2)
Acceleration, body-referenced (x,y,z; m/s^2)
Wind velocity (x,y,z; m/s)
Propeller RPM
Main Rotor RPM
Battery Voltage (V)
Battery Current (A)
Remaining Battery Capacity (mAh)
Fuel Remaining (oz)
Is Locked (bool)
Has Lost Components (bool)
Engine is Running (bool)
Is Touching Ground (bool)
Current Status

How do I send control data?
The plugin accepts up to 8 channels of servo input, scaled from -1 to 1.
Additional SOAP methods are included to:
Reset the simulation
Allow external input
Return manual control to the RealFlight transmitter

What kind of performance can I expect?
RealFlight’s performance will be dependent upon the system used to run it, and the communication frame rate corresponds to the overall performance. On an i7-equipped desktop with a mid-range graphics card, we can reliably operate at 200Hz or better.

Can I simulate my own vehicle?
Yes! Creating a model for RealFlight has two stages: visuals and physics. The visual model is built using our KEMax toolchain (http://www.knifeedge.com/KEmax/). Vehicle physics are defined using RealFlight's internal Vehicle Editor. By specifying the geometry, mass, etc. of the various aircraft components, you create a predictive model of the vehicle’s behavior.

Can Knife Edge Software help me make a simulated vehicle?
Absolutely! We can provide consulting services to assist you in learning this process, or even to create your vehicle for you.

Do I need flight test or wind tunnel data?
No - the FlightAxis physics system is predictive, and simulates each component of the vehicle in every simulation cycle. We find that a simulated model made from careful measurements will match the flight characteristics of the physical model.

Can the vehicle change configuration in flight?
Yes. Tilt-rotor, tilt-wing, and other variable geometries are handled gracefully by the FlightAxis component system. Since each component is simulated in real time, any configuration changes are automatically included in physics calculations.

I have my own Flight Dynamics Model. Can I use RealFlight as a visualization engine only?
Not at this time. In the current generation, the FDM and visualization are inseparable. Future versions may be capable of this, but we do not have a release date at this time.

Can I use RealFlight for visualization of telemetry logs?
Not directly (see the question above). However, RealFlight can record simulated flights internally, allowing playback within the simulator. This will not be synchronized with your telemetry data, unfortunately.

Can I use my own terrain?
Not in the current version. RealFlight uses real terrain data from the Sierra Nevada region in Southern Spain. This makes it possible to test terrain-dependent software, just not at your home range (unless you happen to be operating within that region).

Hardware/Software-in-the-Loop flowchart
The following flowchart shows the basic structure for HIL/SITL testing with an external controller. These are the steps you will need to incorporate into your software in order to test against the RealFlight FDM and terrain.
page1_pic4.jpg


Is there an existing implementation?
Yes. The ArduPilot project has a standing implementation that works well. Please see tridge's post here for links to the code and a sample packet.

Is there formal documentation?
We will provide additional documentation here as soon as possible.
 
Last edited:
How do I connect my Flight Controller?
The FlightAxis toolkit enables Hardware- and Software-in-the-Loop testing for flight controllers.
The toolkit allows RealFlight to communicate using SOAP. Using the ExchangeData SOAP method, the flight controller sends a control vector to RealFlight. In response, RealFlight sends a state vector. After processing, the controller sends an updated control vector and receives a state update, forming a feedback loop as this cycle repeats. A flowchart is provided at the end of this document.

What state data is sent?
The state data sent by RealFlight includes:
Simulation time (s)
Airspeed (m/s)
Altitude, ASL (m)
Altitude, AGL (m)
Ground Speed, (m/s)
Pitch, Roll, Yaw rates (deg/s)
Orientation (Azimuth, Inclination, Roll; deg)
Orientation (quaternion)
Position (x, y, z; m) - can be converted to Latitude and Longitude
Velocity, world-referenced (x,y,z; m/s)
Velocity, body-referenced (x,y,z; m/s)
Acceleration, world-referenced (x,y,z; m/s^2)
Acceleration, body-referenced (x,y,z; m/s^2)
Wind velocity (x,y,z; m/s)
Propeller RPM
Main Rotor RPM
Battery Voltage (V)
Battery Current (A)
Remaining Battery Capacity (mAh)
Fuel Remaining (oz)
Is Locked (bool)
Has Lost Components (bool)
Engine is Running (bool)
Is Touching Ground (bool)
Current Status

How do I send control data?
The plugin accepts up to 8 channels of servo input, scaled from -1 to 1.
Additional SOAP methods are included to:
Reset the simulation
Allow external input
Return manual control to the RealFlight transmitter

What kind of performance can I expect?
RealFlight’s performance will be dependent upon the system used to run it, and the communication frame rate corresponds to the overall performance. On an i7-equipped desktop with a mid-range graphics card, we can reliably operate at 200Hz or better.

Can I simulate my own vehicle?
Yes! Creating a model for RealFlight has two stages: visuals and physics. The visual model is built using our KEMax toolchain (http://www.knifeedge.com/KEmax/). Vehicle physics are defined using RealFlight's internal Vehicle Editor. By specifying the geometry, mass, etc. of the various aircraft components, you create a predictive model of the vehicle’s behavior.

Can Knife Edge Software help me make a simulated vehicle?
Absolutely! We can provide consulting services to assist you in learning this process, or even to create your vehicle for you.

Do I need flight test or wind tunnel data?
No - the FlightAxis physics system is predictive, and simulates each component of the vehicle in every simulation cycle. We find that a simulated model made from careful measurements will match the flight characteristics of the physical model.

Can the vehicle change configuration in flight?
Yes. Tilt-rotor, tilt-wing, and other variable geometries are handled gracefully by the FlightAxis component system. Since each component is simulated in real time, any configuration changes are automatically included in physics calculations.

I have my own Flight Dynamics Model. Can I use RealFlight as a visualization engine only?
Not at this time. In the current generation, the FDM and visualization are inseparable. Future versions may be capable of this, but we do not have a release date at this time.

Can I use RealFlight for visualization of telemetry logs?
Not directly (see the question above). However, RealFlight can record simulated flights internally, allowing playback within the simulator. This will not be synchronized with your telemetry data, unfortunately.

Can I use my own terrain?
Not in the current version. RealFlight uses real terrain data from the Sierra Nevada region in Southern Spain. This makes it possible to test terrain-dependent software, just not at your home range (unless you happen to be operating within that region).

Hardware/Software-in-the-Loop flowchart
The following flowchart shows the basic structure for HIL/SITL testing with an external controller. These are the steps you will need to incorporate into your software in order to test against the RealFlight FDM and terrain.
page1_pic4.jpg


Is there an existing implementation?
Yes. The ArduPilot project has a standing implementation that works well. Please see tridge's post here for links to the code and a sample packet.

Is there formal documentation?
We will provide additional documentation here as soon as possible.
Hi Jeremy,

Thanks for the explanations. Currently I am working my through controlling RF with a Python Script. I was wondering if the formal documentation that was pointed out in the last paragraph is available now and I just overlooked it? Especially, which SOAP Requests are possible and how to formulate them would be really beneficial.

Thanks in Advance.
 
Hi,

Thanks for the clarifications. I was also wondering if documentation was now available for Python scripts in particular?

Thanks in advance.
 
Hi,

Could you update the link to the post by tridge, and add the HIL/SIL flowchart image back in? I'm interested in working on the autonomous flight controller portion, and want to work on a matlab and/or python interface

Thanks
 
Since nobody on here has copy-pasted a simple code snippet to retrieve the angular orientation data from an aircraft whilst flying in RF9, I'll do it, somebody has to do it-*looks up at the sky* I am the chosen one. The code right now just has placeholder controller input values set, you will have to change these to work with whatever controller you're using or algorithm. Use ChatGPT to figure that part out. But, if you just want to see the angles change while you fly a plane with ur regular spektrum controller, or whatever controller you have connected, you can do the following:

1.) load up realflight, select aircraft
2.) execute my python script
3.) select to change the aircraft youre flying

now you should be able to fly using ur controller, i dont know why but that works for me. it like stops accepting the input dummy values from my script and starts accepting regular input values, Also the naming within the script is swapped for roll and pitch but its correct in the terminal where ya see it printed.

AND HERES THE SACRED GOLDEN LINK TO MY SCRIPT:


you're welcome.

And here it is too if the link ever gets fricked:

Code:
#original link where all this was found: https://github.com/camdeno/F16Capstone/blob/main/FlightAxis/flightaxis.py

import time
import math
import signal
import sys
import csv
from datetime import datetime

import pygame
import requests
import navpy
from bs4 import BeautifulSoup
from pymavlink import mavutil
import numpy as np
import quaternionic


class FlightAxis:
    """
    Python version of Ardupilot's FlightAxis implementation, available here:
    - https://github.com/ArduPilot/ardupilot/blob/master/libraries/SITL/SIM_FlightAxis.h
    - https://github.com/ArduPilot/ardupilot/blob/master/libraries/SITL/SIM_FlightAxis.cpp

    Example of FlightAxis response is here: http://uav.tridgell.net/RealFlight/data-exchange.txt

    More details about FlightAxis interface:
    - https://www.knifeedge.com/forums/index.php?threads/flightaxis-link-q-a.32854/
    - https://www.knifeedge.com/forums/index.php?threads/flightaxis-link-network-access-to-realflight-for-developers.32809/#post-282152


    # Performance
    A typical performance measured on a reasonable desktop PC is:
    avg dt = 0.0042965165828869264[s], avg Freq = 232.746686928432

    # Workflow:
    1. get HIL_ACTUATOR_CONTROLS from PX4
    2. exchange data with flight axis
    3. send HIL_SENSOR and HIL_GPS to PX4

    """
    REALFLIGHT_URL = "http://127.0.0.1:18083"
    RC_CHANNLES = 12
    TIMEOUT_S = 0.05

    LAT_0 = 45.25608036722125 # [deg]
    LON_0 = -122.84116451943059 # [deg]
    ALT_0 = 36 # [m]

    # Tuples with mapping values
    # (joystick_channel_idx, RC_IN channel idx, invert?)
    # Incoming:
    # rc_in idx -> SOAP order
    # 0 -> yaw (3)
    # 1 -> throttle (2)
    # 2 -> roll (0) # invert
    # 3 -> pitch (1)

    # Desired:
    # 0 -> roll
    # 1 -> pitch
    # 2 -> yaw
    # 3 -> throttle

    #Throttle idx 3
    # Rudder idx 2
    # Roll idx 0
    # Pitch ?
    YAW_IDX = (0,3,True)
    THROTTLE_IDX = (1,2,False)
    ROLL_IDX = (2,0,True)
    PITCH_IDX = (3,1,False)
    GEAR_IDX = (4,6,False)
    MODE_IDX = (5,7,False)
    CHANNELS = [YAW_IDX, THROTTLE_IDX, ROLL_IDX, PITCH_IDX, GEAR_IDX, MODE_IDX]

    def quaternion_to_euler(self):
        w, x, y, z = self.quats_as_list  # Assuming quats_as_list is [w, x, y, z]

        # Roll (x-axis rotation)
        sinr_cosp = 2 * (w * x + y * z)
        cosr_cosp = 1 - 2 * (x * x + y * y)
        roll = np.arctan2(sinr_cosp, cosr_cosp)

        # Pitch (y-axis rotation)
        sinp = 2 * (w * y - z * x)
        pitch = np.arcsin(sinp) if abs(sinp) <= 1 else np.pi / 2 * np.sign(sinp)

        # Yaw (z-axis rotation)
        siny_cosp = 2 * (w * z + x * y)
        cosy_cosp = 1 - 2 * (y * y + z * z)
        yaw = np.arctan2(siny_cosp, cosy_cosp)

        # Convert from radians to degrees and then to integers
        roll_deg = int(np.degrees(roll))
        pitch_deg = int(np.degrees(pitch))
        yaw_deg = int(np.degrees(yaw))

        return roll_deg, pitch_deg, yaw_deg


    def getHeader(self) -> list:
        header = [key for key in self.getKeytable()] + [f"rc_channel_{idx}" for idx in range(FlightAxis.RC_CHANNLES)]
        return header

    def getKeytable(self) -> dict:
        keytable = {
            "m-airspeed-MPS": "self.m_airspeed_MPS",
            "m-altitudeASL-MTR": "self.m_altitudeASL_MTR",
            "m-altitudeAGL-MTR": "self.m_altitudeAGL_MTR",
            "m-groundspeed-MPS": "self.m_groundspeed_MPS",
            "m-pitchRate-DEGpSEC": "self.m_pitchRate_DEGpSEC",
            "m-rollRate-DEGpSEC": "self.m_rollRate_DEGpSEC",
            "m-yawRate-DEGpSEC": "self.m_yawRate_DEGpSEC",
            "m-azimuth-DEG": "self.m_azimuth_DEG",
            "m-inclination-DEG": "self.m_inclination_DEG",
            "m-roll-DEG": "self.m_roll_DEG",
            "m-aircraftPositionX-MTR": "self.m_aircraftPositionX_MTR",
            "m-aircraftPositionY-MTR": "self.m_aircraftPositionY_MTR",
            "m-velocityWorldU-MPS": "self.m_velocityWorldU_MPS",
            "m-velocityWorldV-MPS": "self.m_velocityWorldV_MPS",
            "m-velocityWorldW-MPS": "self.m_velocityWorldW_MPS",
            "m-velocityBodyU-MPS": "self.m_velocityBodyU_MPS",
            "m-velocityBodyV-MPS": "self.m_velocityBodyV_MPS",
            "m-velocityBodyW-MPS": "self.m_velocityBodyW_MPS",
            "m-accelerationWorldAX-MPS2": "self.m_accelerationWorldAX_MPS2",
            "m-accelerationWorldAY-MPS2": "self.m_accelerationWorldAY_MPS2",
            "m-accelerationWorldAZ-MPS2": "self.m_accelerationWorldAZ_MPS2",
            "m-accelerationBodyAX-MPS2": "self.m_accelerationBodyAX_MPS2",
            "m-accelerationBodyAY-MPS2": "self.m_accelerationBodyAY_MPS2",
            "m-accelerationBodyAZ-MPS2": "self.m_accelerationBodyAZ_MPS2",
            "m-windX-MPS": "self.m_windX_MPS",
            "m-windY-MPS": "self.m_windY_MPS",
            "m-windZ-MPS": "self.m_windZ_MPS",
            "m-propRPM": "self.m_propRPM",
            "m-heliMainRotorRPM": "self.m_heliMainRotorRPM",
            "m-batteryVoltage-VOLTS": "self.m_batteryVoltage_VOLTS",
            "m-batteryCurrentDraw-AMPS": "self.m_batteryCurrentDraw_AMPS",
            "m-batteryRemainingCapacity-MAH": "self.m_batteryRemainingCapacity_MAH",
            "m-fuelRemaining-OZ": "self.m_fuelRemaining_OZ",
            "m-isLocked": "self.m_isLocked",
            "m-hasLostComponents": "self.m_hasLostComponents",
            "m-anEngineIsRunning": "self.m_anEngineIsRunning",
            "m-isTouchingGround": "self.m_isTouchingGround",
            # TODO: needs better string handling here
            # "m-currentAircraftStatus": "self.m_currentAircraftStatus",
            "m-currentPhysicsTime-SEC": "self.m_currentPhysicsTime_SEC",
            "m-currentPhysicsSpeedMultiplier": "self.m_currentPhysicsSpeedMultiplier",
            "m-orientationQuaternion-X": "self.m_orientationQuaternion_X",
            "m-orientationQuaternion-Y": "self.m_orientationQuaternion_Y",
            "m-orientationQuaternion-Z": "self.m_orientationQuaternion_Z",
            "m-orientationQuaternion-W": "self.m_orientationQuaternion_W",
        }
        return keytable

    def __init__(self):
        self.last_frame_time = time.time()
        self.avg_dt = 0.0

        # Default position
        self.lat = FlightAxis.LAT_0
        self.lon = FlightAxis.LON_0

        self.rcin = [0.0 for _ in range(FlightAxis.RC_CHANNLES)]
        self.m_airspeed_MPS = 0.0
        self.m_altitudeASL_MTR = 0.0
        self.m_altitudeAGL_MTR = 0.0
        self.m_groundspeed_MPS = 0.0
        self.m_pitchRate_DEGpSEC = 0.0
        self.m_rollRate_DEGpSEC = 0.0
        self.m_yawRate_DEGpSEC = 0.0
        self.m_azimuth_DEG = 0.0
        self.m_inclination_DEG = 0.0
        self.m_roll_DEG = 0.0
        self.m_aircraftPositionX_MTR = 0.0
        self.m_aircraftPositionY_MTR = 0.0
        self.m_velocityWorldU_MPS = 0.0
        self.m_velocityWorldV_MPS = 0.0
        self.m_velocityWorldW_MPS = 0.0
        self.m_velocityBodyU_MPS = 0.0
        self.m_velocityBodyV_MPS = 0.0
        self.m_velocityBodyW_MPS = 0.0
        self.m_accelerationWorldAX_MPS2 = 0.0
        self.m_accelerationWorldAY_MPS2 = 0.0
        self.m_accelerationWorldAZ_MPS2 = 0.0
        self.m_accelerationBodyAX_MPS2 = 0.0
        self.m_accelerationBodyAY_MPS2 = 0.0
        self.m_accelerationBodyAZ_MPS2 = 0.0
        self.m_windX_MPS = 0.0
        self.m_windY_MPS = 0.0
        self.m_windZ_MPS = 0.0
        self.m_propRPM = 0.0
        self.m_heliMainRotorRPM = 0.0
        self.m_batteryVoltage_VOLTS = 0.0
        self.m_batteryCurrentDraw_AMPS = 0.0
        self.m_batteryRemainingCapacity_MAH = 0.0
        self.m_fuelRemaining_OZ = 0.0
        self.m_isLocked = 0.0
        self.m_hasLostComponents = 0.0
        self.m_anEngineIsRunning = 0.0
        self.m_isTouchingGround = 0.0
        self.m_currentAircraftStatus = "None"
        self.m_currentPhysicsTime_SEC = 0.0
        self.m_currentPhysicsSpeedMultiplier = 0.0
        self.m_orientationQuaternion_X = 0.0
        self.m_orientationQuaternion_Y = 0.0
        self.m_orientationQuaternion_Z = 0.0
        self.m_orientationQuaternion_W = 0.0
        self.m_flightAxisControllerIsActive = False
        self.m_resetButtonHasBeenPressed = False
        self.rows = []  # Initialize the rows attribute as an empty list

    def enableRC(self, doPrint=False) -> bool:
        """
        Set Spektrum as the RC input
        Return True if the response was OK
        """
        headers = {'content-type': "text/xml;charset='UTF-8'",
                'soapaction': 'RestoreOriginalControllerDevice',
                'Connection': 'Keep-Alive'}

        body = "<?xml version='1.0' encoding='UTF-8'?>\
        <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>\
        <soap:Body>\
        <RestoreOriginalControllerDevice><a>1</a><b>2</b></RestoreOriginalControllerDevice>\
        </soap:Body>\
        </soap:Envelope>"

        response = requests.post(FlightAxis.REALFLIGHT_URL,data=body,headers=headers)
        if doPrint:
            print(response.content)
        return response.ok

    def resetSim(self, doPrint=False) -> bool:
        """
        Reset Real Flight simulator,
        per post here: https://www.knifeedge.com/forums/index.php?threads/realflight-reset-soap-envelope.52333/
        NOTE: untested
        """
        headers = {'content-type': "text/xml;charset='UTF-8'",
                   'soapaction': 'ResetAircraft'}
        body = "<?xml version='1.0' encoding='UTF-8'?>\
        <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>\
        <soap:Body>\
        <ResetAircraft><a>1</a><b>2</b></ResetAircraft>\
        </soap:Body>\
        </soap:Envelope>"

    def disableRC(self, doPrint=False) -> bool:
        """
        Disable Spektrum as the RC input, and use FlightAxis instead
        Return True if the response was OK
        """
        headers = {'content-type': "text/xml;charset='UTF-8'",
                'soapaction': 'InjectUAVControllerInterface'}

        body = "<?xml version='1.0' encoding='UTF-8'?>\
        <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>\
        <soap:Body>\
        <InjectUAVControllerInterface><a>1</a><b>2</b></InjectUAVControllerInterface>\
        </soap:Body>\
        </soap:Envelope>"

        response = requests.post(FlightAxis.REALFLIGHT_URL, data=body,headers=headers)
        if doPrint:
            print(response.content)
        return response.ok

    def scaleJoystickValue(self, value, invert=False) -> float:
        # Condition channel
        #     # -0.66..0.66 to 0-1
        # Interval scaling from https://stats.stackexchange.com/a/281164
        R_MIN=-0.66
        R_MAX=0.66
        T_MIN=0.0
        T_MAX=1.0

        k = 1.0
        if invert:
            k = -1.0
        scaled_value = (k*value-R_MIN)/(R_MAX-R_MIN)*(T_MAX-T_MIN)+T_MIN
        if scaled_value < 0.0:
            scaled_value = 0.0
        elif scaled_value > 1.0:
            scaled_value = 1.0
        return scaled_value

    def updateActuators(self,values):
        for channel in FlightAxis.CHANNELS:
            joy_idx, rc_idx, channelInverted = channel
            self.rcin[rc_idx] = self.scaleJoystickValue(values[joy_idx],invert=channelInverted)

    def getStates(self, doPrint=False) -> bool:
        """
        Set the control inputs, and get the states
        Return True if the response was OK
        """
        headers = {'content-type': "text/xml;charset='UTF-8'",
                'soapaction': 'ExchangeData'}

        body = f"<?xml version='1.0' encoding='UTF-8'?><soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>\
        <soap:Body>\
        <ExchangeData>\
        <pControlInputs>\
        <m-selectedChannels>4095</m-selectedChannels>\
        <m-channelValues-0to1>\
        <item>{self.rcin[0]}</item>\
        <item>{self.rcin[1]}</item>\
        <item>{self.rcin[2]}</item>\
        <item>{self.rcin[3]}</item>\
        <item>{self.rcin[4]}</item>\
        <item>{self.rcin[5]}</item>\
        <item>{self.rcin[6]}</item>\
        <item>{self.rcin[7]}</item>\
        <item>{self.rcin[8]}</item>\
        <item>{self.rcin[9]}</item>\
        <item>{self.rcin[10]}</item>\
        <item>{self.rcin[11]}</item>\
        </m-channelValues-0to1>\
        </pControlInputs>\
        </ExchangeData>\
        </soap:Body>\
        </soap:Envelope>"

        response = requests.post(FlightAxis.REALFLIGHT_URL,data=body,headers=headers)

        if doPrint:
            print(response.content)

        if response.ok:
            now = time.time()
            dt = now - self.last_frame_time
            self.avg_dt = (self.avg_dt + dt)/2.0
            self.last_frame_time = now
            self.parseResponse(response.content)
            return True
        # Catch all returns false
        return False

    def parseResponse(self, xml):
        """
        Update internal states from FlightAxis response
        """
        parsed = BeautifulSoup(xml, 'xml')
        body = parsed.find('m-aircraftState')
        assert(body)

        self.row = []
        # Use keytable for rows
        keytable = self.getKeytable()
        for k,v in keytable.items():
            tag = body.find(k)
            tag_val = tag.string
            if tag_val == "false" or tag_val == "true":
                tag_val = tag_val.capitalize()
            cmd = f"{v} = {tag_val}"
            exec(cmd)
            cmd = f"self.row.append({tag_val})"
            exec(cmd)

        # Add actuators
        for idx in range(FlightAxis.RC_CHANNLES):
            self.row.append(self.rcin[idx])

        # Actually append the rows
        self.rows.append(self.row)

        # Update attitude
        self.quats_as_list = [self.m_orientationQuaternion_W,
                              self.m_orientationQuaternion_X,
                              self.m_orientationQuaternion_Y,
                              self.m_orientationQuaternion_Z]

        roll, pitch, yaw = self.quaternion_to_euler()
        # Formatting the output
        print(f"Roll: {pitch:5d}, Pitch: {roll:5d}, Yaw: {yaw:5d}")

        # TODO: is the order of the axis correct?
        self.quats = quaternionic.array(self.quats_as_list)
        #print(f"{self.quats_as_list}")
        # Update position
        ned = [self.m_aircraftPositionX_MTR,
              self.m_aircraftPositionY_MTR,
              self.m_altitudeAGL_MTR]
        self.lat,self.lon, _ = navpy.ned2lla(ned, FlightAxis.LAT_0, FlightAxis.LON_0, FlightAxis.ALT_0,
                                    latlon_unit='deg', alt_unit='m', model='wgs84')

    def getHilActuatorControls(self) -> bool:
        """
        Assign dummy values to actuator controls using the CHANNELS mapping.
        Returns True always as the message reception is simulated.
        """
        # Define and update self.rcin with dummy values
        for idx in range(FlightAxis.RC_CHANNLES):
            self.rcin[idx] = 0.5  # Dummy value for all channels

        # Update self.rcin based on the FlightAxis.CHANNELS mapping
        dummy_joystick_values = {
            # Assuming the format is {joystick_channel_idx: value}
            # These values can be adjusted as per your requirement
            0: 0.5,  # Example value for a joystick channel
            1: 0.5,
            2: 0.5,
            3: 0.5,
            # ... Add other joystick channels if necessary
        }

        for channel in FlightAxis.CHANNELS:
            joy_idx, rc_idx, channelInverted = channel
            joystick_value = dummy_joystick_values.get(joy_idx, 0.0)
            self.rcin[rc_idx] = self.scaleJoystickValue(joystick_value, invert=channelInverted)

        return True

def test_parse1():
    res = b'<?xml version="1.0" encoding="UTF-8"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SOAP-ENV:Body><ReturnData><m-previousInputsState><m-selectedChannels>-1</m-selectedChannels><m-channelValues-0to1 xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="xsd:double[12]"><item>1</item><item>0</item><item>0.51503002643585205</item><item>1</item><item>0</item><item>0</item><item>0</item><item>0</item><item>0</item><item>0</item><item>0</item><item>0</item></m-channelValues-0to1></m-previousInputsState><m-aircraftState><m-currentPhysicsTime-SEC>9.4901527954498306</m-currentPhysicsTime-SEC><m-currentPhysicsSpeedMultiplier>1</m-currentPhysicsSpeedMultiplier><m-airspeed-MPS>4.2668099647568329</m-airspeed-MPS><m-altitudeASL-MTR>0.23033138335698844</m-altitudeASL-MTR><m-altitudeAGL-MTR>0.23033138335698844</m-altitudeAGL-MTR><m-groundspeed-MPS>4.2655322162874292</m-groundspeed-MPS><m-pitchRate-DEGpSEC>46.914449474279536</m-pitchRate-DEGpSEC><m-rollRate-DEGpSEC>-64.392902600666275</m-rollRate-DEGpSEC><m-yawRate-DEGpSEC>-95.093221536022611</m-yawRate-DEGpSEC><m-azimuth-DEG>156.77792358398437</m-azimuth-DEG><m-inclination-DEG>-13.11492919921875</m-inclination-DEG><m-roll-DEG>6.6998014450073242</m-roll-DEG><m-orientationQuaternion-X>0.079808555543422699</m-orientationQuaternion-X><m-orientationQuaternion-Y>0.099987812340259552</m-orientationQuaternion-Y><m-orientationQuaternion-Z>-0.97012227773666382</m-orientationQuaternion-Z><m-orientationQuaternion-W>-0.20614470541477203</m-orientationQuaternion-W><m-aircraftPositionX-MTR>18.125473022460937</m-aircraftPositionX-MTR><m-aircraftPositionY-MTR>13.188897132873535</m-aircraftPositionY-MTR><m-velocityWorldU-MPS>-4.0696659088134766</m-velocityWorldU-MPS><m-velocityWorldV-MPS>1.2777262926101685</m-velocityWorldV-MPS><m-velocityWorldW-MPS>0.10441353917121887</m-velocityWorldW-MPS><m-velocityBodyU-MPS>3.1754355430603027</m-velocityBodyU-MPS><m-velocityBodyV-MPS>-2.8336892127990723</m-velocityBodyV-MPS><m-velocityBodyW-MPS>-0.30408719182014465</m-velocityBodyW-MPS><m-accelerationWorldAX-MPS2>-4.4059576988220215</m-accelerationWorldAX-MPS2><m-accelerationWorldAY-MPS2>-6.3290410041809082</m-accelerationWorldAY-MPS2><m-accelerationWorldAZ-MPS2>8.5558643341064453</m-accelerationWorldAZ-MPS2><m-accelerationBodyAX-MPS2>1.2007522583007813</m-accelerationBodyAX-MPS2><m-accelerationBodyAY-MPS2>8.1494748592376709</m-accelerationBodyAY-MPS2><m-accelerationBodyAZ-MPS2>-8.7651233673095703</m-accelerationBodyAZ-MPS2><m-windX-MPS>0</m-windX-MPS><m-windY-MPS>0</m-windY-MPS><m-windZ-MPS>0</m-windZ-MPS><m-propRPM>10893.9326171875</m-propRPM><m-heliMainRotorRPM>-1</m-heliMainRotorRPM><m-batteryVoltage-VOLTS>-1</m-batteryVoltage-VOLTS><m-batteryCurrentDraw-AMPS>-1</m-batteryCurrentDraw-AMPS><m-batteryRemainingCapacity-MAH>-1</m-batteryRemainingCapacity-MAH><m-fuelRemaining-OZ>11.978337287902832</m-fuelRemaining-OZ><m-isLocked>false</m-isLocked><m-hasLostComponents>false</m-hasLostComponents><m-anEngineIsRunning>true</m-anEngineIsRunning><m-isTouchingGround>true</m-isTouchingGround><m-flightAxisControllerIsActive>true</m-flightAxisControllerIsActive><m-currentAircraftStatus>CAS-FLYING</m-currentAircraftStatus></m-aircraftState><m-notifications><m-resetButtonHasBeenPressed>false</m-resetButtonHasBeenPressed></m-notifications></ReturnData></SOAP-ENV:Body></SOAP-ENV:Envelope>'
    fa = FlightAxis()
    fa.parseResponse(res)

def test_parse2():
    res = b'<?xml version="1.0" encoding="UTF-8"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SOAP-ENV:Body><ReturnData><m-previousInputsState><m-selectedChannels>-1</m-selectedChannels><m-channelValues-0to1 xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="xsd:double[12]"><item>1</item><item>0</item><item>0.51503002643585205</item><item>1</item><item>0</item><item>0</item><item>0</item><item>0</item><item>0</item><item>0</item><item>0</item><item>0</item></m-channelValues-0to1></m-previousInputsState><m-aircraftState><m-currentPhysicsTime-SEC>9.5060089953476563</m-currentPhysicsTime-SEC><m-currentPhysicsSpeedMultiplier>1</m-currentPhysicsSpeedMultiplier><m-airspeed-MPS>3.973699447829139</m-airspeed-MPS><m-altitudeASL-MTR>0.23263369561650649</m-altitudeASL-MTR><m-altitudeAGL-MTR>0.23263369561650649</m-altitudeAGL-MTR><m-groundspeed-MPS>3.9671952662134293</m-groundspeed-MPS><m-pitchRate-DEGpSEC>65.486709524862817</m-pitchRate-DEGpSEC><m-rollRate-DEGpSEC>-113.29177463687665</m-rollRate-DEGpSEC><m-yawRate-DEGpSEC>-115.36654693618766</m-yawRate-DEGpSEC><m-azimuth-DEG>154.92184448242187</m-azimuth-DEG><m-inclination-DEG>-12.265393257141113</m-inclination-DEG><m-roll-DEG>4.7752151489257812</m-roll-DEG><m-orientationQuaternion-X>0.063606671988964081</m-orientationQuaternion-X><m-orientationQuaternion-Y>0.095200046896934509</m-orientationQuaternion-Y><m-orientationQuaternion-Z>-0.96875286102294922</m-orientationQuaternion-Z><m-orientationQuaternion-W>-0.22001981735229492</m-orientationQuaternion-W><m-aircraftPositionX-MTR>18.143898010253906</m-aircraftPositionX-MTR><m-aircraftPositionY-MTR>13.126790046691895</m-aircraftPositionY-MTR><m-velocityWorldU-MPS>-3.8283603191375732</m-velocityWorldU-MPS><m-velocityWorldV-MPS>1.0403343439102173</m-velocityWorldV-MPS><m-velocityWorldW-MPS>-0.22726421058177948</m-velocityWorldW-MPS><m-velocityBodyU-MPS>2.9091477394104004</m-velocityBodyU-MPS><m-velocityBodyV-MPS>-2.6280345916748047</m-velocityBodyV-MPS><m-velocityBodyW-MPS>-0.64850509166717529</m-velocityBodyW-MPS><m-accelerationWorldAX-MPS2>-4.3766188621520996</m-accelerationWorldAX-MPS2><m-accelerationWorldAY-MPS2>-6.6690726280212402</m-accelerationWorldAY-MPS2><m-accelerationWorldAZ-MPS2>9.2979526519775391</m-accelerationWorldAZ-MPS2><m-accelerationBodyAX-MPS2>-0.53152298927307129</m-accelerationBodyAX-MPS2><m-accelerationBodyAY-MPS2>10.025743305683136</m-accelerationBodyAY-MPS2><m-accelerationBodyAZ-MPS2>-8.8302364349365234</m-accelerationBodyAZ-MPS2><m-windX-MPS>0</m-windX-MPS><m-windY-MPS>0</m-windY-MPS><m-windZ-MPS>0</m-windZ-MPS><m-propRPM>10886.970703125</m-propRPM><m-heliMainRotorRPM>-1</m-heliMainRotorRPM><m-batteryVoltage-VOLTS>-1</m-batteryVoltage-VOLTS><m-batteryCurrentDraw-AMPS>-1</m-batteryCurrentDraw-AMPS><m-batteryRemainingCapacity-MAH>-1</m-batteryRemainingCapacity-MAH><m-fuelRemaining-OZ>11.978252410888672</m-fuelRemaining-OZ><m-isLocked>false</m-isLocked><m-hasLostComponents>false</m-hasLostComponents><m-anEngineIsRunning>true</m-anEngineIsRunning><m-isTouchingGround>true</m-isTouchingGround><m-flightAxisControllerIsActive>true</m-flightAxisControllerIsActive><m-currentAircraftStatus>CAS-FLYING</m-currentAircraftStatus></m-aircraftState><m-notifications><m-resetButtonHasBeenPressed>false</m-resetButtonHasBeenPressed></m-notifications></ReturnData></SOAP-ENV:Body></SOAP-ENV:Envelope>'
    fa = FlightAxis()
    fa.parseResponse(res)


# NOTE: try something cleaner, such as using context manager
# and __enter__ and __exit__ dunder methods:
# '''
# with FlightAxisManager() as axis_manager:
#   do stuff
# '''

def signal_handler(sig, frame):
    print('You pressed Ctrl+C!')
    print("RC terminating")
    pygame.quit()
    fa.saveStates()
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')


JOYSTICK_DIRECT = False

if JOYSTICK_DIRECT:
    pygame.init()
    pygame.joystick.init()

    joystick = pygame.joystick.Joystick(0)
    joystick.init()
    print(f"{joystick.get_name()}")


fa = FlightAxis()
# 1) Disable RC control and start FlightAxis
fa.enableRC()
fa.disableRC()
# 2) exchange data once to set RC inputs to zeros
fa.getStates()
# 3) reset the simulator to a known initial state
fa.resetSim()

rc_values = {0: 0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0}
done = False
now = time.time()
while not done:
    t = time.time()
    dt = t - now
    if (dt > 1.0):
        # print stats
        #print(f"avg_f = {1/fa.avg_dt}[HZ]")
        now = t
    # TODO: hide this inside FlightAxis(), perhaps a conf in the constructor?
    if JOYSTICK_DIRECT:
        for event in pygame.event.get():
            #print(event)
            if event.type == pygame.QUIT:
                done = True
            if event.type == pygame.JOYAXISMOTION:
                rc_values[event.axis] = event.value
        fa.updateActuators(rc_values)
    fa.getStates()
    fa.getHilActuatorControls()
print("DONE")
 
Can I use my own terrain?
Not in the current version. RealFlight uses real terrain data from the Sierra Nevada region in Southern Spain. This makes it possible to test terrain-dependent software, just not at your home range (unless you happen to be operating within that region).
Hello,

Hello, are the GPS coordinates of the 3D scenery airports (e.g. Realflight Ranch) available?

We use RealFlight for SITL (Software in the Loop) for the flight controller software INAV (https://github.com/iNavFlight/inav).
With the coordinates it is easier to test GPS modes (e.g. autonomous missions).
 
Hello,

Hello, are the GPS coordinates of the 3D scenery airports (e.g. Realflight Ranch) available?

We use RealFlight for SITL (Software in the Loop) for the flight controller software INAV (https://github.com/iNavFlight/inav).
With the coordinates it is easier to test GPS modes (e.g. autonomous missions).

here is the rough area of the sierra nevada map

realflight ranch some where north of the "nal Si" in "parque Nacional Seirra nevada" in the second picture

2023-02-17 13_33_39-RealFlight Evolution.jpg2023-02-17 13_21_39-Google Earth Pro.jpg2023-02-17 13_21_28-Google Earth Pro.jpg
 
Thanks I found the exact position of the Real Flight Ranch,
Who wants to know: Lat: 37.118060° Lon: -2.773488°

Unfortunately I need the exact topographical data of the runway for my current project, in Realflight the terrain is flat, in real, and thus in the topographical databases, the terrain is hilly.

Thanks anyway.
 
Back
Top