huginn/protocols.py
class ControlsClient(DatagramProtocol):
    """The ControlsClient is used to transmit the updated aircraft controls
    to Huginn"""
    def __init__(self, host, port):
        self.host = host
        self.port = port

    def startProtocol(self):
        self.transport.connect(self.host, self.port)

    def send_datagram(self, datagram):
        """Transmit the UDP datagram that contains the aircraft controls
        values

        Arguments:
        datagram: that datagram to transmit
        """
        self.transport.write(datagram)

    def transmit_controls(self, aileron, elevator, rudder, throttle):
        """Send the aircraft controls to Huginn"""
        controls = fdm_pb2.Controls()
        controls.aileron = aileron
        controls.elevator = elevator
        controls.rudder = rudder
        controls.throttle = throttle

        self.send_datagram(controls.SerializeToString())
huginn/protocols.py
class SensorDataFactory(Factory):
    """Factory class for the SensorDataProtocol twisted protocol class"""

    def __init__(self, aircraft):
        """Create a new SensorDataFactory object

        Arguments:
        aircraft: an Aircraft class instance with will be used to retrieve the
            aircraft data
        """
        self.protocol = SensorDataProtocol
        self.aircraft = aircraft
huginn/protocols.py
class SimulatorDataClient(DatagramProtocol):
    """The SimulatorDataClient is used to receive simulator data from
    Huginn"""

    def __init__(self):
        self.listeners = []

    def add_simulator_data_listener(self, listener):
        """Add an simulator data listener"""
        self.listeners.append(listener)

    def remove_simulator_data_listener(self, listener):
        """Remove an simulator data listener"""
        self.listeners.remove(listener)

    def _notify_simulator_data_listeners(self, simulator_data):
        """Update the simulator data listeners

        Arguments:
        simulator_data: a protocol buffer object that contains the simulator
            data
        """
        for simulator_data_listener in self.listeners:
            simulator_data_listener.simulator_data_received(simulator_data)

    def datagramReceived(self, datagram, addr):
        simulator_data = fdm_pb2.SimulatorData()

        simulator_data.ParseFromString(datagram)

        self._notify_simulator_data_listeners(simulator_data)
huginn/aircraft.py
class Aircraft(object):
    """The Aircraft class is a wrapper around jsbsim that contains data about
    the aircraft state."""
    def __init__(self, fdmexec):
        self.fdmexec = fdmexec
        self.type = "Rascal"
        self.sensors = Sensors(fdmexec)
        self.instruments = Instruments(fdmexec)
        self.engine = Engine(fdmexec)
        self.controls = Controls(fdmexec)

    def start_engines(self):
        """Start the aircraft engines"""
        for i in range(self.fdmexec.GetPropulsion().GetNumEngines()):
            logger.debug("Starting engine %d", i)
            self.fdmexec.GetPropulsion().GetEngine(i).SetRunning(1)

    def trim(self, mode=TRIM_MODE_FULL):
        """Trim the aircraft"""
        logger.debug("Executing trim with mode = %d", mode)

        try:
            self.fdmexec.DoTrim(mode)
        except:
            logger.warning("Failed to trim the aircraft in mode %d", mode)
            return False

        return True

    def print_aircraft_state(self):
        """Print the aircraft state"""
        print("Aircraft state")
        print("")
        print("Position")
        print("========")
        print("Latitude: %f degrees" % self.instruments.gps.latitude)
        print("Longitude: %f degrees" % self.instruments.gps.longitude)
        print("Altitude: %f meters" % self.instruments.gps.altitude)
        print("Airspeed: %f meters/second" % self.instruments.gps.airspeed)
        print("Heading: %f degrees" % self.instruments.gps.heading)
        print("")
        print("Orientation")
        print("===========")

        roll = self.sensors.inertial_navigation_system.roll
        print("Roll: %f degrees" % roll)

        pitch = self.sensors.inertial_navigation_system.pitch
        print("Pitch: %f degrees" % pitch)
        print("")
        print("Engines")
        print("=======")
        print("Thrust: %f Newtons" % self.engine.thrust)
        print("")
        print("Controls")
        print("========")
        print("Aileron: %f" % self.controls.aileron)
        print("Elevator: %f" % self.controls.elevator)
        print("Rudder: %f" % self.controls.rudder)
        print("Throttle: %f" % self.controls.throttle)
        print("")
