212 lines
6.1 KiB
Python
212 lines
6.1 KiB
Python
from telnetlib import Telnet
|
|
import sys
|
|
import socket
|
|
import re
|
|
import time
|
|
|
|
__all__ = ["FlightGear"]
|
|
|
|
CRLF = '\r\n'
|
|
|
|
class FGTelnet(Telnet):
|
|
def __init__(self,host,port):
|
|
Telnet.__init__(self,host,port)
|
|
self.prompt = [re.compile('/[^>]*> '.encode('utf-8'))]
|
|
self.timeout = 5
|
|
#Telnet.set_debuglevel(self,2)
|
|
|
|
def help(self):
|
|
return
|
|
|
|
def ls(self,dir=None):
|
|
"""
|
|
Returns a list of properties.
|
|
"""
|
|
if dir is None:
|
|
self._putcmd('ls')
|
|
else:
|
|
self._putcmd('ls %s' % dir )
|
|
return self._getresp()
|
|
|
|
def ls2(self, dir_):
|
|
self._putcmd(f'ls2 {dir_}')
|
|
return self._getresp()
|
|
|
|
def dump(self):
|
|
"""Dump current state as XML."""
|
|
self._putcmd('dump')
|
|
return self._getresp()
|
|
|
|
def cd(self, dir):
|
|
"""Change directory."""
|
|
self._putcmd('cd ' + dir)
|
|
self._getresp()
|
|
return
|
|
|
|
def pwd(self):
|
|
"""Display current path."""
|
|
self._putcmd('pwd')
|
|
return self._getresp()
|
|
|
|
def get(self,var):
|
|
"""Retrieve the value of a parameter."""
|
|
self._putcmd('get %s' % var )
|
|
return self._getresp()
|
|
|
|
def set(self,var,value):
|
|
"""Set variable to a new value"""
|
|
self._putcmd('set %s %s' % (var,value))
|
|
self._getresp() # Discard response
|
|
|
|
def quit(self):
|
|
"""Terminate connection"""
|
|
self._putcmd('quit')
|
|
self.close()
|
|
return
|
|
|
|
# Internal: send one command to FlightGear
|
|
def _putcmd(self,cmd):
|
|
cmd = cmd + CRLF
|
|
Telnet.write(self, cmd.encode('utf-8'))
|
|
return
|
|
|
|
def _getresp(self):
|
|
# Telnet.expect() can return short result, so we call it in a loop.
|
|
response = b''
|
|
while 1:
|
|
_i, _match, data = Telnet.expect(self, self.prompt, self.timeout)
|
|
response += data
|
|
if _i == 0:
|
|
break # We have the prompt that marks the end of the data.
|
|
assert _i == -1, f'i={i}'
|
|
# Remove the terminating prompt.
|
|
# Everything preceding it is the response.
|
|
return response.decode('utf-8').split('\n')[:-1]
|
|
|
|
class LsItem:
|
|
def __init__(self, num_children, name, index, type_, value_text):
|
|
self.num_children = num_children
|
|
self.name = name
|
|
self.index = index
|
|
self.type_ = type_
|
|
self.value_text = value_text
|
|
# Convert to correct type; type_ is originally from
|
|
# flightgear/src/Network/props.cxx:getValueTypeString().
|
|
#
|
|
if type_ in ('unknown', 'unspecified', 'none'):
|
|
value = value_text
|
|
elif type_ == 'bool':
|
|
value = (value_text == 'true')
|
|
elif type_ in ('int', 'long'):
|
|
value = int(value_text)
|
|
elif type_ in ('float', 'double'):
|
|
self.value = float(value_text)
|
|
elif type_ == 'string':
|
|
self.value = value_text
|
|
else:
|
|
assert 0, f'Unrecognised type: {type_}'
|
|
|
|
def __str__(self):
|
|
return f'num_children={self.num_children} name={self.name}[{self.index}] type={self.type_}: {self.value!r}'
|
|
|
|
class FlightGear:
|
|
"""FlightGear interface class.
|
|
|
|
An instance of this class represents a connection to a FlightGear telnet
|
|
server.
|
|
|
|
Properties are accessed using a dictionary style interface:
|
|
For example:
|
|
|
|
# Connect to flightgear telnet server.
|
|
fg = FlightGear('myhost', 5500)
|
|
# parking brake on
|
|
fg['/controls/gear/brake-parking'] = 1
|
|
# Get current heading
|
|
heading = fg['/orientation/heading-deg']
|
|
|
|
Other non-property related methods
|
|
"""
|
|
|
|
def __init__( self, host = 'localhost', port = 5500 ):
|
|
try:
|
|
self.telnet = FGTelnet(host,port)
|
|
except socket.error as msg:
|
|
self.telnet = None
|
|
raise msg
|
|
|
|
def __del__(self):
|
|
# Ensure telnet connection is closed cleanly.
|
|
self.quit()
|
|
|
|
def __getitem__(self,key):
|
|
"""Get a FlightGear property value.
|
|
Where possible the value is converted to the equivalent Python type.
|
|
"""
|
|
s = self.telnet.get(key)[0]
|
|
match = re.compile( r'[^=]*=\s*\'([^\']*)\'\s*([^\r]*)\r').match( s )
|
|
if not match:
|
|
return None
|
|
value,type = match.groups()
|
|
#value = match.group(1)
|
|
#type = match.group(2)
|
|
if value == '':
|
|
return None
|
|
|
|
if type == '(double)':
|
|
return float(value)
|
|
elif type == '(int)':
|
|
return int(value)
|
|
elif type == '(bool)':
|
|
if value == 'true':
|
|
return 1
|
|
else:
|
|
return 0
|
|
else:
|
|
return value
|
|
|
|
def __setitem__(self, key, value):
|
|
"""Set a FlightGear property value."""
|
|
if value is True:
|
|
# Flightgear props doesn't treat string 'True' as true - see
|
|
# SGPropertyNode::setStringValue().
|
|
value = 'true'
|
|
self.telnet.set( key, value )
|
|
|
|
def ls(self, dir_):
|
|
'''
|
|
Returns list of LsItem's.
|
|
'''
|
|
lines = self.telnet.ls2(dir_)
|
|
ret = []
|
|
for line in lines:
|
|
if line.endswith('\r'):
|
|
line = line[:-1]
|
|
#print(f'line={line!r}')
|
|
try:
|
|
num_children, name, index, type_, value = line.split(' ', 4)
|
|
except Exception as e:
|
|
print(f'*** dir_={dir_!r} len(lines)={len(lines)}. failed to read items from line={line!r}. lines is: {lines!r}')
|
|
raise
|
|
index = int(index)
|
|
num_children = int(num_children)
|
|
item = LsItem(num_children, name, index, type_, value)
|
|
#print(f'item={item}')
|
|
ret.append( item)
|
|
return ret
|
|
|
|
def quit(self):
|
|
"""Close the telnet connection to FlightGear."""
|
|
if self.telnet:
|
|
self.telnet.quit()
|
|
self.telnet = None
|
|
|
|
def view_next(self):
|
|
"""Move to next view."""
|
|
self.telnet.set( "/command/view/next", "true")
|
|
|
|
def view_prev(self):
|
|
"""Move to next view."""
|
|
self.telnet.set( "/command/view/prev", "true")
|
|
|