Skip to content

Commit e294d1c

Browse files
committed
more example of use
1 parent 24b6ad6 commit e294d1c

4 files changed

Lines changed: 199 additions & 19 deletions

File tree

examples/fdr.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,22 +108,23 @@ def __init__(self, api, filename: str = FDR_FILENAME, frequency: float = WRITE_F
108108
self.file = None
109109
self.writes = 0
110110
self.write_thread = threading.Thread(target=self.write, name="FDR Data Writer")
111-
self.datarefs = {path: self.ws.dataref(path) for path in self.get_dataref_names()}
111+
self.datarefs = {}
112112
self.optional_datarefs: Dict[str, xpwebapi.Dataref] = {}
113-
self.init()
114113

115-
def init(self):
114+
def set_api(self, api):
115+
self.ws = api
116116
self.ws.add_callback(cbtype=xpwebapi.CALLBACK_TYPE.ON_DATAREF_UPDATE, callback=self.dataref_changed)
117+
self.datarefs = {path: self.ws.dataref(path) for path in self.get_dataref_names()}
117118

118119
@property
119120
def header_ok(self) -> bool:
120121
return all(self.header.values())
121122

122-
def start(self):
123-
ws.connect()
124-
ws.wait_connection()
125-
ws.monitor_datarefs(datarefs=self.datarefs, reason="Flight data recorder")
126-
ws.start()
123+
def run(self):
124+
self.ws.connect()
125+
self.ws.wait_connection()
126+
self.ws.monitor_datarefs(datarefs=self.datarefs, reason="Flight data recorder")
127+
self.ws.start()
127128

128129
def get_dataref_names(self) -> set:
129130
return HEADER | set(FDR_DATA) | set(FDR_OPTIONAL)
@@ -241,10 +242,10 @@ def terminate(self):
241242

242243

243244
if __name__ == "__main__":
244-
ws = xpwebapi.ws_api() # host="192.168.1.141", port=8080)
245+
ws = xpwebapi.ws_api() # host="192.168.1.141", port=8080)
245246
fdr = FDR(ws, frequency=1.0)
246247
try:
247-
fdr.start()
248+
fdr.run()
248249
except KeyboardInterrupt:
249250
logger.warning("terminating..", exc_info=True)
250251
fdr.terminate()

examples/oooi.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def __init__(self, api, departure: str, arrival: str, callsign: str, logon: str,
8080
self.name = "OOOI"
8181
self.ws = api
8282

83-
self.datarefs = {path: self.ws.dataref(path) for path in self.get_dataref_names()}
83+
self.datarefs = {}
8484

8585
self.departure = departure
8686
self.arrival = arrival
@@ -104,13 +104,16 @@ def __init__(self, api, departure: str, arrival: str, callsign: str, logon: str,
104104
# debug
105105
self._onblock = False
106106

107+
def set_api(self, api):
108+
self.ws = api
109+
self.datarefs = {path: self.ws.dataref(path) for path in self.get_dataref_names()}
107110
self.ws.add_callback(cbtype=xpwebapi.CALLBACK_TYPE.ON_DATAREF_UPDATE, callback=self.dataref_changed)
108111

109-
def start(self):
110-
ws.connect()
111-
ws.wait_connection()
112-
ws.monitor_datarefs(datarefs=self.datarefs, reason=self.name)
113-
ws.start()
112+
def run(self):
113+
self.ws.connect()
114+
self.ws.wait_connection()
115+
self.ws.monitor_datarefs(datarefs=self.datarefs, reason=self.name)
116+
self.ws.start()
114117

115118
@property
116119
def oooi(self) -> OOOI | None:
@@ -175,6 +178,16 @@ def report(self, display: bool = True) -> str:
175178
Returns:
176179
str: string with all values
177180
"""
181+
def strfdelta(tdelta):
182+
ret = ""
183+
if tdelta.days > 0:
184+
ret = f"{tdelta.days} d "
185+
h, rem = divmod(tdelta.seconds, 3600)
186+
ret = ret + f"{h:02d}"
187+
m, s = divmod(rem, 60)
188+
ret = ret + f"{m:02d}{s:02d}:"
189+
return ret
190+
178191
TIME_FMT = "%H%M"
179192

180193
def pt(ts: datetime | None):
@@ -212,8 +225,21 @@ def pt(ts: datetime | None):
212225
report = report + " IN/----"
213226
if self.eta is not None:
214227
report = report + f" ETA/{pt(self.eta)}"
228+
229+
time_info = ""
230+
if self.all_oooi.get(OOOI.OFF) is not None and self.all_oooi.get(OOOI.ON) is not None:
231+
flight_time = self.all_oooi.get(OOOI.ON) - self.all_oooi.get(OOOI.OFF)
232+
time_info = f"flight time: {strfdelta(flight_time)}"
233+
if self.all_oooi.get(OOOI.OUT) is not None and self.all_oooi.get(OOOI.IN) is not None:
234+
block_time = self.all_oooi.get(OOOI.IN) - self.all_oooi.get(OOOI.OUT)
235+
if time_info != "":
236+
time_info = time_info = ", "
237+
time_info = time_info + f"block time: {strfdelta(block_time)}"
238+
215239
if display:
216240
logger.info(report)
241+
if time_info != "":
242+
logger.info(time_info)
217243
return report
218244

219245
def acars_report(self) -> Dict:
@@ -413,7 +439,7 @@ def terminate(self):
413439
oooi = OOOIManager(ws, departure="EBCI", arrival="EBBR", callsign="BEL034", logon="none", station="EBJA")
414440
try:
415441
oooi.set_eta(now() + timedelta(minutes=30))
416-
oooi.start()
442+
oooi.run()
417443
except KeyboardInterrupt:
418444
logger.warning("terminating..")
419445
oooi.terminate()

examples/posreport.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""Send position report periodically
2+
3+
Extrernal (to X-Plane) application to detect OOOI ACARS message changes and generate appropriate message.
4+
5+
/data2/19//N/POSITION REPORT OVHD HABBS AT 1249Z/18700 PPOS:4512.2N/07425.3W AT 1249Z/18700 TO COMAU AT 1252Z NEXT MITIG WIND 325/23 SAT -20 ETA 1304Z SPEED 265 GND SPEED 354 VERT SPEED -2000FPM HDG 68 TRK 71
6+
/data2/18//N/POSITION REPORT OVHD ARVIE AT 1247Z/FL221 PPOS:4507.0N/07437.2W AT 1247Z/FL221 TO HABBS AT 1249Z NEXT COMAU WIND 350/21 SAT -27 ETA 1304Z SPEED 260 GND SPEED 357 VERT SPEED -1900FPM HDG 69 TRK 73
7+
8+
"""
9+
10+
import logging
11+
import os
12+
from re import DEBUG
13+
import sys
14+
import threading
15+
from datetime import datetime, timezone
16+
from enum import StrEnum
17+
from typing import Dict, Any, Tuple
18+
19+
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
20+
21+
from lat_lon_parser import to_deg_min
22+
from unitutil import convert
23+
24+
import xpwebapi
25+
26+
FORMAT = "[%(asctime)s] %(levelname)s %(threadName)s %(filename)s:%(funcName)s:%(lineno)d: %(message)s"
27+
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt="%H:%M:%S")
28+
29+
logging.basicConfig(level=logging.INFO)
30+
logger = logging.getLogger(__name__)
31+
logger.setLevel(logging.INFO)
32+
33+
34+
version = "1.0.0"
35+
36+
37+
class DREFS(StrEnum):
38+
DAYS = "sim/time/local_date_days"
39+
ZULU_SECS = "sim/time/zulu_time_sec"
40+
GROUND_SPEED = "sim/flightmodel2/position/groundspeed"
41+
AGL = "sim/flightmodel/position/y_agl"
42+
ALT = "sim/cockpit/pressure/cabin_altitude_actual_ft"
43+
TRK = "sim/cockpit2/gauges/indicators/ground_track_mag_pilot" # The ground track of the aircraft in degrees magnetic
44+
HDG = "sim/cockpit2/gauges/indicators/compass_heading_deg_mag" # Indicated heading of the wet compass, in degrees.
45+
INDICATED_AIRSPEED = "sim/flightmodel/position/indicated_airspeed"
46+
VS = "sim/flightmodel/position/local_vy"
47+
LATITUDE = "sim/flightmodel/position/latitude"
48+
LONGITUDE = "sim/flightmodel/position/longitude"
49+
WINDDIR = "sim/weather/aircraft/wind_now_direction_degt"
50+
WINDSPD = "sim/weather/aircraft/wind_speed_kts"
51+
WINDSPD_M = "sim/weather/aircraft/wind_now_speed_msc"
52+
AIR_TEMP = "sim/weather/aircraft/temperature_ambient_deg_c"
53+
54+
55+
def now() -> datetime:
56+
return datetime.now(timezone.utc)
57+
58+
59+
class PositionReport:
60+
61+
def __init__(self, api, frequency: int, callsign: str, logon: str, station: str, eta: datetime | None = None) -> None:
62+
self.name = type(self).__name__
63+
self.ws = api
64+
65+
self.datarefs = {path: self.ws.dataref(path) for path in self.get_dataref_names()}
66+
self._report_thread = threading.Thread(target=self.report_loop, name="ACARS Progress Report")
67+
self._report_run = threading.Event()
68+
self.frequency = frequency
69+
70+
self.ws.add_callback(cbtype=xpwebapi.CALLBACK_TYPE.ON_DATAREF_UPDATE, callback=self.dataref_changed)
71+
72+
def get_dataref_names(self) -> set:
73+
return DREFS
74+
75+
def dataref_changed(self, dataref, value):
76+
self.datarefs[dataref].value = value
77+
78+
def dataref_value(self, dataref: str):
79+
dref = self.datarefs.get(dataref)
80+
return dref.value if dref is not None else 0
81+
82+
def run(self):
83+
self._report_thread.start()
84+
ws.connect()
85+
ws.wait_connection()
86+
ws.monitor_datarefs(datarefs=self.datarefs, reason=self.name)
87+
self._report_thread.start()
88+
ws.start()
89+
90+
def terminate(self):
91+
self._report_run.set()
92+
ws.unmonitor_datarefs(datarefs=self.datarefs, reason=self.name)
93+
self.ws.disconnect()
94+
95+
def report(self) -> str:
96+
def f(dref: str, rnd: int = 0) -> int | float:
97+
val = self.dataref_value(dataref=dref)
98+
if val is not None:
99+
return int(val) if rnd == 0 else round(val, rnd)
100+
return 0
101+
102+
lat = self.dataref_value(DREFS.LATITUDE)
103+
ldeg, lmin = to_deg_min(lat)
104+
latstr = f"{ldeg:02d}{lmin:04.1f}{'N' if ldeg >=0 else 'S'}"
105+
lon = self.dataref_value(DREFS.LONGITUDE)
106+
ldeg, lmin = to_deg_min(lon)
107+
lonstr = f"{ldeg:03d}{lmin:04.1f}{'E' if ldeg >=0 else 'W'}"
108+
109+
zulustr = now().strftime("%H%M")
110+
111+
vs = self.dataref_value(DREFS.VS)
112+
vs = convert.ms_to_fpm(ms=vs)
113+
vs = round(vs/100) * 100
114+
alt = self.dataref_value(DREFS.ALT)
115+
if alt < 8000:
116+
altstr = f"{round(alt/10) * 10}"
117+
else:
118+
altstr = convert.meters_to_fl(convert.feet_to_meters(ft=alt))
119+
120+
# find weather parameters for layer
121+
wind_dir = int(self.dataref_value(DREFS.WINDDIR))
122+
wind_speed = int(self.dataref_value(DREFS.WINDSPD))
123+
sat = int(self.dataref_value(DREFS.AIR_TEMP))
124+
125+
return " ".join([
126+
"POSITION REPORT",
127+
f"PPOS:{latstr}/{lonstr} AT {zulustr}Z/{altstr}",
128+
f"WIND {wind_dir}/{wind_speed} SAT {saturation}",
129+
f"SPEED {f(DREFS.INDICATED_AIRSPEED)} GND SPEED {f(DREFS.GROUND_SPEED)} VERT SPEED {vs}FPM",
130+
f"HDG {f(DREFS.HDG)} TRK {f(DREFS.TRK)}",
131+
"PARTIAL AUTOGEN"
132+
])
133+
134+
def report_loop(self):
135+
loop = True
136+
while loop:
137+
try:
138+
logger.debug(self.report())
139+
except:
140+
logger.warning("error producing report")
141+
if self._report_run.wait(self.frequency):
142+
loop = False
143+
144+
145+
if __name__ == "__main__":
146+
ws = xpwebapi.ws_api()
147+
pr = PositionReport(ws, frequency=5, callsign="BEL034", logon="none", station="EBJA")
148+
try:
149+
pr.run()
150+
except KeyboardInterrupt:
151+
logger.warning("terminating..")
152+
pr.terminate()
153+
logger.warning("..terminated")

examples/xgs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ def dataref_changed(self, dataref, value):
463463
self.last_grounded = False
464464
self.state = ALTITUDE.ALT_HIGH
465465

466-
if 300 < value <= 1000: # should have a short list of runway targets
466+
if 300 < value <= 1000: # should have a short list of runway targets
467467
self._air_time = True
468468
if self.ensure_below("alt1000", threshold=1000, value=value, count=20) == 0:
469469
self.state = ALTITUDE.ALT1000M
@@ -475,7 +475,7 @@ def dataref_changed(self, dataref, value):
475475
s = set([d.airport_ident for d in self._runways_shortlist])
476476
logger.info(f"airport short list {', '.join(s)}")
477477

478-
if LOW_ALTITUDE < value <= 300: # should have a runway target
478+
if LOW_ALTITUDE < value <= 300: # should have a runway target
479479
self.last_grounded = False
480480
self._air_time = True
481481
if self.ensure_below("alt300", threshold=300, value=value, count=20) == 0:

0 commit comments

Comments
 (0)