huginn/fdm.py
class FDMBuilder(object):
    """The FDMBuilder creates the flight dynamics model object that will be
    used by the simulator"""
    def __init__(self, data_path):
        self.data_path = data_path
        self.dt = configuration.DT

        self.aircraft = configuration.AIRCRAFT

        self.latitude = configuration.LATITUDE
        self.longitude = configuration.LONGITUDE
        self.altitude = configuration.ALTITUDE
        self.airspeed = configuration.AIRSPEED
        self.heading = configuration.HEADING

    def create_fdm(self):
        """Create the flight dynamics model"""

        fdmexec = FGFDMExec()

        logger.debug("Using jsbsim data at %s", self.data_path)

        aircraft_data_path = self.data_path
        engine_data_path = path.join(self.data_path, "Engines")
        systems_data_path = path.join(self.data_path, "Systems")

        logger.debug("Using aircraft data at %s", aircraft_data_path)
        logger.debug("Using engine data at %s", engine_data_path)
        logger.debug("Using systems data at %s", systems_data_path)

        fdmexec.SetRootDir(self.data_path)
        fdmexec.SetAircraftPath("")
        fdmexec.SetEnginePath(engine_data_path)
        fdmexec.SetSystemsPath(systems_data_path)

        logger.debug("JSBSim dt is %f", self.dt)
        fdmexec.Setdt(self.dt)

        logger.debug("Loading '%s' aircraft model", self.aircraft)
        fdmexec.LoadModel(self.aircraft)

        altitude = self.altitude * ur.meter
        altitude.ito(ur.foot)

        altitude_in_feet = altitude.magnitude

        airspeed = self.airspeed * ur.meters_per_second
        airspeed.ito(ur.knot)

        airspeed_in_knots = airspeed.magnitude

        logger.debug("Initial latitude: %f degrees", self.latitude)
        logger.debug("Initial longitude: %f degrees", self.longitude)
        logger.debug("Initial altitude: %f meters", self.altitude)
        logger.debug("Initial airspeed: %f meters/second", self.airspeed)
        logger.debug("Initial heading: %f degrees", self.heading)

        fdmexec.GetIC().SetLatitudeDegIC(self.latitude)
        fdmexec.GetIC().SetLongitudeDegIC(self.longitude)
        fdmexec.GetIC().SetAltitudeASLFtIC(altitude_in_feet)
        fdmexec.GetIC().SetPsiDegIC(self.heading)
        fdmexec.GetIC().SetVtrueKtsIC(airspeed_in_knots)

        if not fdmexec.RunIC():
            logger.error("Failed to run initial condition")
            return None

        return fdmexec
huginn/protocols.py
class ControlsProtocol(DatagramProtocol):
    """The ControlsProtocol is used to receive and update tha aircraft's
    controls"""
    def __init__(self, fdmexec):
        self.fdmexec = fdmexec

    def update_aircraft_controls(self, aileron, elevator, rudder, throttle):
        """Set the new aircraft controls values"""
        if aileron > 1.0:
            aileron = 1.0
        elif aileron < -1.0:
            aileron = -1.0

        self.fdmexec.GetFCS().SetDaCmd(aileron)

        if elevator > 1.0:
            elevator = 1.0
        elif elevator < -1.0:
            elevator = -1.0

        self.fdmexec.GetFCS().SetDeCmd(elevator)

        if rudder > 1.0:
            rudder = 1.0
        elif rudder < -1.0:
            rudder = -1.0

        self.fdmexec.GetFCS().SetDrCmd(rudder)

        if throttle > 1.0:
            throttle = 1.0
        elif throttle < 0.0:
            throttle = 0.0

        for i in range(self.fdmexec.GetPropulsion().GetNumEngines()):
            self.fdmexec.GetFCS().SetThrottleCmd(i, throttle)

    def datagramReceived(self, datagram, addr):
        controls = fdm_pb2.Controls()

        try:
            controls.ParseFromString(datagram)
        except DecodeError:
            logger.exception("Failed to parse control data")
            print("Failed to parse control data")
            return

        self.update_aircraft_controls(controls.aileron,
                                      controls.elevator,
                                      controls.rudder,
                                      controls.throttle)
