Skip to content

Commit f821bef

Browse files
committed
prelim callback system impl
1 parent 9ea92d7 commit f821bef

4 files changed

Lines changed: 110 additions & 6 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from pymavlink import mavutil
2+
3+
class Callback():
4+
def __init__(
5+
self,
6+
name: str,
7+
trigger_message_type: str,
8+
trigger_condition: function = (lambda curr_msg, prev_msg: True),
9+
payload: function = (lambda msg, conn, state: print(msg)),
10+
only_once: bool = True
11+
):
12+
"""
13+
A callback instance. Callbacks are composed of two components - the trigger, which controls
14+
when and how the callback fires, and the payload, which contains the code to run.
15+
16+
Parameters:
17+
`name`: A name to help disambiguate the callback.
18+
`trigger_message_type`: mavlink message type that triggers the callback,
19+
e.g. `'GLOBAL_POSITION_INT'`
20+
`trigger_condition`: a function that should take in mavlink messages of the designated
21+
trigger type and return a boolean that indicates whether or not the callback should
22+
fire on that message.
23+
Also has access to the previous message of that time seen by the callback system. An
24+
example of how this can be used is triggering specifically on the transition between
25+
waypoints 2 -> 3.
26+
Functions must be able to handle cases where prev_msg is `None`.
27+
If unspecified, a function that will always return `True` will be used - i.e., the
28+
callback will fire on every message of the correct type.
29+
`payload`: a function that contains the desired logic to run when the callback fires.
30+
Has access to the message it was triggered on, the `mavlink_connection` object, and
31+
a state dictionary owned by the MPS server instance itself (for passing information
32+
back to the wider server context.)
33+
If unspecified, a default function will be used that simply prints the message.
34+
`only_once`: indicates that the callback should only execute once, i.e. it will be removed
35+
after being triggered for the first and only time.
36+
"""
37+
38+
self.name = name
39+
self.trigger_message_type = trigger_message_type
40+
self.trigger_condition = trigger_condition
41+
self.payload = payload
42+
self.only_once = only_once
43+
44+
class CallbackSystem():
45+
def __init__(self, mav_connection: mavutil.mavfile, state: dict):
46+
self.conn = mav_connection
47+
self.state = state
48+
49+
self.callbacks: dict[str, list[Callback]] = {}
50+
self.prev_messages: dict[str, object] = {}
51+
52+
def update_and_check(self, latest_messages: dict):
53+
# traverse through all callbacks and check each of their triggers
54+
for callback_type, callback_list in self.callbacks.items():
55+
prev_msg = self.prev_messages.get(callback_type, None)
56+
curr_msg = latest_messages.get(callback_type, None)
57+
58+
if curr_msg == None:
59+
# no point testing
60+
continue
61+
62+
for callback in callback_list:
63+
if callback.trigger_condition(curr_msg, prev_msg):
64+
65+
if callback.only_once:
66+
# remove callback before firing it
67+
self.callbacks[callback_type].remove(callback)
68+
69+
# fire callback
70+
callback.payload(curr_msg, self.conn, self.state)
71+
72+
print(f"DEBUG: Fired Callback {callback.name}")
73+
74+
# update prev_messages
75+
self.prev_messages.update(latest_messages)
76+
77+
def register_callback(self, callback: Callback):
78+
self.callbacks[callback.trigger_message_type].append(callback)
79+
80+
# def unregister_callback(self, name: str, trigger_message_type: int):
81+
# pass

src_pymav/server/features/aeac_water_delivery.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from server.common.wpqueue import WaypointQueue, Waypoint
2+
from server.common.callback import CallbackSystem, Callback
23
from server.operations.rc_channel_cmd import send_rc_channel_value
34
from pymavlink import mavutil
45

