# Module for generic flight-simulator interfaces #------------------------------------------------------------------------------- import const from sound import startSound import fsuipc import threading import time #------------------------------------------------------------------------------- class ConnectionListener(object): """Base class for listeners on connections to the flight simulator.""" def connected(self, fsType, descriptor): """Called when a connection has been established to the flight simulator of the given type.""" print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor def connectionFailed(self): """Called when the connection could not be established.""" print "fs.ConnectionListener.connectionFailed" def disconnected(self): """Called when a connection to the flight simulator has been broken.""" print "fs.ConnectionListener.disconnected" #------------------------------------------------------------------------------- class SimulatorException(Exception): """Exception thrown by the simulator interface for communication failure.""" #------------------------------------------------------------------------------- def createSimulator(type, connectionListener): """Create a simulator instance for the given simulator type with the given connection listener. The returned object should provide the following members: FIXME: add info """ assert type in [const.SIM_MSFS9, const.SIM_MSFSX], \ "Only MS Flight Simulator 2004 and X are supported" return fsuipc.Simulator(connectionListener, connectAttempts = 3) #------------------------------------------------------------------------------- class MessageThread(threading.Thread): """Thread to handle messages.""" def __init__(self, config, simulator): """Initialize the message thread with the given configuration and simulator.""" super(MessageThread, self).__init__() self._config = config self._simulator = simulator self._requestCondition = threading.Condition() self._messages = [] self._nextMessageTime = None self._toQuit = False self.daemon = True def add(self, messageType, text, duration, disconnect): """Add the given message to the requested messages.""" with self._requestCondition: self._messages.append((messageType, text, duration, disconnect)) self._requestCondition.notify() def quit(self): """Quit the thread.""" with self._requestCondition: self._toQuit = True self._requestCondition.notifty() self.join() def run(self): """Perform the thread's operation.""" while True: (messageType, text, duration, disconnect) = (None, None, None, None) with self._requestCondition: now = time.time() while not self._toQuit and \ ((self._nextMessageTime is not None and \ self._nextMessageTime>now) or \ not self._messages): self._requestCondition.wait(1) now = time.time() if self._toQuit: return if self._nextMessageTime is None or \ self._nextMessageTime<=now: self._nextMessageTime = None if self._messages: (messageType, text, duration, disconnect) = self._messages[0] del self._messages[0] if text is not None: self._sendMessage(messageType, text, duration, disconnect) def _sendMessage(self, messageType, text, duration, disconnect): """Send the message and setup the next message time.""" messageLevel = self._config.getMessageTypeLevel(messageType) if messageLevel==const.MESSAGELEVEL_SOUND or \ messageLevel==const.MESSAGELEVEL_BOTH: startSound(const.SOUND_NOTIFY if messageType==const.MESSAGETYPE_VISIBILITY else const.SOUND_DING) if (messageLevel==const.MESSAGELEVEL_FS or \ messageLevel==const.MESSAGELEVEL_BOTH): if disconnect: self._simulator.disconnect("[MLX] " + text, duration = duration) else: self._simulator.sendMessage("[MLX] " + text, duration = duration) elif disconnecte: self._simulator.disconnect() self._nextMessageTime = time.time() + duration #------------------------------------------------------------------------------- _messageThread = None #------------------------------------------------------------------------------- def setupMessageSending(config, simulator): """Setup message sending with the given config and simulator.""" global _messageThread if _messageThread is not None: _messageThread.quit() _messageThread = MessageThread(config, simulator) _messageThread.start() #------------------------------------------------------------------------------- def sendMessage(messageType, text, duration = 3, disconnect = False): """Send the given message of the given type into the simulator and/or play a corresponding sound.""" global _messageThread if _messageThread is not None: _messageThread.add(messageType, text, duration, disconnect) #------------------------------------------------------------------------------- class AircraftState(object): """Base class for the aircraft state produced by the aircraft model based on readings from the simulator. The following data members should be provided at least: - timestamp: the simulator time of the measurement in seconds since the epoch (float) - latitude (in degrees, North is positive) - longitude (in degrees, East is positive) - paused: a boolean indicating if the flight simulator is paused for whatever reason (it could be a pause mode, or a menu, a dialog, or a replay, etc.) - trickMode: a boolean indicating if some "trick" mode (e.g. "SLEW" in MSFS) is activated - overspeed: a boolean indicating if the aircraft is in overspeed - stalled: a boolean indicating if the aircraft is stalled - onTheGround: a boolean indicating if the aircraft is on the ground - zfw: the zero-fuel weight in kilograms (float) - grossWeight: the gross weight in kilograms (float) - heading: the heading of the aircraft in degrees (float) - pitch: the pitch of the aircraft in degrees. Positive means pitch down, negative means pitch up (float) - bank: the bank of the aircraft in degrees. Positive means bank left, negative means bank right (float) - ias: the indicated airspeed in knots (float) - smoothedIAS: the smoothed IAS in knots (float) - mach: the airspeed in mach (float) - groundSpeed: the ground speed (float) - vs: the vertical speed in feet/minutes (float) - smoothedVS: the smoothed VS in feet/minutes (float) - radioAltitude: the radio altitude of the aircraft in feet (float) - altitude: the altitude of the aircraft in feet (float) - gLoad: G-load (float) - flapsSet: the selected degrees of the flaps (float) - flaps: the actual degrees of the flaps (float) - fuelWeight[]: the fuel weights in the different tanks in kgs (array of floats of as many items as the number fuel tanks) - n1[]: the N1 values of the turbine engines (array of floats of as many items as the number of engines, present only for aircraft with turbines) - rpm[]: the RPM values of the piston engines (array of floats of as many items as the number of engines, present only for aircraft with pistons) - reverser[]: an array of booleans indicating if the thrust reversers are activated on any of the engines. The number of items equals to the number of engines with a reverser. - navLightsOn: a boolean indicating if the navigation lights are on - antiCollisionLightsOn: a boolean indicating if the anti-collision lights are on - strobeLightsOn: a boolean indicating if the strobe lights are on - landingLightsOn: a boolean indicating if the landing lights are on - pitotHeatOn: a boolean indicating if the pitot heat is on - parking: a boolean indicating if the parking brake is set - gearControlDown: a boolean indicating if the gear control is set to down - gearsDown: a boolean indicating if the gears are down - spoilersArmed: a boolean indicating if the spoilers have been armed for automatic deployment - spoilersExtension: the percentage of how much the spoiler is extended (float) - altimeter: the altimeter setting in hPa (float) - nav1: the frequency of the NAV1 radio in MHz (string) - nav2: the frequency of the NAV1 radio in MHz (string) - squawk: the transponder code (string) - windSpeed: the speed of the wind at the aircraft in knots (float) - windDirection: the direction of the wind at the aircraft in degrees (float) - visibility: the visibility in metres (float) FIXME: needed when taxiing only: - payload weight FIXME: needed rarely: - latitude, longitude - transporter - visibility """