huginn/simulator.py
class Simulator(object):
    """The Simulator class is used to perform the simulation of an aircraft"""

    def __init__(self, fdmexec):
        """Constructor for the Simulator object

        Arguments:
        fdmexec: A flight dynamics model
        """
        self.aircraft = Aircraft(fdmexec)
        self.fdmexec = fdmexec
        self.fdm = FDM(fdmexec)
        self.trim_mode = TRIM_MODE_FULL
        self._crashed = False
        self.start_paused = False

    @property
    def crashed(self):
        """Returns True if the aircraft has crashed"""
        return self._crashed

    @property
    def dt(self):
        """The simulation time step"""
        return self.fdmexec.GetDeltaT()

    @property
    def simulation_time(self):
        """The current simulation time"""
        return self.fdmexec.GetSimTime()

    def pause(self):
        """Pause the simulator"""
        self.fdmexec.Hold()

    def resume(self):
        """Resume the simulation"""
        if self.crashed:
            logger.debug("Not resuming simulation because the aircraft has "
                         "crashed")
            return

        if self.fdmexec.IntegrationSuspended():
            self.fdmexec.ResumeIntegration()

        self.fdmexec.Resume()

    def is_paused(self):
        """Check if the simulator is paused"""
        return self.fdmexec.Holding()

    def reset(self):
        """Reset the simulation"""
        logger.debug("Reseting the aircraft")
        self._crashed = False

        self.pause()

        self.aircraft.controls.aileron = 0.0
        self.aircraft.controls.elevator = 0.0
        self.aircraft.controls.rudder = 0.0
        self.aircraft.controls.throttle = 0.0

        self.fdmexec.ResetToInitialConditions(0)

        if not self.fdmexec.RunIC():
            logger.error("Failed to run initial condition")
            return False

        logger.debug("starting the aircraft's engines")
        self.aircraft.start_engines()

        trim_result = self.aircraft.trim(self.trim_mode)
        if not trim_result:
            logger.warning("Failed to trim the aircraft")

            # reset the controls because the trim operation might have messed
            # them up
            self.aircraft.controls.aileron = 0.0
            self.aircraft.controls.elevator = 0.0
            self.aircraft.controls.rudder = 0.0
            self.aircraft.controls.throttle = 0.0

        if not self.step():
            logger.error("Failed to execute initial run")
            return False

        logger.debug("Engine thrust after simulation reset %f",
                     self.aircraft.engine.thrust)

        if not self.start_paused:
            self.resume()

        return True

    def step(self):
        """Run the simulation one time"""
        if not self.crashed and self.fdm.position.altitude < 0.0:
            logger.debug("Aircraft has crashed. Pausing simulator")
            self.pause()
            self._crashed = True
            return True

        if self.crashed:
            logger.debug("Not executing simulation step because aircraft has "
                         "crashed")
            return True

        was_paused = self.is_paused()

        if was_paused:
            self.resume()

        try:
            self.fdmexec.ProcessMessage()
            self.fdmexec.CheckIncrementalHold()
            run_result = self.fdmexec.Run()
        except:
            raise SimulationError()

        if run_result:
            if was_paused:
                self.pause()

            return True
        else:
            if was_paused:
                self.pause()

            logger.error("The simulator has failed to run")
            return False

    def run_for(self, time_to_run):
        """Run the simulation for the given time in seconds

        Arguments:
        time_to_run: the time in seconds that the simulator will run
        """
        if time_to_run < 0.0:
            logger.error("Invalid simulator run time length %f",
                         time_to_run)

            return False

        start_time = self.fdmexec.GetSimTime()
        end_time = start_time + time_to_run

        while self.fdmexec.GetSimTime() <= end_time:
            result = self.step()

            if not result:
                return False

        return True

    def run(self):
        """Run the simulation"""
        if not self.fdmexec.Holding():
            result = self.step()

            return result

        return True

    def set_aircraft_controls(self, aileron, elevator, rudder, throttle):
        """Update the aircraft controls"""
        self.aircraft.controls.aileron = aileron
        self.aircraft.controls.elevator = elevator
        self.aircraft.controls.rudder = rudder
        self.aircraft.controls.throttle = throttle

    def print_simulator_state(self):
        """Show the current state of the simulation"""
        print("Simulation state")
        print("================")
        print("Time: %f seconds" % self.simulation_time)
        print("DT: %f seconds" % self.dt)
        print("Running: %s" % (not self.is_paused()))
        print("")
