Skip to content

Commit 2f4795a

Browse files
Copilotmsis
andcommitted
Add CMOOSApp wrapper with tests and examples
Co-authored-by: msis <577139+msis@users.noreply.github.com>
1 parent c713fe3 commit 2f4795a

3 files changed

Lines changed: 383 additions & 0 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import pymoos
2+
import time
3+
4+
# A simple example using the CMOOSApp wrapper:
5+
# a) we make an app object and set up callbacks for the application lifecycle
6+
# b) we run the app which starts the main loop
7+
# The app class provides a more structured approach than comms, with separate
8+
# callbacks for startup, iteration, and mail handling
9+
10+
app = pymoos.app()
11+
12+
# OnStartUp is called when the app first starts
13+
def on_startup():
14+
print("App starting up...")
15+
app.set_app_freq(1.0) # Set how often Iterate is called (Hz)
16+
app.set_comms_freq(5.0) # Set how often comms happen (Hz)
17+
return True
18+
19+
# OnConnectToServer is called when connection to MOOSDB is established
20+
def on_connect_to_server():
21+
print("Connected to MOOSDB, registering for variables...")
22+
return app.register('simple_app_var', 0)
23+
24+
# OnNewMail is called when new mail arrives
25+
def on_new_mail(mail):
26+
print(f"Received {len(mail)} messages:")
27+
for msg in mail:
28+
msg.trace()
29+
return True
30+
31+
# Iterate is the main work loop, called at the frequency set by set_app_freq
32+
iteration_count = 0
33+
def iterate():
34+
global iteration_count
35+
iteration_count += 1
36+
print(f"Iterate #{iteration_count}")
37+
38+
# Publish a message
39+
app.notify('simple_app_var', f'iteration {iteration_count}', pymoos.time())
40+
41+
# Run for 10 iterations then stop
42+
if iteration_count >= 10:
43+
return False
44+
45+
return True
46+
47+
def main():
48+
# Set up the callbacks
49+
app.set_on_start_up_callback(on_startup)
50+
app.set_on_connect_to_server_callback(on_connect_to_server)
51+
app.set_on_new_mail_callback(on_new_mail)
52+
app.set_iterate_callback(iterate)
53+
54+
# Run the application (this blocks until iterate returns False)
55+
app.run('localhost', 9000, 'pymoos_simple_app')
56+
57+
if __name__ == "__main__":
58+
main()

src/pyMOOS.cpp

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "MOOS/libMOOS/Comms/MOOSMsg.h"
77
#include "MOOS/libMOOS/Comms/MOOSCommClient.h"
88
#include "MOOS/libMOOS/Comms/MOOSAsyncCommClient.h"
9+
#include "MOOS/libMOOS/App/MOOSApp.h"
910