@@ -14,6 +15,7 @@
1415
'''
1516
def generate_water_wps(
1617
mav_connection: mavutil.mavfile,
18+
callback_sys: CallbackSystem,
1719
current_alt: float,
1820
deliver_alt: float,
1921
deliver_duration_secs: int,
@@ -32,6 +34,16 @@ def generate_water_wps(
3234
)
3335

3436
# TODO how do we send a message here?
37+
38+
# TODO test: setting up a callback to trigger on waypoint 2
39+
callback_sys.register_callback(Callback(
40+
"Water Delivery Callback",
41+
'MISSION_CURRENT',
42+
lambda curr_msg, prev_msg: (curr_msg.seq == 2),
43+
lambda msg, conn, state: send_payload_command(conn, 0, 'TODO'), # TODO !!!
44+
True
45+
))
46+
3547
wp_2 = Waypoint(
3648
"stay",
3749
"curr_wp",

src_pymav/server/httpserver.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818
from server.common.wpqueue import WaypointQueue, Waypoint
1919
from server.common.status import Status
2020
from server.common.encoders import command_string_to_int, command_int_to_string
21+
from server.common.callback import CallbackSystem
2122

2223

2324
class HTTP_Server:
2425
def __init__(self, mav_connection):
2526
self.mav_connection: mavfile = mav_connection
27+
self.miscellaneous_state = {}
28+
29+
self.callback_sys = CallbackSystem(self.mav_connection, self.miscellaneous_state)
30+
31+
# TODO Handle Camera Protocol via Callbacks?
2632

2733
def serve_forever(self, production=True, HOST="localhost", PORT=9000):
2834
print("GCOM HTTP Server starting...")
@@ -36,7 +42,7 @@ def index():
3642

3743
@app.route("/queue", methods=["GET"])
3844
def get_queue():
39-
curr = get_status(self.mav_connection)._wpn
45+
curr = get_status(self.mav_connection, self.callback_sys)._wpn
4046
wpq = get_current_mission(self.mav_connection)
4147

4248
formatted = []
@@ -56,7 +62,7 @@ def get_queue():
5662
def post_queue():
5763
payload = request.get_json()
5864

59-
ret = get_status(self.mav_connection)
65+
ret = get_status(self.mav_connection, self.callback_sys)
6066
last_altitude = ret.as_dictionary().get("altitude", 50)
6167

6268
wpq = []
@@ -103,7 +109,7 @@ def post_queue():
103109
def post_insert_wp():
104110
payload = request.get_json()
105111

106-
ret: Status = get_status(self.mav_connection)
112+
ret: Status = get_status(self.mav_connection, self.callback_sys)
107113
last_altitude = ret._alt if ret != () else 50
108114

109115
curr = max(ret._wpn, 1)
@@ -165,7 +171,7 @@ def get_clear_queue():
165171
@app.route("/status", methods=["GET"])
166172
def get_status_handler():
167173
print("Status sent to GCOM")
168-
s = get_status(self.mav_connection).as_dictionary()
174+
s = get_status(self.mav_connection, self.callback_sys).as_dictionary()
169175
return s, 200
170176

171177
@app.route("/takeoff", methods=["POST"])
@@ -330,7 +336,7 @@ def deliver_water_down():
330336
deliver_duration_secs = input["deliver_duration_secs"]
331337
curr_lat = input["curr_lat"]
332338
curr_lon = input["curr_lon"]
333-
wpq = generate_water_wps(self.mav_connection, current_alt, deliver_alt, deliver_duration_secs, curr_lat, curr_lon)
339+
wpq = generate_water_wps(self.mav_connection, self.callback_sys, current_alt, deliver_alt, deliver_duration_secs, curr_lat, curr_lon)
334340

335341

336342
if new_mission(self.mav_connection, wpq):

src_pymav/server/operations/get_info.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
from server.common.status import Status
66
from server.common.wpqueue import WaypointQueue, Waypoint
77
from server.common.encoders import command_int_to_string
8+
from server.common.callback import CallbackSystem
89
from server.utilities.request_message_streaming import request_messages
910

11+
1012
"""
1113
Get current status of a drone
1214
Type of message can be found on https://mavlink.io/en/messages/common.html
1315
1416
"""
15-
def get_status(mav_connection: mavutil.mavfile) -> Status:
17+
def get_status(mav_connection: mavutil.mavfile, callback_sys: CallbackSystem = None) -> Status:
1618

1719
# trigger an update
1820
# mav_connection.recv_match(blocking=True)
@@ -57,6 +59,9 @@ def get_status(mav_connection: mavutil.mavfile) -> Status:
5759
winddirection = math.degrees(math.atan(status_wind.wind_x / status_wind.wind_y)) if status_wind.wind_y != 0 else (0 if status_wind.wind_x > 0 else 180)
5860
windvelocity = math.sqrt(status_wind.wind_x * status_wind.wind_x + status_wind.wind_y * status_wind.wind_y)
5961

62+
# trigger / callback mechanism
63+
callback_sys.update_and_check(mav_connection.messages)
64+
6065
return Status(
6166
system_time.time_unix_usec / 1000000, # seconds
6267

0 commit comments

Comments
 (0)