huginn/protocols.py
class SimulatorDataProtocol(DatagramProtocol):
    """The FDMDataProtocol class is used to transmit the flight dynamics model
    data to the client"""
    def __init__(self, simulator, remote_host, port):
        self.simulator = simulator
        self.fdmexec = simulator.fdmexec
        self.aircraft = simulator.aircraft
        self.remote_host = remote_host
        self.port = port

    def _fill_gps_data(self, simulator_data):
        """Fill the gps data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        simulator_data.gps.latitude = self.aircraft.instruments.gps.latitude
        simulator_data.gps.longitude = self.aircraft.instruments.gps.longitude
        simulator_data.gps.altitude = self.aircraft.instruments.gps.altitude
        simulator_data.gps.airspeed = self.aircraft.instruments.gps.airspeed
        simulator_data.gps.heading = self.aircraft.instruments.gps.heading

    def _fill_accelerometer_data(self, simulator_data):
        """Fill the accelerometer data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        sensors = self.aircraft.sensors

        simulator_data.accelerometer.x = sensors.accelerometer.x
        simulator_data.accelerometer.y = sensors.accelerometer.y
        simulator_data.accelerometer.z = sensors.accelerometer.z

    def _fill_gyroscope_data(self, simulator_data):
        """Fill the gyroscope data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        sensors = self.aircraft.sensors

        simulator_data.gyroscope.roll_rate = sensors.gyroscope.roll_rate
        simulator_data.gyroscope.pitch_rate = sensors.gyroscope.pitch_rate
        simulator_data.gyroscope.yaw_rate = sensors.gyroscope.yaw_rate

    def _fill_thermometer_data(self, simulator_data):
        """Fill the thermometer data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        thermometer = self.aircraft.sensors.thermometer

        simulator_data.thermometer.temperature = thermometer.temperature

    def _fill_pressure_sensor_data(self, simulator_data):
        """Fill the pressure sensor data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        pressure_sensor = self.aircraft.sensors.pressure_sensor
        pitot_tube = self.aircraft.sensors.pitot_tube

        simulator_data.pressure_sensor.pressure = pressure_sensor.pressure
        simulator_data.pitot_tube.pressure = pitot_tube.pressure

    def _fill_engine_data(self, simulator_data):
        """Fill the engine data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        simulator_data.engine.thrust = self.aircraft.engine.thrust
        simulator_data.engine.throttle = self.aircraft.engine.throttle

    def _fill_aircraft_controls_data(self, simulator_data):
        """Fill the aircraft controls data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        simulator_data.controls.aileron = self.aircraft.controls.aileron
        simulator_data.controls.elevator = self.aircraft.controls.elevator
        simulator_data.controls.rudder = self.aircraft.controls.rudder
        simulator_data.controls.throttle = self.aircraft.controls.throttle

    def _fill_ins_data(self, simulator_data):
        """Fill the inertial navigation system data in the SimulatorData
        object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        ins = self.aircraft.sensors.inertial_navigation_system

        simulator_data.ins.roll = ins.roll
        simulator_data.ins.pitch = ins.pitch
        simulator_data.ins.latitude = ins.latitude
        simulator_data.ins.longitude = ins.longitude
        simulator_data.ins.altitude = ins.altitude
        simulator_data.ins.airspeed = ins.airspeed
        simulator_data.ins.heading = ins.heading

    def _fill_accelerations(self, simulator_data):
        """Fill the fdm accelerations data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        accelerations = self.simulator.fdm.accelerations

        simulator_data.accelerations.x = accelerations.x
        simulator_data.accelerations.y = accelerations.y
        simulator_data.accelerations.z = accelerations.z
        simulator_data.accelerations.p_dot = accelerations.p_dot
        simulator_data.accelerations.q_dot = accelerations.q_dot
        simulator_data.accelerations.r_dot = accelerations.r_dot
        simulator_data.accelerations.u_dot = accelerations.u_dot
        simulator_data.accelerations.v_dot = accelerations.v_dot
        simulator_data.accelerations.w_dot = accelerations.w_dot
        simulator_data.accelerations.gravity = accelerations.gravity

    def _fill_velocities(self, simulator_data):
        """Fill the fdm velocities data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        velocities = self.simulator.fdm.velocities

        simulator_data.velocities.p = velocities.p
        simulator_data.velocities.q = velocities.q
        simulator_data.velocities.r = velocities.r
        simulator_data.velocities.u = velocities.u
        simulator_data.velocities.v = velocities.v
        simulator_data.velocities.w = velocities.w
        simulator_data.velocities.true_airspeed = velocities.true_airspeed

        calibrated_airspeed = velocities.calibrated_airspeed
        simulator_data.velocities.calibrated_airspeed = calibrated_airspeed

        equivalent_airspeed = velocities.equivalent_airspeed
        simulator_data.velocities.equivalent_airspeed = equivalent_airspeed

        simulator_data.velocities.climb_rate = velocities.climb_rate
        simulator_data.velocities.ground_speed = velocities.ground_speed

    def _fill_position(self, simulator_data):
        """Fill the fdm position data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        position = self.simulator.fdm.position

        simulator_data.position.latitude = position.latitude
        simulator_data.position.longitude = position.longitude
        simulator_data.position.altitude = position.altitude
        simulator_data.position.heading = position.heading

    def _fill_orientation(self, simulator_data):
        """Fill the fdm orientation data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        orientation = self.simulator.fdm.orientation

        simulator_data.orientation.phi = orientation.phi
        simulator_data.orientation.theta = orientation.theta
        simulator_data.orientation.psi = orientation.psi

    def _fill_atmosphere(self, simulator_data):
        """Fill the fdm atmospheric data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        atmosphere = self.simulator.fdm.atmosphere

        simulator_data.atmosphere.pressure = atmosphere.pressure

        sea_level_pressure = atmosphere.sea_level_pressure
        simulator_data.atmosphere.sea_level_pressure = sea_level_pressure

        simulator_data.atmosphere.temperature = atmosphere.temperature

        sea_level_temperature = atmosphere.sea_level_temperature
        simulator_data.atmosphere.sea_level_temperature = sea_level_temperature

        simulator_data.atmosphere.density = atmosphere.density

        sea_level_density = atmosphere.sea_level_density
        simulator_data.atmosphere.sea_level_density = sea_level_density

    def _fill_forces(self, simulator_data):
        """Fill the fdm atmospheric data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        forces = self.simulator.fdm.forces

        simulator_data.forces.x_body = forces.x_body
        simulator_data.forces.y_body = forces.y_body
        simulator_data.forces.z_body = forces.z_body
        simulator_data.forces.x_wind = forces.x_wind
        simulator_data.forces.y_wind = forces.y_wind
        simulator_data.forces.z_wind = forces.z_wind
        simulator_data.forces.x_total = forces.x_total
        simulator_data.forces.y_total = forces.y_total
        simulator_data.forces.z_total = forces.z_total

    def get_simulator_data(self):
        """Return the simulator data"""
        simulator_data = fdm_pb2.SimulatorData()

        simulator_data.time = self.fdmexec.GetSimTime()

        self._fill_gps_data(simulator_data)
        self._fill_accelerometer_data(simulator_data)
        self._fill_gyroscope_data(simulator_data)
        self._fill_thermometer_data(simulator_data)
        self._fill_pressure_sensor_data(simulator_data)
        self._fill_engine_data(simulator_data)
        self._fill_aircraft_controls_data(simulator_data)
        self._fill_ins_data(simulator_data)
        self._fill_accelerations(simulator_data)
        self._fill_velocities(simulator_data)
        self._fill_position(simulator_data)
        self._fill_orientation(simulator_data)
        self._fill_atmosphere(simulator_data)
        self._fill_forces(simulator_data)

        return simulator_data

    def send_simulator_data(self):
        """Transmit the simulator data"""
        simulator_data = self.get_simulator_data()

        datagram = simulator_data.SerializeToString()

        self.transport.write(datagram, (self.remote_host, self.port))