1011
#include <pybind11/pybind11.h>
1112
#include <pybind11/stl_bind.h>
@@ -215,6 +216,159 @@ class AsyncCommsWrapper : public MOOS::MOOSAsyncCommClient {
215216
/** close connection flag */
216217
bool closing_;
217218
};
219+
220+
/** this is a class which wraps CMOOSApp to provide
221+
* an interface more suitable for python wrapping
222+
*/
223+
class AppWrapper : public CMOOSApp {
224+
private:
225+
typedef CMOOSApp BASE;
226+
public:
227+
228+
~AppWrapper(){
229+
// Clean shutdown
230+
}
231+
232+
bool Run(const std::string & sServer, int Port, const std::string & sMyName, const std::string & sMissionFile = "") {
233+
SetAppName(sMyName);
234+
SetServer(sServer, Port);
235+
if (!sMissionFile.empty()) {
236+
SetMissionFile(sMissionFile);
237+
}
238+
return BASE::Run();
239+
}
240+
241+
//we can support vectors of objects by not lists so
242+
//here we have a function which copies a list to vector
243+
MsgVector FetchMailAsVector() {
244+
MsgVector v;
245+
MOOSMSG_LIST M;
246+
if (Fetch(M)) {
247+
std::copy(M.begin(), M.end(), std::back_inserter(v));
248+
}
249+
return v;
250+
}
251+
252+
/* python strings can be binary lets make this specific*/
253+
bool NotifyBinary(const std::string& sKey, const std::string & sData,
254+
double dfTime) {
255+
CMOOSMsg M(MOOS_NOTIFY, sKey, sData.size(), (void *) sData.data(),
256+
dfTime);
257+
return BASE::m_Comms.Post(M);
258+
}
259+
260+
// Virtual methods that can be overridden
261+
bool OnNewMail(MOOSMSG_LIST &NewMail) override {
262+
bool bResult = true;
263+
264+
if (on_new_mail_object_) {
265+
PyGILState_STATE gstate = PyGILState_Ensure();
266+
try {
267+
MsgVector v;
268+
std::copy(NewMail.begin(), NewMail.end(), std::back_inserter(v));
269+
py::object result = on_new_mail_object_(v);
270+
bResult = py::bool_(result);
271+
} catch (const py::error_already_set& e) {
272+
PyGILState_Release(gstate);
273+
std::string err_msg = "OnNewMail:: caught an exception thrown in python callback:\n";
274+
err_msg.append(e.what());
275+
throw pyMOOSException(err_msg.c_str());
276+
}
277+
PyGILState_Release(gstate);
278+
}
279+
280+
return bResult;
281+
}
282+
283+
bool OnConnectToServer() override {
284+
bool bResult = true;
285+
286+
if (on_connect_to_server_object_) {
287+
PyGILState_STATE gstate = PyGILState_Ensure();
288+
try {
289+
py::object result = on_connect_to_server_object_();
290+
bResult = py::bool_(result);
291+
} catch (const py::error_already_set& e) {
292+
PyGILState_Release(gstate);
293+
std::string err_msg = "OnConnectToServer:: caught an exception thrown in python callback:\n";
294+
err_msg.append(e.what());
295+
throw pyMOOSException(err_msg.c_str());
296+
}
297+
PyGILState_Release(gstate);
298+
}
299+
300+
return bResult;
301+
}
302+
303+
bool OnStartUp() override {
304+
bool bResult = true;
305+
306+
if (on_start_up_object_) {
307+
PyGILState_STATE gstate = PyGILState_Ensure();
308+
try {
309+
py::object result = on_start_up_object_();
310+
bResult = py::bool_(result);
311+
} catch (const py::error_already_set& e) {
312+
PyGILState_Release(gstate);
313+
std::string err_msg = "OnStartUp:: caught an exception thrown in python callback:\n";
314+
err_msg.append(e.what());
315+
throw pyMOOSException(err_msg.c_str());
316+
}
317+
PyGILState_Release(gstate);
318+
}
319+
320+
return bResult;
321+
}
322+
323+
bool Iterate() override {
324+
bool bResult = true;
325+
326+
if (iterate_object_) {
327+
PyGILState_STATE gstate = PyGILState_Ensure();
328+
try {
329+
py::object result = iterate_object_();
330+
bResult = py::bool_(result);
331+
} catch (const py::error_already_set& e) {
332+
PyGILState_Release(gstate);
333+
std::string err_msg = "Iterate:: caught an exception thrown in python callback:\n";
334+
err_msg.append(e.what());
335+
throw pyMOOSException(err_msg.c_str());
336+
}
337+
PyGILState_Release(gstate);
338+
}
339+
340+
return bResult;
341+
}
342+
343+
// Setters for Python callbacks
344+
bool SetOnNewMailCallback(py::object func) {
345+
on_new_mail_object_ = func;
346+
return true;
347+
}
348+
349+
bool SetOnConnectToServerCallback(py::object func) {
350+
on_connect_to_server_object_ = func;
351+
return true;
352+
}
353+
354+
bool SetOnStartUpCallback(py::object func) {
355+
on_start_up_object_ = func;
356+
return true;
357+
}
358+
359+
bool SetIterateCallback(py::object func) {
360+
iterate_object_ = func;
361+
return true;
362+
}
363+
364+
private:
365+
/** callback functions (stored) */
366+
py::object on_new_mail_object_;
367+
py::object on_connect_to_server_object_;
368+
py::object on_start_up_object_;
369+
py::object iterate_object_;
370+
};
371+
218372
}
219373
;//namesapce
220374

