You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

lib.rs 6.5 kB

3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #![allow(clippy::borrow_deref_ref)] // clippy warns about code generated by #[pymethods]
  2. use arrow::pyarrow::{FromPyArrow, ToPyArrow};
  3. use dora_node_api::merged::{MergeExternalSend, MergedEvent};
  4. use dora_node_api::{DoraNode, EventStream};
  5. use dora_operator_api_python::{pydict_to_metadata, PyEvent};
  6. use dora_ros2_bridge_python::Ros2Subscription;
  7. use eyre::Context;
  8. use futures::{Stream, StreamExt};
  9. use pyo3::prelude::*;
  10. use pyo3::types::{PyBytes, PyDict};
  11. /// The custom node API lets you integrate `dora` into your application.
  12. /// It allows you to retrieve input and send output in any fashion you want.
  13. ///
  14. /// Use with:
  15. ///
  16. /// ```python
  17. /// from dora import Node
  18. ///
  19. /// node = Node()
  20. /// ```
  21. ///
  22. #[pyclass]
  23. pub struct Node {
  24. events: Events,
  25. node: DoraNode,
  26. }
  27. #[pymethods]
  28. impl Node {
  29. #[new]
  30. pub fn new() -> eyre::Result<Self> {
  31. let (node, events) = DoraNode::init_from_env()?;
  32. Ok(Node {
  33. events: Events::Dora(events),
  34. node,
  35. })
  36. }
  37. /// `.next()` gives you the next input that the node has received.
  38. /// It blocks until the next event becomes available.
  39. /// It will return `None` when all senders has been dropped.
  40. ///
  41. /// ```python
  42. /// event = node.next()
  43. /// ```
  44. ///
  45. /// You can also iterate over the event stream with a loop
  46. ///
  47. /// ```python
  48. /// for event in node:
  49. /// match event["type"]:
  50. /// case "INPUT":
  51. /// match event["id"]:
  52. /// case "image":
  53. /// ```
  54. #[allow(clippy::should_implement_trait)]
  55. pub fn next(&mut self, py: Python) -> PyResult<Option<PyEvent>> {
  56. self.__next__(py)
  57. }
  58. pub fn __next__(&mut self, py: Python) -> PyResult<Option<PyEvent>> {
  59. let event = py.allow_threads(|| self.events.recv());
  60. Ok(event)
  61. }
  62. fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
  63. slf
  64. }
  65. /// `send_output` send data from the node.
  66. ///
  67. /// ```python
  68. /// Args:
  69. /// output_id: str,
  70. /// data: Bytes|Arrow,
  71. /// metadata: Option[Dict],
  72. /// ```
  73. ///
  74. /// ```python
  75. /// node.send_output("string", b"string", {"open_telemetry_context": "7632e76"})
  76. /// ```
  77. ///
  78. pub fn send_output(
  79. &mut self,
  80. output_id: String,
  81. data: PyObject,
  82. metadata: Option<&PyDict>,
  83. py: Python,
  84. ) -> eyre::Result<()> {
  85. let parameters = pydict_to_metadata(metadata)?;
  86. if let Ok(py_bytes) = data.downcast::<PyBytes>(py) {
  87. let data = py_bytes.as_bytes();
  88. self.node
  89. .send_output_bytes(output_id.into(), parameters, data.len(), data)
  90. .wrap_err("failed to send output")?;
  91. } else if let Ok(arrow_array) = arrow::array::ArrayData::from_pyarrow(data.as_ref(py)) {
  92. self.node.send_output(
  93. output_id.into(),
  94. parameters,
  95. arrow::array::make_array(arrow_array),
  96. )?;
  97. } else {
  98. eyre::bail!("invalid `data` type, must by `PyBytes` or arrow array")
  99. }
  100. Ok(())
  101. }
  102. /// Returns the full dataflow descriptor that this node is part of.
  103. ///
  104. /// This method returns the parsed dataflow YAML file.
  105. pub fn dataflow_descriptor(&self, py: Python) -> pythonize::Result<PyObject> {
  106. pythonize::pythonize(py, self.node.dataflow_descriptor())
  107. }
  108. pub fn merge_external_events(
  109. &mut self,
  110. subscription: &mut Ros2Subscription,
  111. ) -> eyre::Result<()> {
  112. let subscription = subscription.into_stream()?;
  113. let stream = futures::stream::poll_fn(move |cx| {
  114. let s = subscription.as_stream().map(|item| {
  115. match item.context("failed to read ROS2 message") {
  116. Ok((value, _info)) => Python::with_gil(|py| {
  117. value
  118. .to_pyarrow(py)
  119. .context("failed to convert value to pyarrow")
  120. .unwrap_or_else(|err| PyErr::from(err).to_object(py))
  121. }),
  122. Err(err) => Python::with_gil(|py| PyErr::from(err).to_object(py)),
  123. }
  124. });
  125. futures::pin_mut!(s);
  126. s.poll_next_unpin(cx)
  127. });
  128. // take out the event stream and temporarily replace it with a dummy
  129. let events = std::mem::replace(
  130. &mut self.events,
  131. Events::Merged(Box::new(futures::stream::empty())),
  132. );
  133. // update self.events with the merged stream
  134. self.events = Events::Merged(events.merge_external_send(Box::pin(stream)));
  135. Ok(())
  136. }
  137. }
  138. enum Events {
  139. Dora(EventStream),
  140. Merged(Box<dyn Stream<Item = MergedEvent<PyObject>> + Unpin + Send>),
  141. }
  142. impl Events {
  143. fn recv(&mut self) -> Option<PyEvent> {
  144. match self {
  145. Events::Dora(events) => events.recv().map(PyEvent::from),
  146. Events::Merged(events) => futures::executor::block_on(events.next()).map(PyEvent::from),
  147. }
  148. }
  149. }
  150. impl<'a> MergeExternalSend<'a, PyObject> for Events {
  151. type Item = MergedEvent<PyObject>;
  152. fn merge_external_send(
  153. self,
  154. external_events: impl Stream<Item = PyObject> + Unpin + Send + 'a,
  155. ) -> Box<dyn Stream<Item = Self::Item> + Unpin + Send + 'a> {
  156. match self {
  157. Events::Dora(events) => events.merge_external_send(external_events),
  158. Events::Merged(events) => {
  159. let merged = events.merge_external_send(external_events);
  160. Box::new(merged.map(|event| match event {
  161. MergedEvent::Dora(e) => MergedEvent::Dora(e),
  162. MergedEvent::External(e) => MergedEvent::External(e.flatten()),
  163. }))
  164. }
  165. }
  166. }
  167. }
  168. impl Node {
  169. pub fn id(&self) -> String {
  170. self.node.id().to_string()
  171. }
  172. }
  173. /// Start a runtime for Operators
  174. #[pyfunction]
  175. pub fn start_runtime() -> eyre::Result<()> {
  176. dora_runtime::main().wrap_err("Dora Runtime raised an error.")
  177. }
  178. #[pymodule]
  179. fn dora(py: Python, m: &PyModule) -> PyResult<()> {
  180. m.add_function(wrap_pyfunction!(start_runtime, m)?)?;
  181. m.add_class::<Node>().unwrap();
  182. let ros2_bridge = PyModule::new(py, "ros2_bridge")?;
  183. dora_ros2_bridge_python::create_dora_ros2_bridge_module(ros2_bridge)?;
  184. let experimental = PyModule::new(py, "experimental")?;
  185. experimental.add_submodule(ros2_bridge)?;
  186. m.add_submodule(experimental)?;
  187. Ok(())
  188. }

DORA (Dataflow-Oriented Robotic Architecture) is middleware designed to streamline and simplify the creation of AI-based robotic applications. It offers low latency, composable, and distributed datafl