huginn/configuration.py
SIMULATOR_CONTROL_PORT = 10500
WEB_SERVER_PORT = 8090
SENSORS_PORT = 10300
CONTROLS_PORT = 10301
FDM_CLIENT_ADDRESS = "127.0.0.1"
FDM_CLIENT_PORT = 10302
FDM_CLIENT_DT = 0.1
TELEMETRY_PORT = 10400
TELEMETRY_DT = 1.0
WEBSOCKET_HOST = "localhost"
WEBSOCKET_PORT = 8091
WEBSOCKET_UPDATE_RATE = 20.0
AIRCRAFT = "Rascal"
DT = 1.0/300.0
LATITUDE = 37.9232547
LONGITUDE = 23.921773
ALTITUDE = 300.0
AIRSPEED = 30.0
HEADING = 45.0
AVAILABLE_AIRCRAFT = ["Rascal", "easystar"]
DEFAULT_AICRAFT = "Rascal"
LOG_FILE = "huginn.log"
def get_data_path():
huginn/fdm.py
class FDM(object):
    """The FDM object is a wrapper around the JSBSim objects that contains the
    values of the flight dynamics model."""
    def __init__(self, fdmexec):
        self.fdmexec = fdmexec
        self.accelerations = Accelerations(fdmexec)
        self.velocities = Velocities(fdmexec)
        self.position = Position(fdmexec)
        self.orientation = Orientation(fdmexec)
        self.atmosphere = Atmosphere(fdmexec)
        self.forces = Forces(fdmexec)
        self.initial_condition = InitialCondition(fdmexec)