@@ -504,6 +658,92 @@ PYBIND11_MODULE(pymoos, m) {
504658
;
505659

506660

661+
/*********************************************************************
662+
CMOOSApp base class
663+
*********************************************************************/
664+
665+
py::class_<CMOOSApp>(m, "base_app")
666+
.def("register",static_cast<bool(CMOOSApp::*)
667+
(const std::string&, double)> (&CMOOSApp::Register),
668+
"Register for notification in changes of named variable.",
669+
py::arg("name"), py::arg("interval") = 0)
670+
.def("notify",static_cast<bool(CMOOSApp::*)
671+
(const std::string&,
672+
const std::string&, double)> (&CMOOSApp::Notify),
673+
"Notify the MOOS community that something has changed.",
674+
py::arg("name"), py::arg("value"), py::arg("time") = -1.)
675+
.def("notify",static_cast<bool(CMOOSApp::*)
676+
(const std::string&, double,
677+
double)> (&CMOOSApp::Notify),
678+
"Notify the MOOS community that something has changed.",
679+
py::arg("name"), py::arg("value"), py::arg("time") = -1.)
680+
.def("get_app_name", &CMOOSApp::GetAppName,
681+
"Return the name of the application.")
682+
.def("get_community_name", &CMOOSApp::GetCommunityName,
683+
"Return name of community the app is attached to.")
684+
.def("is_connected", &CMOOSApp::IsConnected,
685+
"Return True if this app is connected to the server.")
686+
.def("is_registered_for", &CMOOSApp::IsRegisteredFor,
687+
"Return True if we are registered for name variable")
688+
.def("set_app_freq", &CMOOSApp::SetAppFreq,
689+
"Set the frequency at which Iterate() is called.",
690+
py::arg("freq"))
691+
.def("get_app_freq", &CMOOSApp::GetAppFreq,
692+
"Get the frequency at which Iterate() is called.")
693+
.def("set_comms_freq", &CMOOSApp::SetCommsFreq,
694+
"Set the frequency at which communications occur.",
695+
py::arg("freq"))
696+
.def("get_comms_freq", &CMOOSApp::GetCommsFreq,
697+
"Get the frequency at which communications occur.")
698+
;
699+
700+
701+
/*********************************************************************/
702+
/** CMOOSApp WRAPPER CLASS FOR PYTHON */
703+
/*********************************************************************/
704+
705+
py::class_<MOOS::AppWrapper, CMOOSApp>(m, "app")
706+
.def(py::init<>())
707+
708+
.def("run", &MOOS::AppWrapper::Run,
709+
"Run the MOOS application.",
710+
py::arg("server"), py::arg("port"), py::arg("name"),
711+
py::arg("mission_file") = "")
712+
713+
.def("fetch", &MOOS::AppWrapper::FetchMailAsVector,
714+
"Fetch incoming mail as a vector.")
715+
716+
.def("notify_binary", &MOOS::AppWrapper::NotifyBinary,
717+
"Notify binary data. (specific to pymoos.)",
718+
py::arg("name"), py::arg("binary_data"), py::arg("time")=-1)
719+
720+
.def("set_on_new_mail_callback",
721+
&MOOS::AppWrapper::SetOnNewMailCallback,
722+
"Set the callback to be invoked when new mail arrives. "
723+
"The callback will be passed a list of messages.",
724+
py::arg("func"))
725+
726+
.def("set_on_connect_to_server_callback",
727+
&MOOS::AppWrapper::SetOnConnectToServerCallback,
728+
"Set the callback to be invoked when connected to the server. "
729+
"This is a good place to register for notifications.",
730+
py::arg("func"))
731+
732+
.def("set_on_start_up_callback",
733+
&MOOS::AppWrapper::SetOnStartUpCallback,
734+
"Set the callback to be invoked at application startup. "
735+
"This is called before connecting to the server.",
736+
py::arg("func"))
737+
738+
.def("set_iterate_callback",
739+
&MOOS::AppWrapper::SetIterateCallback,
740+
"Set the callback to be invoked at each iteration. "
741+
"This is the main work loop of the application.",
742+
py::arg("func"))
743+
744+
;
745+
746+
507747
/*********************************************************************
508748
some MOOS Utilities
509749
*********************************************************************/

0 commit comments

Comments
 (0)