Question: from unittest import TestCase
from mock import MagicMock
from huginn.protocols import ControlsProtocol, SimulatorDataProtocol,\
                             SimulatorDataClient, ControlsClient,\
                             SensorDataFactory
from huginn.aircraft import Aircraft
from huginn.protobuf import fdm_pb2
from huginn.fdm import FDMBuilder, FDM
from huginn import configuration
from huginn.simulator import Simulator
from test_common import isclose
from mockObjects import MockSimulatorDataDatagram, MockSimulatorDataListener
        self.assertAlmostEqual(simulator_data.velocities.r, fdm.velocities.r, 3)
        self.assertAlmostEqual(simulator_data.velocities.true_airspeed, fdm.velocities.true_airspeed, 3)
        self.assertAlmostEqual(simulator_data.velocities.climb_rate, fdm.velocities.climb_rate, 3)
        self.assertAlmostEqual(simulator_data.velocities.u, fdm.velocities.u, 3)
        self.assertAlmostEqual(simulator_data.velocities.v, fdm.velocities.v, 3)
        self.assertAlmostEqual(simulator_data.velocities.w, fdm.velocities.w, 3)
        self.assertAlmostEqual(simulator_data.velocities.calibrated_airspeed, fdm.velocities.calibrated_airspeed, 3)
        self.assertAlmostEqual(simulator_data.velocities.equivalent_airspeed, fdm.velocities.equivalent_airspeed, 3)
        self.assertAlmostEqual(simulator_data.velocities.ground_speed, fdm.velocities.ground_speed, 3)

        self.assertAlmostEqual(simulator_data.position.latitude, fdm.position.latitude, 3)
        self.assertAlmostEqual(simulator_data.position.longitude, fdm.position.longitude, 3)
        self.assertAlmostEqual(simulator_data.position.altitude, fdm.position.altitude, 3)
        self.assertAlmostEqual(simulator_data.position.heading, fdm.position.heading, 3)

        self.assertAlmostEqual(simulator_data.orientation.phi, fdm.orientation.phi, 3)
        self.assertAlmostEqual(simulator_data.orientation.theta, fdm.orientation.theta, 3)
        self.assertAlmostEqual(simulator_data.orientation.psi, fdm.orientation.psi, 3)

        self.assertAlmostEqual(simulator_data.atmosphere.pressure, fdm.atmosphere.pressure, 3)
        self.assertAlmostEqual(simulator_data.atmosphere.sea_level_pressure, fdm.atmosphere.sea_level_pressure, 3)
        self.assertAlmostEqual(simulator_data.atmosphere.temperature, fdm.atmosphere.temperature, 3)
        self.assertAlmostEqual(simulator_data.atmosphere.sea_level_temperature, fdm.atmosphere.sea_level_temperature, 3)
        self.assertAlmostEqual(simulator_data.atmosphere.density, fdm.atmosphere.density, 3)
        self.assertAlmostEqual(simulator_data.atmosphere.sea_level_density, fdm.atmosphere.sea_level_density, 3)

        self.assertAlmostEqual(simulator_data.forces.x_body, fdm.forces.x_body, 3)
        self.assertAlmostEqual(simulator_data.forces.y_body, fdm.forces.y_body, 3)
        self.assertAlmostEqual(simulator_data.forces.z_body, fdm.forces.z_body, 3)
        self.assertAlmostEqual(simulator_data.forces.x_wind, fdm.forces.x_wind, 3)
        self.assertAlmostEqual(simulator_data.forces.y_wind, fdm.forces.y_wind, 3)
        self.assertAlmostEqual(simulator_data.forces.z_wind, fdm.forces.z_wind, 3)
        self.assertAlmostEqual(simulator_data.forces.x_total, fdm.forces.x_total, 3)
        self.assertAlmostEqual(simulator_data.forces.y_total, fdm.forces.y_total, 3)
        self.assertAlmostEqual(simulator_data.forces.z_total, fdm.forces.z_total, 3)

class SimulatorDataMatcher(object):
    def __eq__(self, fdm_data):
        mock_simulator_data_datagram = MockSimulatorDataDatagram()

        if not isclose(fdm_data.time, mock_simulator_data_datagram.simulation_time, 0.001): return False
        if not isclose(fdm_data.gps.latitude, mock_simulator_data_datagram.latitude, 0.001): return False
        if not isclose(fdm_data.gps.longitude, mock_simulator_data_datagram.longitude, 0.001): return False
        if not isclose(fdm_data.gps.altitude, mock_simulator_data_datagram.altitude, 0.001): return False
        if not isclose(fdm_data.gps.airspeed, mock_simulator_data_datagram.airspeed, 0.001): return False
        if not isclose(fdm_data.gps.heading, mock_simulator_data_datagram.heading, 0.001): return False
        if not isclose(fdm_data.accelerometer.x, mock_simulator_data_datagram.x_acceleration, 0.001): return False
        if not isclose(fdm_data.accelerometer.y, mock_simulator_data_datagram.y_acceleration, 0.001): return False
        if not isclose(fdm_data.accelerometer.z, mock_simulator_data_datagram.z_acceleration, 0.001): return False
        if not isclose(fdm_data.gyroscope.roll_rate, mock_simulator_data_datagram.roll_rate, 0.001): return False
        if not isclose(fdm_data.gyroscope.pitch_rate, mock_simulator_data_datagram.pitch_rate, 0.001): return False
        if not isclose(fdm_data.gyroscope.yaw_rate, mock_simulator_data_datagram.yaw_rate, 0.001): return False
        if not isclose(fdm_data.thermometer.temperature, mock_simulator_data_datagram.temperature, 0.001): return False
        if not isclose(fdm_data.pressure_sensor.pressure, mock_simulator_data_datagram.static_pressure, 0.001): return False
        if not isclose(fdm_data.pitot_tube.pressure, mock_simulator_data_datagram.total_pressure, 0.001): return False
        if not isclose(fdm_data.ins.roll, mock_simulator_data_datagram.roll, 0.001): return False
        if not isclose(fdm_data.ins.pitch, mock_simulator_data_datagram.pitch, 0.001): return False
        if not isclose(fdm_data.ins.latitude, mock_simulator_data_datagram.latitude, 0.001): return False
        if not isclose(fdm_data.ins.longitude, mock_simulator_data_datagram.longitude, 0.001): return False
        if not isclose(fdm_data.ins.altitude, mock_simulator_data_datagram.altitude, 0.001): return False
        if not isclose(fdm_data.ins.airspeed, mock_simulator_data_datagram.airspeed, 0.001): return False
        if not isclose(fdm_data.ins.heading, mock_simulator_data_datagram.heading, 0.001): return False
        if not isclose(fdm_data.engine.thrust, mock_simulator_data_datagram.thrust, 0.001): return False
        if not isclose(fdm_data.controls.aileron, mock_simulator_data_datagram.aileron, 0.001): return False
        if not isclose(fdm_data.controls.elevator, mock_simulator_data_datagram.elevator, 0.001): return False
        if not isclose(fdm_data.controls.rudder, mock_simulator_data_datagram.rudder, 0.001): return False
        if not isclose(fdm_data.controls.throttle, mock_simulator_data_datagram.throttle, 0.001): return False
        if not isclose(fdm_data.accelerations.x, mock_simulator_data_datagram.fdm_x_acceleration, 0.001): return False
        if not isclose(fdm_data.accelerations.y, mock_simulator_data_datagram.fdm_y_acceleration, 0.001): return False
        if not isclose(fdm_data.accelerations.z, mock_simulator_data_datagram.fdm_z_acceleration, 0.001): return False
        if not isclose(fdm_data.accelerations.p_dot, mock_simulator_data_datagram.p_dot, 0.001): return False
        if not isclose(fdm_data.accelerations.q_dot, mock_simulator_data_datagram.q_dot, 0.001): return False
        if not isclose(fdm_data.accelerations.r_dot, mock_simulator_data_datagram.r_dot, 0.001): return False
        if not isclose(fdm_data.accelerations.u_dot, mock_simulator_data_datagram.u_dot, 0.001): return False
        if not isclose(fdm_data.accelerations.v_dot, mock_simulator_data_datagram.v_dot, 0.001): return False
        if not isclose(fdm_data.accelerations.w_dot, mock_simulator_data_datagram.w_dot, 0.001): return False
        if not isclose(fdm_data.accelerations.gravity, mock_simulator_data_datagram.gravity, 0.001): return False
        
        if not isclose(fdm_data.velocities.p, mock_simulator_data_datagram.p, 0.001): return False
        if not isclose(fdm_data.velocities.q, mock_simulator_data_datagram.q, 0.001): return False
        if not isclose(fdm_data.velocities.r, mock_simulator_data_datagram.r, 0.001): return False
        if not isclose(fdm_data.velocities.true_airspeed, mock_simulator_data_datagram.true_airspeed, 0.001): return False
        if not isclose(fdm_data.velocities.climb_rate, mock_simulator_data_datagram.climb_rate, 0.001): return False
        if not isclose(fdm_data.velocities.u, mock_simulator_data_datagram.u, 0.001): return False
        if not isclose(fdm_data.velocities.v, mock_simulator_data_datagram.v, 0.001): return False
        if not isclose(fdm_data.velocities.w, mock_simulator_data_datagram.w, 0.001): return False
        if not isclose(fdm_data.velocities.calibrated_airspeed, mock_simulator_data_datagram.calibrated_airspeed, 0.001): return False
        if not isclose(fdm_data.velocities.equivalent_airspeed, mock_simulator_data_datagram.equivalent_airspeed, 0.001): return False
        if not isclose(fdm_data.velocities.ground_speed, mock_simulator_data_datagram.ground_speed, 0.001): return False

        if not isclose(fdm_data.position.latitude, mock_simulator_data_datagram.fdm_latitude, 0.001): return False
        if not isclose(fdm_data.position.longitude, mock_simulator_data_datagram.fdm_longitude, 0.001): return False
        if not isclose(fdm_data.position.altitude, mock_simulator_data_datagram.fdm_altitude, 0.001): return False
        if not isclose(fdm_data.position.heading, mock_simulator_data_datagram.fdm_heading, 0.001): return False

        if not isclose(fdm_data.orientation.phi, mock_simulator_data_datagram.phi, 0.001): return False
        if not isclose(fdm_data.orientation.theta, mock_simulator_data_datagram.theta, 0.001): return False
        if not isclose(fdm_data.orientation.psi, mock_simulator_data_datagram.psi, 0.001): return False

        if not isclose(fdm_data.atmosphere.pressure, mock_simulator_data_datagram.fdm_pressure, 0.001): return False
        if not isclose(fdm_data.atmosphere.sea_level_pressure, mock_simulator_data_datagram.fdm_sea_level_pressure, 0.001): return False
        if not isclose(fdm_data.atmosphere.temperature, mock_simulator_data_datagram.fdm_temperature, 0.001): return False
        if not isclose(fdm_data.atmosphere.sea_level_temperature, mock_simulator_data_datagram.fdm_sea_level_temperature, 0.001): return False
        if not isclose(fdm_data.atmosphere.density, mock_simulator_data_datagram.fdm_density, 0.001): return False
        if not isclose(fdm_data.atmosphere.sea_level_density, mock_simulator_data_datagram.fdm_sea_level_density, 0.001): return False

        if not isclose(fdm_data.forces.x_body, mock_simulator_data_datagram.x_body, 0.001): return False
        if not isclose(fdm_data.forces.y_body, mock_simulator_data_datagram.y_body, 0.001): return False
        if not isclose(fdm_data.forces.z_body, mock_simulator_data_datagram.z_body, 0.001): return False
        if not isclose(fdm_data.forces.x_wind, mock_simulator_data_datagram.x_wind, 0.001): return False
        if not isclose(fdm_data.forces.y_wind, mock_simulator_data_datagram.y_wind, 0.001): return False
        if not isclose(fdm_data.forces.z_wind, mock_simulator_data_datagram.z_wind, 0.001): return False
        if not isclose(fdm_data.forces.x_total, mock_simulator_data_datagram.x_total, 0.001): return False
        if not isclose(fdm_data.forces.y_total, mock_simulator_data_datagram.y_total, 0.001): return False
        if not isclose(fdm_data.forces.z_total, mock_simulator_data_datagram.z_total, 0.001): return False

        return True

class TestSimulatorDataClient(TestCase):
    def test_received_simulator_data_datagram(self):

Answer:         simulator_data_client = SimulatorDataClient()