diff --git a/crates/hiroz-codegen/src/python_msgspec_generator.rs b/crates/hiroz-codegen/src/python_msgspec_generator.rs index b439e070..708ab71f 100644 --- a/crates/hiroz-codegen/src/python_msgspec_generator.rs +++ b/crates/hiroz-codegen/src/python_msgspec_generator.rs @@ -27,6 +27,15 @@ pub fn generate_python_bindings( .push(msg); } + // Group services by package so we can emit rclpy-style grouping classes (P4). + let mut service_groups: HashMap> = HashMap::new(); + for srv in services { + service_groups + .entry(srv.parsed.package.clone()) + .or_default() + .push(srv); + } + // Group service Request/Response by package, and track service type hashes let mut service_messages: HashMap> = HashMap::new(); let mut service_hashes: HashMap> = HashMap::new(); @@ -65,11 +74,16 @@ pub fn generate_python_bindings( .get(package_name) .cloned() .unwrap_or_default(); + let srv_groups = service_groups + .get(package_name) + .map(|v| v.as_slice()) + .unwrap_or(&[]); let python_code = generate_python_package_with_services( package_name, package_msgs, srv_msgs, &svc_hashes, + srv_groups, )?; let output_path = python_output_dir.join(format!("{}.py", package_name)); fs::write(output_path, python_code)?; @@ -82,8 +96,17 @@ pub fn generate_python_bindings( .get(package_name) .cloned() .unwrap_or_default(); - let python_code = - generate_python_package_with_services(package_name, &[], srv_msgs, &svc_hashes)?; + let srv_groups = service_groups + .get(package_name) + .map(|v| v.as_slice()) + .unwrap_or(&[]); + let python_code = generate_python_package_with_services( + package_name, + &[], + srv_msgs, + &svc_hashes, + srv_groups, + )?; let output_path = python_output_dir.join(format!("{}.py", package_name)); fs::write(output_path, python_code)?; } @@ -113,6 +136,7 @@ fn generate_python_package_with_services( messages: &[&ResolvedMessage], service_messages: &[&ResolvedMessage], service_hashes: &HashMap, + service_groups: &[&ResolvedService], ) -> Result { let mut code = format!( "\"\"\"Auto-generated ROS 2 message types for {}.\"\"\"\n\ @@ -132,9 +156,33 @@ fn generate_python_package_with_services( code.push_str(&generate_msgspec_struct(msg, svc_hash.map(|s| s.as_str()))?); } + // Emit rclpy-style service grouping classes (P4). These reference the + // Request/Response structs above, so they must come after them. + for srv in service_groups { + code.push_str(&generate_service_grouping_class(srv)); + } + Ok(code) } +/// Generate a service grouping class: `AddTwoInts.Request` / `.Response` (P4). +/// +/// Lets `create_client`/`create_server` accept a single rclpy-style type +/// (`example_interfaces.AddTwoInts`) instead of the bare Request class. +fn generate_service_grouping_class(srv: &ResolvedService) -> String { + let srv_name = &srv.parsed.name; + let package = &srv.parsed.package; + let request_struct = &srv.request.parsed.name; + let response_struct = &srv.response.parsed.name; + format!( + "class {srv_name}:\n \ + \"\"\"Service grouping type. Use {srv_name}.Request and {srv_name}.Response.\"\"\"\n \ + __srvtype__: ClassVar[str] = '{package}/srv/{srv_name}'\n \ + Request: ClassVar[type] = {request_struct}\n \ + Response: ClassVar[type] = {response_struct}\n\n" + ) +} + fn rust_to_python_type(field_type: &FieldType, current_package: &str) -> Result { // Get the base field type (without array indicators) let base_type = &field_type.base_type; diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/action_msgs.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/action_msgs.py index 055b55e5..c18c5164 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/action_msgs.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/action_msgs.py @@ -2,6 +2,12 @@ import msgspec from typing import ClassVar +class GoalStatusArray(msgspec.Struct, frozen=True, kw_only=True): + status_list: list["action_msgs.GoalStatus"] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'action_msgs/msg/GoalStatusArray' + __hash__: ClassVar[str] = 'RIHS01_6c1684b00f177d37438febe6e709fc4e2b0d4248dca4854946f9ed8b30cda83e' + class GoalInfo(msgspec.Struct, frozen=True, kw_only=True): goal_id: "unique_identifier_msgs.UUID | None" = None stamp: "builtin_interfaces.Time | None" = msgspec.field(default_factory=lambda: {'sec': 0, 'nanosec': 0}) @@ -16,12 +22,6 @@ class GoalStatus(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'action_msgs/msg/GoalStatus' __hash__: ClassVar[str] = 'RIHS01_32f4cfd717735d17657e1178f24431c1ce996c878c515230f6c5b3476819dbb9' -class GoalStatusArray(msgspec.Struct, frozen=True, kw_only=True): - status_list: list["action_msgs.GoalStatus"] = msgspec.field(default_factory=list) - - __msgtype__: ClassVar[str] = 'action_msgs/msg/GoalStatusArray' - __hash__: ClassVar[str] = 'RIHS01_6c1684b00f177d37438febe6e709fc4e2b0d4248dca4854946f9ed8b30cda83e' - class CancelGoalRequest(msgspec.Struct, frozen=True, kw_only=True): goal_info: "action_msgs.GoalInfo | None" = None @@ -35,3 +35,9 @@ class CancelGoalResponse(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'action_msgs/msg/CancelGoalResponse' __hash__: ClassVar[str] = 'RIHS01_c66d49f351ea4375bf3eef8569e74b7afc19305d9fa94c71b412262e411f2a8f' +class CancelGoal: + """Service grouping type. Use CancelGoal.Request and CancelGoal.Response.""" + __srvtype__: ClassVar[str] = 'action_msgs/srv/CancelGoal' + Request: ClassVar[type] = CancelGoalRequest + Response: ClassVar[type] = CancelGoalResponse + diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/example_interfaces.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/example_interfaces.py index 0b93842d..fd5ec056 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/example_interfaces.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/example_interfaces.py @@ -2,6 +2,18 @@ import msgspec from typing import ClassVar +class UInt8(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 + + __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt8' + __hash__: ClassVar[str] = 'RIHS01_9255b6d0dd98f5b573afbff223131279b788ac45cf051fb462c12dd9a30f4061' + +class UInt16(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 + + __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt16' + __hash__: ClassVar[str] = 'RIHS01_e123d0a691fa0ce58b682f1a4eee55137dd3f20c81665f0c556f53596c7fb377' + class UInt64MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "example_interfaces.MultiArrayLayout | None" = None data: list[int] = msgspec.field(default_factory=list) @@ -9,31 +21,45 @@ class UInt64MultiArray(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt64MultiArray' __hash__: ClassVar[str] = 'RIHS01_c05a348bd887aac76a947c901f67f93bf9a7fa14b28dce02eb495dadeb8842e4' -class Float32(msgspec.Struct, frozen=True, kw_only=True): - data: float = 0.0 +class Int32MultiArray(msgspec.Struct, frozen=True, kw_only=True): + layout: "example_interfaces.MultiArrayLayout | None" = None + data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Float32' - __hash__: ClassVar[str] = 'RIHS01_6a112d9235f8e8088d7a2bc77cb955341ac0d5c9870bdc592651a4186bb246f3' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int32MultiArray' + __hash__: ClassVar[str] = 'RIHS01_b347aff8e38e2ce3f7f4023db7922d2d4abb9d593b03c40b1427640c26595bfa' -class Float64(msgspec.Struct, frozen=True, kw_only=True): - data: float = 0.0 +class MultiArrayDimension(msgspec.Struct, frozen=True, kw_only=True): + label: str = "" + size: int = 0 + stride: int = 0 - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Float64' - __hash__: ClassVar[str] = 'RIHS01_74c137b7930c26339425a95fcfab441199bc41e0e572d3a0c9e95badd72b50da' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/MultiArrayDimension' + __hash__: ClassVar[str] = 'RIHS01_a785cb9839e177e3eb760260139a919fec87821edc3314c592f2725abbf0bfcd' -class UInt8MultiArray(msgspec.Struct, frozen=True, kw_only=True): - layout: "example_interfaces.MultiArrayLayout | None" = None - data: bytes = b"" +class UInt64(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 - __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt8MultiArray' - __hash__: ClassVar[str] = 'RIHS01_7a49ef5ed9b7182488303b4027d54719ba7266e776bd446dac30e79b56fb0a71' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt64' + __hash__: ClassVar[str] = 'RIHS01_6a3f8548c5818b7add62dd6cbbd840fd1ab17fbf9d73cad6690557b7326d8908' -class ByteMultiArray(msgspec.Struct, frozen=True, kw_only=True): +class Char(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 + + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Char' + __hash__: ClassVar[str] = 'RIHS01_320dcd57e1183fb08463cc3ab50bf7e5ce0ecee39f64d15a9e9eeca3384c91a5' + +class UInt16MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "example_interfaces.MultiArrayLayout | None" = None - data: bytes = b"" + data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'example_interfaces/msg/ByteMultiArray' - __hash__: ClassVar[str] = 'RIHS01_257fc3429b3c4dd0aea678c234f543665d1144b396f82c7ee60fed151e1dace3' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt16MultiArray' + __hash__: ClassVar[str] = 'RIHS01_507f6456c1965ef6eab8a9cfa8860bd484e1b7039b4c4ea2c75c648182fceade' + +class Int32(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 + + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int32' + __hash__: ClassVar[str] = 'RIHS01_5cd04cd7f3adb9d6c6064c316047b24c76622eb89144f300b536d657fd55e652' class Int16(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 @@ -41,25 +67,24 @@ class Int16(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int16' __hash__: ClassVar[str] = 'RIHS01_332d94306732e4e35da38e5ae744ff35bbdaeca300908dc43488d3a844687cd6' -class Float64MultiArray(msgspec.Struct, frozen=True, kw_only=True): - layout: "example_interfaces.MultiArrayLayout | None" = None - data: list[float] = msgspec.field(default_factory=list) +class Bool(msgspec.Struct, frozen=True, kw_only=True): + data: bool = False - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Float64MultiArray' - __hash__: ClassVar[str] = 'RIHS01_62a0985e7920b7bc489d1392f08c06ba0080fff5b8eaa09b27dcac122f50ad95' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Bool' + __hash__: ClassVar[str] = 'RIHS01_4765c142500f8fd4e1a32fb3edd7b7d9d822a16ec270445f5120e772c5f9aed5' -class Char(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 +class Float32(msgspec.Struct, frozen=True, kw_only=True): + data: float = 0.0 - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Char' - __hash__: ClassVar[str] = 'RIHS01_320dcd57e1183fb08463cc3ab50bf7e5ce0ecee39f64d15a9e9eeca3384c91a5' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Float32' + __hash__: ClassVar[str] = 'RIHS01_6a112d9235f8e8088d7a2bc77cb955341ac0d5c9870bdc592651a4186bb246f3' -class MultiArrayLayout(msgspec.Struct, frozen=True, kw_only=True): - dim: list["example_interfaces.MultiArrayDimension"] = msgspec.field(default_factory=list) - data_offset: int = 0 +class Int16MultiArray(msgspec.Struct, frozen=True, kw_only=True): + layout: "example_interfaces.MultiArrayLayout | None" = None + data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'example_interfaces/msg/MultiArrayLayout' - __hash__: ClassVar[str] = 'RIHS01_ba42ea30074e5826a1e91f70f3660dda2996937169487f877fc20a8a402e2c27' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int16MultiArray' + __hash__: ClassVar[str] = 'RIHS01_d663e739410479619bfb7b9b94b0a764998acffadb8b7f5b200affa87b7ffdd2' class Int64MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "example_interfaces.MultiArrayLayout | None" = None @@ -68,17 +93,18 @@ class Int64MultiArray(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int64MultiArray' __hash__: ClassVar[str] = 'RIHS01_ebce42c0d2b81bc5309deb0b2d83e30236aeb4899aea3cc7ddd18787af39c94a' -class Bool(msgspec.Struct, frozen=True, kw_only=True): - data: bool = False +class Float64(msgspec.Struct, frozen=True, kw_only=True): + data: float = 0.0 - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Bool' - __hash__: ClassVar[str] = 'RIHS01_4765c142500f8fd4e1a32fb3edd7b7d9d822a16ec270445f5120e772c5f9aed5' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Float64' + __hash__: ClassVar[str] = 'RIHS01_74c137b7930c26339425a95fcfab441199bc41e0e572d3a0c9e95badd72b50da' -class String(msgspec.Struct, frozen=True, kw_only=True): - data: str = "" +class UInt32MultiArray(msgspec.Struct, frozen=True, kw_only=True): + layout: "example_interfaces.MultiArrayLayout | None" = None + data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'example_interfaces/msg/String' - __hash__: ClassVar[str] = 'RIHS01_5509d866a579951f2fc6c19577c32605ba16f308cae7b498341d79536d4eb06b' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt32MultiArray' + __hash__: ClassVar[str] = 'RIHS01_0683176c523a116c76141c6d4abd150dbff492b72185f1260ce38a18a35aafcf' class Int8MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "example_interfaces.MultiArrayLayout | None" = None @@ -87,46 +113,44 @@ class Int8MultiArray(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int8MultiArray' __hash__: ClassVar[str] = 'RIHS01_6d26932aefc0e5c0c473613376338609bac9925e3527dc4e8fab6b6783b6d680' -class MultiArrayDimension(msgspec.Struct, frozen=True, kw_only=True): - label: str = "" - size: int = 0 - stride: int = 0 - - __msgtype__: ClassVar[str] = 'example_interfaces/msg/MultiArrayDimension' - __hash__: ClassVar[str] = 'RIHS01_a785cb9839e177e3eb760260139a919fec87821edc3314c592f2725abbf0bfcd' - -class Byte(msgspec.Struct, frozen=True, kw_only=True): +class Int64(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Byte' - __hash__: ClassVar[str] = 'RIHS01_f014e0424be54b8ba7c35490aea4198be92df1de4e88f4e19a2fbbce2e020bb9' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int64' + __hash__: ClassVar[str] = 'RIHS01_1b3b9a6502f560d079520c73c685a9550e5a1838d2cefd537fe0aba75a3639a0' -class Int32MultiArray(msgspec.Struct, frozen=True, kw_only=True): +class UInt8MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "example_interfaces.MultiArrayLayout | None" = None - data: list[int] = msgspec.field(default_factory=list) + data: bytes = b"" - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int32MultiArray' - __hash__: ClassVar[str] = 'RIHS01_b347aff8e38e2ce3f7f4023db7922d2d4abb9d593b03c40b1427640c26595bfa' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt8MultiArray' + __hash__: ClassVar[str] = 'RIHS01_7a49ef5ed9b7182488303b4027d54719ba7266e776bd446dac30e79b56fb0a71' -class UInt16MultiArray(msgspec.Struct, frozen=True, kw_only=True): - layout: "example_interfaces.MultiArrayLayout | None" = None - data: list[int] = msgspec.field(default_factory=list) +class Int8(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 - __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt16MultiArray' - __hash__: ClassVar[str] = 'RIHS01_507f6456c1965ef6eab8a9cfa8860bd484e1b7039b4c4ea2c75c648182fceade' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int8' + __hash__: ClassVar[str] = 'RIHS01_2e9ef643d84ff37840fe787d1269aa06268960e294f4a0f5eb1e9d4eb21cbb57' -class UInt32MultiArray(msgspec.Struct, frozen=True, kw_only=True): - layout: "example_interfaces.MultiArrayLayout | None" = None - data: list[int] = msgspec.field(default_factory=list) +class MultiArrayLayout(msgspec.Struct, frozen=True, kw_only=True): + dim: list["example_interfaces.MultiArrayDimension"] = msgspec.field(default_factory=list) + data_offset: int = 0 - __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt32MultiArray' - __hash__: ClassVar[str] = 'RIHS01_0683176c523a116c76141c6d4abd150dbff492b72185f1260ce38a18a35aafcf' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/MultiArrayLayout' + __hash__: ClassVar[str] = 'RIHS01_ba42ea30074e5826a1e91f70f3660dda2996937169487f877fc20a8a402e2c27' -class Int32(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 +class String(msgspec.Struct, frozen=True, kw_only=True): + data: str = "" - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int32' - __hash__: ClassVar[str] = 'RIHS01_5cd04cd7f3adb9d6c6064c316047b24c76622eb89144f300b536d657fd55e652' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/String' + __hash__: ClassVar[str] = 'RIHS01_5509d866a579951f2fc6c19577c32605ba16f308cae7b498341d79536d4eb06b' + +class Float64MultiArray(msgspec.Struct, frozen=True, kw_only=True): + layout: "example_interfaces.MultiArrayLayout | None" = None + data: list[float] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Float64MultiArray' + __hash__: ClassVar[str] = 'RIHS01_62a0985e7920b7bc489d1392f08c06ba0080fff5b8eaa09b27dcac122f50ad95' class UInt32(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 @@ -134,6 +158,13 @@ class UInt32(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt32' __hash__: ClassVar[str] = 'RIHS01_e86cccba586f30c16498f3ccd3550a764b255239a6142be3a4d7a2fa9a43515c' +class ByteMultiArray(msgspec.Struct, frozen=True, kw_only=True): + layout: "example_interfaces.MultiArrayLayout | None" = None + data: bytes = b"" + + __msgtype__: ClassVar[str] = 'example_interfaces/msg/ByteMultiArray' + __hash__: ClassVar[str] = 'RIHS01_257fc3429b3c4dd0aea678c234f543665d1144b396f82c7ee60fed151e1dace3' + class Float32MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "example_interfaces.MultiArrayLayout | None" = None data: list[float] = msgspec.field(default_factory=list) @@ -141,48 +172,17 @@ class Float32MultiArray(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'example_interfaces/msg/Float32MultiArray' __hash__: ClassVar[str] = 'RIHS01_8705278e8d206a9711133794ce6466281e9698138331c4b3d91e6cc9e9dfee26' -class UInt8(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 - - __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt8' - __hash__: ClassVar[str] = 'RIHS01_9255b6d0dd98f5b573afbff223131279b788ac45cf051fb462c12dd9a30f4061' - -class UInt16(msgspec.Struct, frozen=True, kw_only=True): +class Byte(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 - __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt16' - __hash__: ClassVar[str] = 'RIHS01_e123d0a691fa0ce58b682f1a4eee55137dd3f20c81665f0c556f53596c7fb377' + __msgtype__: ClassVar[str] = 'example_interfaces/msg/Byte' + __hash__: ClassVar[str] = 'RIHS01_f014e0424be54b8ba7c35490aea4198be92df1de4e88f4e19a2fbbce2e020bb9' class Empty(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'example_interfaces/msg/Empty' __hash__: ClassVar[str] = 'RIHS01_73c22a7341eeccf8ef504a991e60d4078223f0931a5d5d212800e7c978903c58' -class Int8(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 - - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int8' - __hash__: ClassVar[str] = 'RIHS01_2e9ef643d84ff37840fe787d1269aa06268960e294f4a0f5eb1e9d4eb21cbb57' - -class Int64(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 - - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int64' - __hash__: ClassVar[str] = 'RIHS01_1b3b9a6502f560d079520c73c685a9550e5a1838d2cefd537fe0aba75a3639a0' - -class Int16MultiArray(msgspec.Struct, frozen=True, kw_only=True): - layout: "example_interfaces.MultiArrayLayout | None" = None - data: list[int] = msgspec.field(default_factory=list) - - __msgtype__: ClassVar[str] = 'example_interfaces/msg/Int16MultiArray' - __hash__: ClassVar[str] = 'RIHS01_d663e739410479619bfb7b9b94b0a764998acffadb8b7f5b200affa87b7ffdd2' - -class UInt64(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 - - __msgtype__: ClassVar[str] = 'example_interfaces/msg/UInt64' - __hash__: ClassVar[str] = 'RIHS01_6a3f8548c5818b7add62dd6cbbd840fd1ab17fbf9d73cad6690557b7326d8908' - class AddTwoIntsRequest(msgspec.Struct, frozen=True, kw_only=True): a: int = 0 b: int = 0 @@ -221,3 +221,21 @@ class TriggerResponse(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'example_interfaces/msg/TriggerResponse' __hash__: ClassVar[str] = 'RIHS01_cfeeee47f8105dd7685e4c92d46d4074669cb1c477402be1dea37486542a69e0' +class AddTwoInts: + """Service grouping type. Use AddTwoInts.Request and AddTwoInts.Response.""" + __srvtype__: ClassVar[str] = 'example_interfaces/srv/AddTwoInts' + Request: ClassVar[type] = AddTwoIntsRequest + Response: ClassVar[type] = AddTwoIntsResponse + +class SetBool: + """Service grouping type. Use SetBool.Request and SetBool.Response.""" + __srvtype__: ClassVar[str] = 'example_interfaces/srv/SetBool' + Request: ClassVar[type] = SetBoolRequest + Response: ClassVar[type] = SetBoolResponse + +class Trigger: + """Service grouping type. Use Trigger.Request and Trigger.Response.""" + __srvtype__: ClassVar[str] = 'example_interfaces/srv/Trigger' + Request: ClassVar[type] = TriggerRequest + Response: ClassVar[type] = TriggerResponse + diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/geometry_msgs.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/geometry_msgs.py index f9015942..d5d94d88 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/geometry_msgs.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/geometry_msgs.py @@ -2,26 +2,12 @@ import msgspec from typing import ClassVar -class InertiaStamped(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - inertia: "geometry_msgs.Inertia | None" = None - - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/InertiaStamped' - __hash__: ClassVar[str] = 'RIHS01_766be45976252babf7f9d8ac4ae7c912a7ceccf71035622529f27518b695aa09' - -class PoseStamped(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - pose: "geometry_msgs.Pose | None" = None - - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PoseStamped' - __hash__: ClassVar[str] = 'RIHS01_10f3786d7d40fd2b54367835614bff85d4ad3b5dab62bf8bca0cc232d73b4cd8' - -class PoseWithCovarianceStamped(msgspec.Struct, frozen=True, kw_only=True): +class PolygonStamped(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - pose: "geometry_msgs.PoseWithCovariance | None" = None + polygon: "geometry_msgs.Polygon | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PoseWithCovarianceStamped' - __hash__: ClassVar[str] = 'RIHS01_26432f9803e43727d3c8f668d1fdb3c630f548af631e2f4e31382371bfea3b6e' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PolygonStamped' + __hash__: ClassVar[str] = 'RIHS01_b7cf07932f1523d4b4088075945c1a0141f7cd21da87cc940fc61652e9138b46' class VelocityStamped(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -32,12 +18,12 @@ class VelocityStamped(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/VelocityStamped' __hash__: ClassVar[str] = 'RIHS01_55e7196186c8dbe4375278d7f1ac050dd8c9bacade1cf3eef8460fa667bd2457' -class AccelWithCovarianceStamped(msgspec.Struct, frozen=True, kw_only=True): +class WrenchStamped(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - accel: "geometry_msgs.AccelWithCovariance | None" = None + wrench: "geometry_msgs.Wrench | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/AccelWithCovarianceStamped' - __hash__: ClassVar[str] = 'RIHS01_61c9ad8928e71dd95ce791b2f02809ee2a0bbcc42cd0e4047fd00a822a08e444' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/WrenchStamped' + __hash__: ClassVar[str] = 'RIHS01_8dc3deaf06b2ab281f9f9a742a8961c328ca7cec16e3fd6586d3a5c83fa78f77' class PointStamped(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -53,19 +39,35 @@ class AccelStamped(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/AccelStamped' __hash__: ClassVar[str] = 'RIHS01_ef1df9eabae0a708cc049a061ebcddc4e2a5f745730100ba680e086a9698b165' -class PolygonStamped(msgspec.Struct, frozen=True, kw_only=True): +class Vector3Stamped(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - polygon: "geometry_msgs.Polygon | None" = None + vector: "geometry_msgs.Vector3 | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PolygonStamped' - __hash__: ClassVar[str] = 'RIHS01_b7cf07932f1523d4b4088075945c1a0141f7cd21da87cc940fc61652e9138b46' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Vector3Stamped' + __hash__: ClassVar[str] = 'RIHS01_d4829622288cbb443886e7ea94ea5671a3b1be6bab4ad04224432a65f7d7887a' -class WrenchStamped(msgspec.Struct, frozen=True, kw_only=True): +class Point32(msgspec.Struct, frozen=True, kw_only=True): + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Point32' + __hash__: ClassVar[str] = 'RIHS01_2fc4db7cae16a4582c79a56b66173a8d48d52c7dc520ddc55a0d4bcf2a4bfdbc' + +class TransformStamped(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - wrench: "geometry_msgs.Wrench | None" = None + child_frame_id: str = "" + transform: "geometry_msgs.Transform | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/WrenchStamped' - __hash__: ClassVar[str] = 'RIHS01_8dc3deaf06b2ab281f9f9a742a8961c328ca7cec16e3fd6586d3a5c83fa78f77' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/TransformStamped' + __hash__: ClassVar[str] = 'RIHS01_0a241f87d04668d94099cbb5ba11691d5ad32c2f29682e4eb5653424bd275206' + +class AccelWithCovarianceStamped(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + accel: "geometry_msgs.AccelWithCovariance | None" = None + + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/AccelWithCovarianceStamped' + __hash__: ClassVar[str] = 'RIHS01_61c9ad8928e71dd95ce791b2f02809ee2a0bbcc42cd0e4047fd00a822a08e444' class QuaternionStamped(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -74,19 +76,12 @@ class QuaternionStamped(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/QuaternionStamped' __hash__: ClassVar[str] = 'RIHS01_381add86c6c3160644d228ca342182c7fd6c7fab11c7a85ad817a9cc22dbac6e' -class Wrench(msgspec.Struct, frozen=True, kw_only=True): - force: "geometry_msgs.Vector3 | None" = None - torque: "geometry_msgs.Vector3 | None" = None - - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Wrench' - __hash__: ClassVar[str] = 'RIHS01_018e8519d57c16adbe97c9fe1460ef21fec7e31bc541de3d653a35895677ce52' - -class PolygonInstanceStamped(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - polygon: "geometry_msgs.PolygonInstance | None" = None +class PolygonInstance(msgspec.Struct, frozen=True, kw_only=True): + polygon: "geometry_msgs.Polygon | None" = None + id: int = 0 - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PolygonInstanceStamped' - __hash__: ClassVar[str] = 'RIHS01_802f37ea4398d7ce547936aab1fd278923716a13c63373887cd896957434ce2f' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PolygonInstance' + __hash__: ClassVar[str] = 'RIHS01_fa1cb3dc774329865258afef74f65b0553d487510c6d0f93ba38cc32d62ac0e5' class Inertia(msgspec.Struct, frozen=True, kw_only=True): m: float = 0.0 @@ -101,13 +96,13 @@ class Inertia(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Inertia' __hash__: ClassVar[str] = 'RIHS01_2ddd5dab5c347825ba2e56c895ddccfd0b8efe53ae931bf67f905529930b4bd7' -class Point(msgspec.Struct, frozen=True, kw_only=True): +class Vector3(msgspec.Struct, frozen=True, kw_only=True): x: float = 0.0 y: float = 0.0 z: float = 0.0 - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Point' - __hash__: ClassVar[str] = 'RIHS01_6963084842a9b04494d6b2941d11444708d892da2f4b09843b9c43f42a7f6881' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Vector3' + __hash__: ClassVar[str] = 'RIHS01_cc12fe83e4c02719f1ce8070bfd14aecd40f75a96696a67a2a1f37f7dbb0765d' class Quaternion(msgspec.Struct, frozen=True, kw_only=True): x: float = 0.0 @@ -118,6 +113,13 @@ class Quaternion(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Quaternion' __hash__: ClassVar[str] = 'RIHS01_8a765f66778c8ff7c8ab94afcc590a2ed5325a1d9a076ffff38fbce36f458684' +class Twist(msgspec.Struct, frozen=True, kw_only=True): + linear: "geometry_msgs.Vector3 | None" = None + angular: "geometry_msgs.Vector3 | None" = None + + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Twist' + __hash__: ClassVar[str] = 'RIHS01_9c45bf16fe0983d80e3cfe750d6835843d265a9a6c46bd2e609fcddde6fb8d2a' + class Pose(msgspec.Struct, frozen=True, kw_only=True): position: "geometry_msgs.Point | None" = None orientation: "geometry_msgs.Quaternion | None" = None @@ -125,12 +127,19 @@ class Pose(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Pose' __hash__: ClassVar[str] = 'RIHS01_d501954e9476cea2996984e812054b68026ae0bfae789d9a10b23daf35cc90fa' -class PolygonInstance(msgspec.Struct, frozen=True, kw_only=True): - polygon: "geometry_msgs.Polygon | None" = None - id: int = 0 +class AccelWithCovariance(msgspec.Struct, frozen=True, kw_only=True): + accel: "geometry_msgs.Accel | None" = None + covariance: list[float] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PolygonInstance' - __hash__: ClassVar[str] = 'RIHS01_fa1cb3dc774329865258afef74f65b0553d487510c6d0f93ba38cc32d62ac0e5' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/AccelWithCovariance' + __hash__: ClassVar[str] = 'RIHS01_230d51bd53bc36f260574e73b42941cefe44684753480b6fc330c032c5db5997' + +class Wrench(msgspec.Struct, frozen=True, kw_only=True): + force: "geometry_msgs.Vector3 | None" = None + torque: "geometry_msgs.Vector3 | None" = None + + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Wrench' + __hash__: ClassVar[str] = 'RIHS01_018e8519d57c16adbe97c9fe1460ef21fec7e31bc541de3d653a35895677ce52' class PoseArray(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -139,49 +148,34 @@ class PoseArray(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PoseArray' __hash__: ClassVar[str] = 'RIHS01_af0cc36d190e104d546d168d6b39df04fa4b4ccecf59cb4c9ed328d3d5004aa0' -class TwistStamped(msgspec.Struct, frozen=True, kw_only=True): +class PoseWithCovarianceStamped(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - twist: "geometry_msgs.Twist | None" = None - - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/TwistStamped' - __hash__: ClassVar[str] = 'RIHS01_5f0fcd4f81d5d06ad9b4c4c63e3ea51b82d6ae4d0558f1d475229b1121db6f64' - -class Vector3(msgspec.Struct, frozen=True, kw_only=True): - x: float = 0.0 - y: float = 0.0 - z: float = 0.0 + pose: "geometry_msgs.PoseWithCovariance | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Vector3' - __hash__: ClassVar[str] = 'RIHS01_cc12fe83e4c02719f1ce8070bfd14aecd40f75a96696a67a2a1f37f7dbb0765d' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PoseWithCovarianceStamped' + __hash__: ClassVar[str] = 'RIHS01_26432f9803e43727d3c8f668d1fdb3c630f548af631e2f4e31382371bfea3b6e' -class Twist(msgspec.Struct, frozen=True, kw_only=True): - linear: "geometry_msgs.Vector3 | None" = None - angular: "geometry_msgs.Vector3 | None" = None +class PolygonInstanceStamped(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + polygon: "geometry_msgs.PolygonInstance | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Twist' - __hash__: ClassVar[str] = 'RIHS01_9c45bf16fe0983d80e3cfe750d6835843d265a9a6c46bd2e609fcddde6fb8d2a' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PolygonInstanceStamped' + __hash__: ClassVar[str] = 'RIHS01_802f37ea4398d7ce547936aab1fd278923716a13c63373887cd896957434ce2f' -class Point32(msgspec.Struct, frozen=True, kw_only=True): +class Point(msgspec.Struct, frozen=True, kw_only=True): x: float = 0.0 y: float = 0.0 z: float = 0.0 - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Point32' - __hash__: ClassVar[str] = 'RIHS01_2fc4db7cae16a4582c79a56b66173a8d48d52c7dc520ddc55a0d4bcf2a4bfdbc' - -class Transform(msgspec.Struct, frozen=True, kw_only=True): - translation: "geometry_msgs.Vector3 | None" = None - rotation: "geometry_msgs.Quaternion | None" = None - - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Transform' - __hash__: ClassVar[str] = 'RIHS01_beb83fbe698636351461f6f35d1abb20010c43d55374d81bd041f1ba2581fddc' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Point' + __hash__: ClassVar[str] = 'RIHS01_6963084842a9b04494d6b2941d11444708d892da2f4b09843b9c43f42a7f6881' -class Vector3Stamped(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - vector: "geometry_msgs.Vector3 | None" = None +class Accel(msgspec.Struct, frozen=True, kw_only=True): + linear: "geometry_msgs.Vector3 | None" = None + angular: "geometry_msgs.Vector3 | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Vector3Stamped' - __hash__: ClassVar[str] = 'RIHS01_d4829622288cbb443886e7ea94ea5671a3b1be6bab4ad04224432a65f7d7887a' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Accel' + __hash__: ClassVar[str] = 'RIHS01_dc448243ded9b1fcbcca24aba0c22f013dae06c354ba2d849571c0a2a3f57ca0' class Polygon(msgspec.Struct, frozen=True, kw_only=True): points: list["geometry_msgs.Point32"] = msgspec.field(default_factory=list) @@ -189,28 +183,26 @@ class Polygon(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Polygon' __hash__: ClassVar[str] = 'RIHS01_3782f9f0bf044964d692d6c017d705e37611afb1f0bf6a9dee248a7dda0f784a' -class Pose2D(msgspec.Struct, frozen=True, kw_only=True): - x: float = 0.0 - y: float = 0.0 - theta: float = 0.0 +class InertiaStamped(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + inertia: "geometry_msgs.Inertia | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Pose2D' - __hash__: ClassVar[str] = 'RIHS01_d68efa5b46e70f7b16ca23085474fdac5a44b638783ec42f661da64da4724ccc' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/InertiaStamped' + __hash__: ClassVar[str] = 'RIHS01_766be45976252babf7f9d8ac4ae7c912a7ceccf71035622529f27518b695aa09' -class TransformStamped(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - child_frame_id: str = "" - transform: "geometry_msgs.Transform | None" = None +class Transform(msgspec.Struct, frozen=True, kw_only=True): + translation: "geometry_msgs.Vector3 | None" = None + rotation: "geometry_msgs.Quaternion | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/TransformStamped' - __hash__: ClassVar[str] = 'RIHS01_0a241f87d04668d94099cbb5ba11691d5ad32c2f29682e4eb5653424bd275206' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Transform' + __hash__: ClassVar[str] = 'RIHS01_beb83fbe698636351461f6f35d1abb20010c43d55374d81bd041f1ba2581fddc' -class AccelWithCovariance(msgspec.Struct, frozen=True, kw_only=True): - accel: "geometry_msgs.Accel | None" = None - covariance: list[float] = msgspec.field(default_factory=list) +class TwistWithCovarianceStamped(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + twist: "geometry_msgs.TwistWithCovariance | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/AccelWithCovariance' - __hash__: ClassVar[str] = 'RIHS01_230d51bd53bc36f260574e73b42941cefe44684753480b6fc330c032c5db5997' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/TwistWithCovarianceStamped' + __hash__: ClassVar[str] = 'RIHS01_77b67434531e6529b7a0091357b186b6ebdb17fd9ffd3e0c7ce9d3fb11a44563' class TwistWithCovariance(msgspec.Struct, frozen=True, kw_only=True): twist: "geometry_msgs.Twist | None" = None @@ -219,19 +211,20 @@ class TwistWithCovariance(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/TwistWithCovariance' __hash__: ClassVar[str] = 'RIHS01_49f574f033f095d8b6cd1beaca5ca7925e296e84af1716d16c89d38b059c8c18' -class Accel(msgspec.Struct, frozen=True, kw_only=True): - linear: "geometry_msgs.Vector3 | None" = None - angular: "geometry_msgs.Vector3 | None" = None +class PoseStamped(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + pose: "geometry_msgs.Pose | None" = None - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Accel' - __hash__: ClassVar[str] = 'RIHS01_dc448243ded9b1fcbcca24aba0c22f013dae06c354ba2d849571c0a2a3f57ca0' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PoseStamped' + __hash__: ClassVar[str] = 'RIHS01_10f3786d7d40fd2b54367835614bff85d4ad3b5dab62bf8bca0cc232d73b4cd8' -class TwistWithCovarianceStamped(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - twist: "geometry_msgs.TwistWithCovariance | None" = None +class Pose2D(msgspec.Struct, frozen=True, kw_only=True): + x: float = 0.0 + y: float = 0.0 + theta: float = 0.0 - __msgtype__: ClassVar[str] = 'geometry_msgs/msg/TwistWithCovarianceStamped' - __hash__: ClassVar[str] = 'RIHS01_77b67434531e6529b7a0091357b186b6ebdb17fd9ffd3e0c7ce9d3fb11a44563' + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/Pose2D' + __hash__: ClassVar[str] = 'RIHS01_d68efa5b46e70f7b16ca23085474fdac5a44b638783ec42f661da64da4724ccc' class PoseWithCovariance(msgspec.Struct, frozen=True, kw_only=True): pose: "geometry_msgs.Pose | None" = None @@ -240,3 +233,10 @@ class PoseWithCovariance(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'geometry_msgs/msg/PoseWithCovariance' __hash__: ClassVar[str] = 'RIHS01_9a7c0fd234b7f45c6098745ecccd773ca1085670e64107135397aee31c02e1bb' +class TwistStamped(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + twist: "geometry_msgs.Twist | None" = None + + __msgtype__: ClassVar[str] = 'geometry_msgs/msg/TwistStamped' + __hash__: ClassVar[str] = 'RIHS01_5f0fcd4f81d5d06ad9b4c4c63e3ea51b82d6ae4d0558f1d475229b1121db6f64' + diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/lifecycle_msgs.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/lifecycle_msgs.py index 90ccde3d..aa13e250 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/lifecycle_msgs.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/lifecycle_msgs.py @@ -2,14 +2,6 @@ import msgspec from typing import ClassVar -class TransitionDescription(msgspec.Struct, frozen=True, kw_only=True): - transition: "lifecycle_msgs.Transition | None" = None - start_state: "lifecycle_msgs.State | None" = None - goal_state: "lifecycle_msgs.State | None" = None - - __msgtype__: ClassVar[str] = 'lifecycle_msgs/msg/TransitionDescription' - __hash__: ClassVar[str] = 'RIHS01_c5f1cd4bb1ad2ba0e3329d4ac7015c52a674a72c1faf7974c37a33f4f6048b28' - class Transition(msgspec.Struct, frozen=True, kw_only=True): id: int = 0 label: str = "" @@ -26,6 +18,14 @@ class TransitionEvent(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'lifecycle_msgs/msg/TransitionEvent' __hash__: ClassVar[str] = 'RIHS01_f90405bf3073265eec4847f23ac63298cf2dd3933a6d541be11a9232dd840c32' +class TransitionDescription(msgspec.Struct, frozen=True, kw_only=True): + transition: "lifecycle_msgs.Transition | None" = None + start_state: "lifecycle_msgs.State | None" = None + goal_state: "lifecycle_msgs.State | None" = None + + __msgtype__: ClassVar[str] = 'lifecycle_msgs/msg/TransitionDescription' + __hash__: ClassVar[str] = 'RIHS01_c5f1cd4bb1ad2ba0e3329d4ac7015c52a674a72c1faf7974c37a33f4f6048b28' + class State(msgspec.Struct, frozen=True, kw_only=True): id: int = 0 label: str = "" @@ -78,3 +78,27 @@ class GetStateResponse(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'lifecycle_msgs/msg/GetStateResponse' __hash__: ClassVar[str] = 'RIHS01_800a0a5aae599782b02932de0caf563f6dc4e7e94b794eadde075ba2cbef9795' +class ChangeState: + """Service grouping type. Use ChangeState.Request and ChangeState.Response.""" + __srvtype__: ClassVar[str] = 'lifecycle_msgs/srv/ChangeState' + Request: ClassVar[type] = ChangeStateRequest + Response: ClassVar[type] = ChangeStateResponse + +class GetAvailableStates: + """Service grouping type. Use GetAvailableStates.Request and GetAvailableStates.Response.""" + __srvtype__: ClassVar[str] = 'lifecycle_msgs/srv/GetAvailableStates' + Request: ClassVar[type] = GetAvailableStatesRequest + Response: ClassVar[type] = GetAvailableStatesResponse + +class GetAvailableTransitions: + """Service grouping type. Use GetAvailableTransitions.Request and GetAvailableTransitions.Response.""" + __srvtype__: ClassVar[str] = 'lifecycle_msgs/srv/GetAvailableTransitions' + Request: ClassVar[type] = GetAvailableTransitionsRequest + Response: ClassVar[type] = GetAvailableTransitionsResponse + +class GetState: + """Service grouping type. Use GetState.Request and GetState.Response.""" + __srvtype__: ClassVar[str] = 'lifecycle_msgs/srv/GetState' + Request: ClassVar[type] = GetStateRequest + Response: ClassVar[type] = GetStateResponse + diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/nav_msgs.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/nav_msgs.py index d370d3f0..1c67f515 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/nav_msgs.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/nav_msgs.py @@ -2,13 +2,12 @@ import msgspec from typing import ClassVar -class OccupancyGrid(msgspec.Struct, frozen=True, kw_only=True): +class Path(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - info: "nav_msgs.MapMetaData | None" = None - data: list[int] = msgspec.field(default_factory=list) + poses: list["geometry_msgs.PoseStamped"] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'nav_msgs/msg/OccupancyGrid' - __hash__: ClassVar[str] = 'RIHS01_8d348150c12913a31ee0ec170fbf25089e4745d17035792a1ba94d6f0bc0cfc7' + __msgtype__: ClassVar[str] = 'nav_msgs/msg/Path' + __hash__: ClassVar[str] = 'RIHS01_1957a5bb3cee5da65c4e52e52b65a93df227efce4c20f8458b36e73066ca334b' class GridCells(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -19,12 +18,22 @@ class GridCells(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'nav_msgs/msg/GridCells' __hash__: ClassVar[str] = 'RIHS01_bb99c2f5d0a04750745a81ec6a8147aa373cce5bd17c8cd6507f2413354a6933' -class Goals(msgspec.Struct, frozen=True, kw_only=True): +class OccupancyGrid(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - goals: list["geometry_msgs.PoseStamped"] = msgspec.field(default_factory=list) + info: "nav_msgs.MapMetaData | None" = None + data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'nav_msgs/msg/Goals' - __hash__: ClassVar[str] = 'RIHS01_02305a51633b5c04d8979b878a7577cafd422f8a07465c878b17a920af3759e9' + __msgtype__: ClassVar[str] = 'nav_msgs/msg/OccupancyGrid' + __hash__: ClassVar[str] = 'RIHS01_8d348150c12913a31ee0ec170fbf25089e4745d17035792a1ba94d6f0bc0cfc7' + +class Odometry(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + child_frame_id: str = "" + pose: "geometry_msgs.PoseWithCovariance | None" = None + twist: "geometry_msgs.TwistWithCovariance | None" = None + + __msgtype__: ClassVar[str] = 'nav_msgs/msg/Odometry' + __hash__: ClassVar[str] = 'RIHS01_3cc97dc7fb7502f8714462c526d369e35b603cfc34d946e3f2eda2766dfec6e0' class MapMetaData(msgspec.Struct, frozen=True, kw_only=True): map_load_time: "builtin_interfaces.Time | None" = msgspec.field(default_factory=lambda: {'sec': 0, 'nanosec': 0}) @@ -36,21 +45,12 @@ class MapMetaData(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'nav_msgs/msg/MapMetaData' __hash__: ClassVar[str] = 'RIHS01_2772d4b2000ef2b35dbaeb80fd3946c1369f817fb4f75677d916d27c17d763c8' -class Path(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - poses: list["geometry_msgs.PoseStamped"] = msgspec.field(default_factory=list) - - __msgtype__: ClassVar[str] = 'nav_msgs/msg/Path' - __hash__: ClassVar[str] = 'RIHS01_1957a5bb3cee5da65c4e52e52b65a93df227efce4c20f8458b36e73066ca334b' - -class Odometry(msgspec.Struct, frozen=True, kw_only=True): +class Goals(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - child_frame_id: str = "" - pose: "geometry_msgs.PoseWithCovariance | None" = None - twist: "geometry_msgs.TwistWithCovariance | None" = None + goals: list["geometry_msgs.PoseStamped"] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'nav_msgs/msg/Odometry' - __hash__: ClassVar[str] = 'RIHS01_3cc97dc7fb7502f8714462c526d369e35b603cfc34d946e3f2eda2766dfec6e0' + __msgtype__: ClassVar[str] = 'nav_msgs/msg/Goals' + __hash__: ClassVar[str] = 'RIHS01_02305a51633b5c04d8979b878a7577cafd422f8a07465c878b17a920af3759e9' class GetMapRequest(msgspec.Struct, frozen=True, kw_only=True): @@ -103,3 +103,27 @@ class SetMapResponse(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'nav_msgs/msg/SetMapResponse' __hash__: ClassVar[str] = 'RIHS01_5e11a5b2ca53d8ae85b666a019f16c9904ebc787828f1f566c4e048a1ddedfb4' +class GetMap: + """Service grouping type. Use GetMap.Request and GetMap.Response.""" + __srvtype__: ClassVar[str] = 'nav_msgs/srv/GetMap' + Request: ClassVar[type] = GetMapRequest + Response: ClassVar[type] = GetMapResponse + +class GetPlan: + """Service grouping type. Use GetPlan.Request and GetPlan.Response.""" + __srvtype__: ClassVar[str] = 'nav_msgs/srv/GetPlan' + Request: ClassVar[type] = GetPlanRequest + Response: ClassVar[type] = GetPlanResponse + +class LoadMap: + """Service grouping type. Use LoadMap.Request and LoadMap.Response.""" + __srvtype__: ClassVar[str] = 'nav_msgs/srv/LoadMap' + Request: ClassVar[type] = LoadMapRequest + Response: ClassVar[type] = LoadMapResponse + +class SetMap: + """Service grouping type. Use SetMap.Request and SetMap.Response.""" + __srvtype__: ClassVar[str] = 'nav_msgs/srv/SetMap' + Request: ClassVar[type] = SetMapRequest + Response: ClassVar[type] = SetMapResponse + diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/rcl_interfaces.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/rcl_interfaces.py index 21e14879..1089cbf1 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/rcl_interfaces.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/rcl_interfaces.py @@ -2,13 +2,6 @@ import msgspec from typing import ClassVar -class ListParametersResult(msgspec.Struct, frozen=True, kw_only=True): - names: list[str] = msgspec.field(default_factory=list) - prefixes: list[str] = msgspec.field(default_factory=list) - - __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/ListParametersResult' - __hash__: ClassVar[str] = 'RIHS01_237ae3428413dcbcfb452b510c42355f3a2b021dc091afa3e18526d57022f1cd' - class SetParametersResult(msgspec.Struct, frozen=True, kw_only=True): successful: bool = False reason: str = "" @@ -16,21 +9,6 @@ class SetParametersResult(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/SetParametersResult' __hash__: ClassVar[str] = 'RIHS01_cfcc0fb0371ee5159b403960ef4300f8f9d2f1fd6117c8666b7f9654d528a9b1' -class Parameter(msgspec.Struct, frozen=True, kw_only=True): - name: str = "" - value: "rcl_interfaces.ParameterValue | None" = None - - __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/Parameter' - __hash__: ClassVar[str] = 'RIHS01_ddfe6442cffc462317adb5c92536a7b6dd55858c5c3e1e328165a6b73c2831af' - -class FloatingPointRange(msgspec.Struct, frozen=True, kw_only=True): - from_value: float = 0.0 - to_value: float = 0.0 - step: float = 0.0 - - __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/FloatingPointRange' - __hash__: ClassVar[str] = 'RIHS01_e6af23a23c177fee5f3075c8b1e435162a9b63c863d78c06017460b49684262d' - class IntegerRange(msgspec.Struct, frozen=True, kw_only=True): from_value: int = 0 to_value: int = 0 @@ -39,6 +17,41 @@ class IntegerRange(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/IntegerRange' __hash__: ClassVar[str] = 'RIHS01_f7b7fdc0f65f07702e099218e13288c3963bcb9345bde78b560e6cd19800fc5a' +class ListParametersResult(msgspec.Struct, frozen=True, kw_only=True): + names: list[str] = msgspec.field(default_factory=list) + prefixes: list[str] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/ListParametersResult' + __hash__: ClassVar[str] = 'RIHS01_237ae3428413dcbcfb452b510c42355f3a2b021dc091afa3e18526d57022f1cd' + +class ParameterDescriptor(msgspec.Struct, frozen=True, kw_only=True): + name: str = "" + type: int = 0 + description: str = "" + additional_constraints: str = "" + read_only: bool = False + dynamic_typing: bool = False + floating_point_range: list["rcl_interfaces.FloatingPointRange"] = msgspec.field(default_factory=list) + integer_range: list["rcl_interfaces.IntegerRange"] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/ParameterDescriptor' + __hash__: ClassVar[str] = 'RIHS01_52175dbfda6c51153101d33d2a9da05743f66f02d5ab2ca9ec4709b46b73d704' + +class ParameterValue(msgspec.Struct, frozen=True, kw_only=True): + type: int = 0 + bool_value: bool = False + integer_value: int = 0 + double_value: float = 0.0 + string_value: str = "" + byte_array_value: bytes = b"" + bool_array_value: list[bool] = msgspec.field(default_factory=list) + integer_array_value: list[int] = msgspec.field(default_factory=list) + double_array_value: list[float] = msgspec.field(default_factory=list) + string_array_value: list[str] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/ParameterValue' + __hash__: ClassVar[str] = 'RIHS01_115fc089a387e23c7ecd3525c9189c379109119d6ab82e8dfbde0fdf6a7f9b68' + class ParameterEventDescriptors(msgspec.Struct, frozen=True, kw_only=True): new_parameters: list["rcl_interfaces.ParameterDescriptor"] = msgspec.field(default_factory=list) changed_parameters: list["rcl_interfaces.ParameterDescriptor"] = msgspec.field(default_factory=list) @@ -52,6 +65,13 @@ class ParameterType(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/ParameterType' __hash__: ClassVar[str] = 'RIHS01_df29ed057a834862187be24dd187d981790ff3ea6502f4cd27b432cbc42c6d46' +class Parameter(msgspec.Struct, frozen=True, kw_only=True): + name: str = "" + value: "rcl_interfaces.ParameterValue | None" = None + + __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/Parameter' + __hash__: ClassVar[str] = 'RIHS01_ddfe6442cffc462317adb5c92536a7b6dd55858c5c3e1e328165a6b73c2831af' + class ParameterEvent(msgspec.Struct, frozen=True, kw_only=True): stamp: "builtin_interfaces.Time | None" = msgspec.field(default_factory=lambda: {'sec': 0, 'nanosec': 0}) node: str = "" @@ -62,33 +82,13 @@ class ParameterEvent(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/ParameterEvent' __hash__: ClassVar[str] = 'RIHS01_043e627780fcad87a22d225bc2a037361dba713fca6a6b9f4b869a5aa0393204' -class ParameterValue(msgspec.Struct, frozen=True, kw_only=True): - type: int = 0 - bool_value: bool = False - integer_value: int = 0 - double_value: float = 0.0 - string_value: str = "" - byte_array_value: bytes = b"" - bool_array_value: list[bool] = msgspec.field(default_factory=list) - integer_array_value: list[int] = msgspec.field(default_factory=list) - double_array_value: list[float] = msgspec.field(default_factory=list) - string_array_value: list[str] = msgspec.field(default_factory=list) - - __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/ParameterValue' - __hash__: ClassVar[str] = 'RIHS01_115fc089a387e23c7ecd3525c9189c379109119d6ab82e8dfbde0fdf6a7f9b68' - -class ParameterDescriptor(msgspec.Struct, frozen=True, kw_only=True): - name: str = "" - type: int = 0 - description: str = "" - additional_constraints: str = "" - read_only: bool = False - dynamic_typing: bool = False - floating_point_range: list["rcl_interfaces.FloatingPointRange"] = msgspec.field(default_factory=list) - integer_range: list["rcl_interfaces.IntegerRange"] = msgspec.field(default_factory=list) +class FloatingPointRange(msgspec.Struct, frozen=True, kw_only=True): + from_value: float = 0.0 + to_value: float = 0.0 + step: float = 0.0 - __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/ParameterDescriptor' - __hash__: ClassVar[str] = 'RIHS01_52175dbfda6c51153101d33d2a9da05743f66f02d5ab2ca9ec4709b46b73d704' + __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/FloatingPointRange' + __hash__: ClassVar[str] = 'RIHS01_e6af23a23c177fee5f3075c8b1e435162a9b63c863d78c06017460b49684262d' class DescribeParametersRequest(msgspec.Struct, frozen=True, kw_only=True): names: list[str] = msgspec.field(default_factory=list) @@ -163,3 +163,39 @@ class SetParametersAtomicallyResponse(msgspec.Struct, frozen=True, kw_only=True) __msgtype__: ClassVar[str] = 'rcl_interfaces/msg/SetParametersAtomicallyResponse' __hash__: ClassVar[str] = 'RIHS01_0e192ef259c07fc3c07a13191d27002222e65e00ccec653ca05e856f79285fcd' +class DescribeParameters: + """Service grouping type. Use DescribeParameters.Request and DescribeParameters.Response.""" + __srvtype__: ClassVar[str] = 'rcl_interfaces/srv/DescribeParameters' + Request: ClassVar[type] = DescribeParametersRequest + Response: ClassVar[type] = DescribeParametersResponse + +class GetParameterTypes: + """Service grouping type. Use GetParameterTypes.Request and GetParameterTypes.Response.""" + __srvtype__: ClassVar[str] = 'rcl_interfaces/srv/GetParameterTypes' + Request: ClassVar[type] = GetParameterTypesRequest + Response: ClassVar[type] = GetParameterTypesResponse + +class GetParameters: + """Service grouping type. Use GetParameters.Request and GetParameters.Response.""" + __srvtype__: ClassVar[str] = 'rcl_interfaces/srv/GetParameters' + Request: ClassVar[type] = GetParametersRequest + Response: ClassVar[type] = GetParametersResponse + +class ListParameters: + """Service grouping type. Use ListParameters.Request and ListParameters.Response.""" + __srvtype__: ClassVar[str] = 'rcl_interfaces/srv/ListParameters' + Request: ClassVar[type] = ListParametersRequest + Response: ClassVar[type] = ListParametersResponse + +class SetParameters: + """Service grouping type. Use SetParameters.Request and SetParameters.Response.""" + __srvtype__: ClassVar[str] = 'rcl_interfaces/srv/SetParameters' + Request: ClassVar[type] = SetParametersRequest + Response: ClassVar[type] = SetParametersResponse + +class SetParametersAtomically: + """Service grouping type. Use SetParametersAtomically.Request and SetParametersAtomically.Response.""" + __srvtype__: ClassVar[str] = 'rcl_interfaces/srv/SetParametersAtomically' + Request: ClassVar[type] = SetParametersAtomicallyRequest + Response: ClassVar[type] = SetParametersAtomicallyResponse + diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/sensor_msgs.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/sensor_msgs.py index cde385a0..7ea07784 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/sensor_msgs.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/sensor_msgs.py @@ -2,45 +2,46 @@ import msgspec from typing import ClassVar -class PointField(msgspec.Struct, frozen=True, kw_only=True): - name: str = "" - offset: int = 0 - datatype: int = 0 - count: int = 0 +class PointCloud(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + points: list["geometry_msgs.Point32"] = msgspec.field(default_factory=list) + channels: list["sensor_msgs.ChannelFloat32"] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/PointField' - __hash__: ClassVar[str] = 'RIHS01_5c6a4750728c2bcfbbf7037225b20b02d4429634732146b742dee1726637ef01' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/PointCloud' + __hash__: ClassVar[str] = 'RIHS01_614593df71d3c2b9bd4604a71b750fd218f0d65c045ea988b713719455a65b3b' -class LaserEcho(msgspec.Struct, frozen=True, kw_only=True): - echoes: list[float] = msgspec.field(default_factory=list) +class Temperature(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + temperature: float = 0.0 + variance: float = 0.0 - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/LaserEcho' - __hash__: ClassVar[str] = 'RIHS01_0fbc05a0db7d37fe52c0f0375356db55da0046f7ef5bd27ca6b34bd0582bc952' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Temperature' + __hash__: ClassVar[str] = 'RIHS01_72514a14126ab9f8a9abec974c78e5610a367b59db5da355ff1fb982d5bad4b8' -class JointState(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - name: list[str] = msgspec.field(default_factory=list) - position: list[float] = msgspec.field(default_factory=list) - velocity: list[float] = msgspec.field(default_factory=list) - effort: list[float] = msgspec.field(default_factory=list) +class NavSatStatus(msgspec.Struct, frozen=True, kw_only=True): + status: int = 0 + service: int = 0 - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/JointState' - __hash__: ClassVar[str] = 'RIHS01_a13ee3a330e346c9d87b5aa18d24e11690752bd33a0350f11c5882bc9179260e' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/NavSatStatus' + __hash__: ClassVar[str] = 'RIHS01_d1ed3befa628e09571bd273b888ba1c1fd187c9a5e0006b385d7e5e9095a3204' -class LaserScan(msgspec.Struct, frozen=True, kw_only=True): +class RegionOfInterest(msgspec.Struct, frozen=True, kw_only=True): + x_offset: int = 0 + y_offset: int = 0 + height: int = 0 + width: int = 0 + do_rectify: bool = False + + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/RegionOfInterest' + __hash__: ClassVar[str] = 'RIHS01_ad16bcba5f9131dcdba6fbded19f726f5440e3c513b4fb586dd3027eeed8abb1' + +class CompressedImage(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - angle_min: float = 0.0 - angle_max: float = 0.0 - angle_increment: float = 0.0 - time_increment: float = 0.0 - scan_time: float = 0.0 - range_min: float = 0.0 - range_max: float = 0.0 - ranges: list[float] = msgspec.field(default_factory=list) - intensities: list[float] = msgspec.field(default_factory=list) + format: str = "" + data: bytes = b"" - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/LaserScan' - __hash__: ClassVar[str] = 'RIHS01_64c191398013af96509d518dac71d5164f9382553fce5c1f8cca5be7924bd828' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/CompressedImage' + __hash__: ClassVar[str] = 'RIHS01_15640771531571185e2efc8a100baf923961a4d15d5569652e6cb6691e8e371a' class PointCloud2(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -56,19 +57,24 @@ class PointCloud2(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'sensor_msgs/msg/PointCloud2' __hash__: ClassVar[str] = 'RIHS01_9198cabf7da3796ae6fe19c4cb3bdd3525492988c70522628af5daa124bae2b5' -class NavSatStatus(msgspec.Struct, frozen=True, kw_only=True): - status: int = 0 - service: int = 0 +class JointState(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + name: list[str] = msgspec.field(default_factory=list) + position: list[float] = msgspec.field(default_factory=list) + velocity: list[float] = msgspec.field(default_factory=list) + effort: list[float] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/NavSatStatus' - __hash__: ClassVar[str] = 'RIHS01_d1ed3befa628e09571bd273b888ba1c1fd187c9a5e0006b385d7e5e9095a3204' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/JointState' + __hash__: ClassVar[str] = 'RIHS01_a13ee3a330e346c9d87b5aa18d24e11690752bd33a0350f11c5882bc9179260e' -class ChannelFloat32(msgspec.Struct, frozen=True, kw_only=True): +class PointField(msgspec.Struct, frozen=True, kw_only=True): name: str = "" - values: list[float] = msgspec.field(default_factory=list) + offset: int = 0 + datatype: int = 0 + count: int = 0 - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/ChannelFloat32' - __hash__: ClassVar[str] = 'RIHS01_92665437ddf39346f4ba39ee32e648390605b633cc077d40f4bd4d7b58af6cd4' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/PointField' + __hash__: ClassVar[str] = 'RIHS01_5c6a4750728c2bcfbbf7037225b20b02d4429634732146b742dee1726637ef01' class TimeReference(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -78,51 +84,42 @@ class TimeReference(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'sensor_msgs/msg/TimeReference' __hash__: ClassVar[str] = 'RIHS01_dd66e84cf40bbb5d5a40472e6ecf2675a031334d4c426abdb2ad41801a8efc99' -class NavSatFix(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - status: "sensor_msgs.NavSatStatus | None" = None - latitude: float = 0.0 - longitude: float = 0.0 - altitude: float = 0.0 - position_covariance: list[float] = msgspec.field(default_factory=list) - position_covariance_type: int = 0 +class LaserEcho(msgspec.Struct, frozen=True, kw_only=True): + echoes: list[float] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/NavSatFix' - __hash__: ClassVar[str] = 'RIHS01_62223ab3fe210a15976021da7afddc9e200dc9ec75231c1b6a557fc598a65404' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/LaserEcho' + __hash__: ClassVar[str] = 'RIHS01_0fbc05a0db7d37fe52c0f0375356db55da0046f7ef5bd27ca6b34bd0582bc952' -class Image(msgspec.Struct, frozen=True, kw_only=True): +class LaserScan(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - height: int = 0 - width: int = 0 - encoding: str = "" - is_bigendian: int = 0 - step: int = 0 - data: bytes = b"" + angle_min: float = 0.0 + angle_max: float = 0.0 + angle_increment: float = 0.0 + time_increment: float = 0.0 + scan_time: float = 0.0 + range_min: float = 0.0 + range_max: float = 0.0 + ranges: list[float] = msgspec.field(default_factory=list) + intensities: list[float] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Image' - __hash__: ClassVar[str] = 'RIHS01_d31d41a9a4c4bc8eae9be757b0beed306564f7526c88ea6a4588fb9582527d47' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/LaserScan' + __hash__: ClassVar[str] = 'RIHS01_64c191398013af96509d518dac71d5164f9382553fce5c1f8cca5be7924bd828' -class RegionOfInterest(msgspec.Struct, frozen=True, kw_only=True): - x_offset: int = 0 - y_offset: int = 0 +class CameraInfo(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None height: int = 0 width: int = 0 - do_rectify: bool = False - - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/RegionOfInterest' - __hash__: ClassVar[str] = 'RIHS01_ad16bcba5f9131dcdba6fbded19f726f5440e3c513b4fb586dd3027eeed8abb1' - -class Range(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - radiation_type: int = 0 - field_of_view: float = 0.0 - min_range: float = 0.0 - max_range: float = 0.0 - range: float = 0.0 - variance: float = 0.0 + distortion_model: str = "" + d: list[float] = msgspec.field(default_factory=list) + k: list[float] = msgspec.field(default_factory=list) + r: list[float] = msgspec.field(default_factory=list) + p: list[float] = msgspec.field(default_factory=list) + binning_x: int = 0 + binning_y: int = 0 + roi: "sensor_msgs.RegionOfInterest | None" = None - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Range' - __hash__: ClassVar[str] = 'RIHS01_b42b62562e93cbfe9d42b82fe5994dfa3d63d7d5c90a317981703f7388adff3a' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/CameraInfo' + __hash__: ClassVar[str] = 'RIHS01_b3dfd68ff46c9d56c80fd3bd4ed22c7a4ddce8c8348f2f59c299e73118e7e275' class Joy(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -142,6 +139,34 @@ class MultiDOFJointState(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'sensor_msgs/msg/MultiDOFJointState' __hash__: ClassVar[str] = 'RIHS01_4d4ded702cfba7ff3ec783835c1a1425f75e53939a430ff355d1fee4b3bbc40b' +class JoyFeedback(msgspec.Struct, frozen=True, kw_only=True): + type: int = 0 + id: int = 0 + intensity: float = 0.0 + + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/JoyFeedback' + __hash__: ClassVar[str] = 'RIHS01_231dd362f71d6fc08272770d07120ad5fe5874ce2dbac70109b28986834290cd' + +class RelativeHumidity(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + relative_humidity: float = 0.0 + variance: float = 0.0 + + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/RelativeHumidity' + __hash__: ClassVar[str] = 'RIHS01_8687c99b4fb393cb2e545e407b5ea7fd0b5d8960bcd849a0f86c544740138839' + +class Imu(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + orientation: "geometry_msgs.Quaternion | None" = None + orientation_covariance: list[float] = msgspec.field(default_factory=list) + angular_velocity: "geometry_msgs.Vector3 | None" = None + angular_velocity_covariance: list[float] = msgspec.field(default_factory=list) + linear_acceleration: "geometry_msgs.Vector3 | None" = None + linear_acceleration_covariance: list[float] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Imu' + __hash__: ClassVar[str] = 'RIHS01_7d9a00ff131080897a5ec7e26e315954b8eae3353c3f995c55faf71574000b5b' + class FluidPressure(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None fluid_pressure: float = 0.0 @@ -156,21 +181,52 @@ class JoyFeedbackArray(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'sensor_msgs/msg/JoyFeedbackArray' __hash__: ClassVar[str] = 'RIHS01_3287c32e1b688cae04555e465443df3cca7dae76ee4ebf85c4658d585037bcaa' -class CameraInfo(msgspec.Struct, frozen=True, kw_only=True): +class ChannelFloat32(msgspec.Struct, frozen=True, kw_only=True): + name: str = "" + values: list[float] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/ChannelFloat32' + __hash__: ClassVar[str] = 'RIHS01_92665437ddf39346f4ba39ee32e648390605b633cc077d40f4bd4d7b58af6cd4' + +class Image(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None height: int = 0 width: int = 0 - distortion_model: str = "" - d: list[float] = msgspec.field(default_factory=list) - k: list[float] = msgspec.field(default_factory=list) - r: list[float] = msgspec.field(default_factory=list) - p: list[float] = msgspec.field(default_factory=list) - binning_x: int = 0 - binning_y: int = 0 - roi: "sensor_msgs.RegionOfInterest | None" = None + encoding: str = "" + is_bigendian: int = 0 + step: int = 0 + data: bytes = b"" - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/CameraInfo' - __hash__: ClassVar[str] = 'RIHS01_b3dfd68ff46c9d56c80fd3bd4ed22c7a4ddce8c8348f2f59c299e73118e7e275' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Image' + __hash__: ClassVar[str] = 'RIHS01_d31d41a9a4c4bc8eae9be757b0beed306564f7526c88ea6a4588fb9582527d47' + +class MagneticField(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + magnetic_field: "geometry_msgs.Vector3 | None" = None + magnetic_field_covariance: list[float] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/MagneticField' + __hash__: ClassVar[str] = 'RIHS01_e80f32f56a20486c9923008fc1a1db07bbb273cbbf6a5b3bfa00835ee00e4dff' + +class Illuminance(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + illuminance: float = 0.0 + variance: float = 0.0 + + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Illuminance' + __hash__: ClassVar[str] = 'RIHS01_b954b25f452fcf81a91c9c2a7e3b3fd85c4c873d452aecb3cfd8fd1da732a22d' + +class NavSatFix(msgspec.Struct, frozen=True, kw_only=True): + header: "std_msgs.Header | None" = None + status: "sensor_msgs.NavSatStatus | None" = None + latitude: float = 0.0 + longitude: float = 0.0 + altitude: float = 0.0 + position_covariance: list[float] = msgspec.field(default_factory=list) + position_covariance_type: int = 0 + + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/NavSatFix' + __hash__: ClassVar[str] = 'RIHS01_62223ab3fe210a15976021da7afddc9e200dc9ec75231c1b6a557fc598a65404' class BatteryState(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -193,41 +249,17 @@ class BatteryState(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'sensor_msgs/msg/BatteryState' __hash__: ClassVar[str] = 'RIHS01_4bee5dfce981c98faa6828b868307a0a73f992ed0789f374ee96c8f840e69741' -class Imu(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - orientation: "geometry_msgs.Quaternion | None" = None - orientation_covariance: list[float] = msgspec.field(default_factory=list) - angular_velocity: "geometry_msgs.Vector3 | None" = None - angular_velocity_covariance: list[float] = msgspec.field(default_factory=list) - linear_acceleration: "geometry_msgs.Vector3 | None" = None - linear_acceleration_covariance: list[float] = msgspec.field(default_factory=list) - - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Imu' - __hash__: ClassVar[str] = 'RIHS01_7d9a00ff131080897a5ec7e26e315954b8eae3353c3f995c55faf71574000b5b' - -class PointCloud(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - points: list["geometry_msgs.Point32"] = msgspec.field(default_factory=list) - channels: list["sensor_msgs.ChannelFloat32"] = msgspec.field(default_factory=list) - - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/PointCloud' - __hash__: ClassVar[str] = 'RIHS01_614593df71d3c2b9bd4604a71b750fd218f0d65c045ea988b713719455a65b3b' - -class Temperature(msgspec.Struct, frozen=True, kw_only=True): +class Range(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None - temperature: float = 0.0 + radiation_type: int = 0 + field_of_view: float = 0.0 + min_range: float = 0.0 + max_range: float = 0.0 + range: float = 0.0 variance: float = 0.0 - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Temperature' - __hash__: ClassVar[str] = 'RIHS01_72514a14126ab9f8a9abec974c78e5610a367b59db5da355ff1fb982d5bad4b8' - -class JoyFeedback(msgspec.Struct, frozen=True, kw_only=True): - type: int = 0 - id: int = 0 - intensity: float = 0.0 - - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/JoyFeedback' - __hash__: ClassVar[str] = 'RIHS01_231dd362f71d6fc08272770d07120ad5fe5874ce2dbac70109b28986834290cd' + __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Range' + __hash__: ClassVar[str] = 'RIHS01_b42b62562e93cbfe9d42b82fe5994dfa3d63d7d5c90a317981703f7388adff3a' class MultiEchoLaserScan(msgspec.Struct, frozen=True, kw_only=True): header: "std_msgs.Header | None" = None @@ -244,38 +276,6 @@ class MultiEchoLaserScan(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'sensor_msgs/msg/MultiEchoLaserScan' __hash__: ClassVar[str] = 'RIHS01_ba5eac341cd5bbb2701527aa4568e8baec172b69cadb9a1945d6f149d087ee48' -class RelativeHumidity(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - relative_humidity: float = 0.0 - variance: float = 0.0 - - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/RelativeHumidity' - __hash__: ClassVar[str] = 'RIHS01_8687c99b4fb393cb2e545e407b5ea7fd0b5d8960bcd849a0f86c544740138839' - -class Illuminance(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - illuminance: float = 0.0 - variance: float = 0.0 - - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/Illuminance' - __hash__: ClassVar[str] = 'RIHS01_b954b25f452fcf81a91c9c2a7e3b3fd85c4c873d452aecb3cfd8fd1da732a22d' - -class MagneticField(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - magnetic_field: "geometry_msgs.Vector3 | None" = None - magnetic_field_covariance: list[float] = msgspec.field(default_factory=list) - - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/MagneticField' - __hash__: ClassVar[str] = 'RIHS01_e80f32f56a20486c9923008fc1a1db07bbb273cbbf6a5b3bfa00835ee00e4dff' - -class CompressedImage(msgspec.Struct, frozen=True, kw_only=True): - header: "std_msgs.Header | None" = None - format: str = "" - data: bytes = b"" - - __msgtype__: ClassVar[str] = 'sensor_msgs/msg/CompressedImage' - __hash__: ClassVar[str] = 'RIHS01_15640771531571185e2efc8a100baf923961a4d15d5569652e6cb6691e8e371a' - class SetCameraInfoRequest(msgspec.Struct, frozen=True, kw_only=True): camera_info: "sensor_msgs.CameraInfo | None" = None @@ -289,3 +289,9 @@ class SetCameraInfoResponse(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'sensor_msgs/msg/SetCameraInfoResponse' __hash__: ClassVar[str] = 'RIHS01_a10cca5d33dc637c8d49db50ab288701a3592bb9cd854f2f16a0659613b68984' +class SetCameraInfo: + """Service grouping type. Use SetCameraInfo.Request and SetCameraInfo.Response.""" + __srvtype__: ClassVar[str] = 'sensor_msgs/srv/SetCameraInfo' + Request: ClassVar[type] = SetCameraInfoRequest + Response: ClassVar[type] = SetCameraInfoResponse + diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/std_msgs.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/std_msgs.py index 6b7275d6..b2ae8ff5 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/std_msgs.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/std_msgs.py @@ -2,71 +2,91 @@ import msgspec from typing import ClassVar -class ByteMultiArray(msgspec.Struct, frozen=True, kw_only=True): +class UInt16MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "std_msgs.MultiArrayLayout | None" = None - data: bytes = b"" + data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'std_msgs/msg/ByteMultiArray' - __hash__: ClassVar[str] = 'RIHS01_972fec7f50ab3c1d06783c228e79e8a9a509021708c511c059926261ada901d4' + __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt16MultiArray' + __hash__: ClassVar[str] = 'RIHS01_94fe73428ec63baecc774f8fb82406123e9291cf728f1b7c91caf5335129492b' -class String(msgspec.Struct, frozen=True, kw_only=True): - data: str = "" +class Byte(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/String' - __hash__: ClassVar[str] = 'RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Byte' + __hash__: ClassVar[str] = 'RIHS01_41e1a3345f73fe93ede006da826a6ee274af23dd4653976ff249b0f44e3e798f' -class UInt64MultiArray(msgspec.Struct, frozen=True, kw_only=True): +class Char(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 + + __msgtype__: ClassVar[str] = 'std_msgs/msg/Char' + __hash__: ClassVar[str] = 'RIHS01_3ad2d04dd29ba19d04b16659afa3ccaedd691914b02a64e82e252f2fa6a586a9' + +class Int8MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "std_msgs.MultiArrayLayout | None" = None data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt64MultiArray' - __hash__: ClassVar[str] = 'RIHS01_fc1c685c2f76bdc6983da025cb25d2db5fb5157b059e300f6d957d86f981b366' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Int8MultiArray' + __hash__: ClassVar[str] = 'RIHS01_f21998d4b492abd63330765d75d5831238d400740386f651f13a872a4d2188db' -class Bool(msgspec.Struct, frozen=True, kw_only=True): - data: bool = False +class Float64MultiArray(msgspec.Struct, frozen=True, kw_only=True): + layout: "std_msgs.MultiArrayLayout | None" = None + data: list[float] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'std_msgs/msg/Bool' - __hash__: ClassVar[str] = 'RIHS01_feb91e995ff9ebd09c0cb3d2aed18b11077585839fb5db80193b62d74528f6c9' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Float64MultiArray' + __hash__: ClassVar[str] = 'RIHS01_1025ddc6b9552d191f89ef1a8d2f60f3d373e28b283d8891ddcc974e8c55397f' -class Int8(msgspec.Struct, frozen=True, kw_only=True): +class UInt32(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/Int8' - __hash__: ClassVar[str] = 'RIHS01_26525065a403d972cb672f0777e333f0c799ad444ae5fcd79e43d1e73bd0f440' + __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt32' + __hash__: ClassVar[str] = 'RIHS01_a5c874829b752bc5fa190024b0ad76f578cc278271e855c7d02a818b3516fb4a' -class Int16MultiArray(msgspec.Struct, frozen=True, kw_only=True): +class ColorRGBA(msgspec.Struct, frozen=True, kw_only=True): + r: float = 0.0 + g: float = 0.0 + b: float = 0.0 + a: float = 0.0 + + __msgtype__: ClassVar[str] = 'std_msgs/msg/ColorRGBA' + __hash__: ClassVar[str] = 'RIHS01_77a7a5b9ae477306097665106e0413ba74440245b1f3d0c6d6405fe5c7813fe8' + +class UInt32MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "std_msgs.MultiArrayLayout | None" = None data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'std_msgs/msg/Int16MultiArray' - __hash__: ClassVar[str] = 'RIHS01_b58810e8e5b90fb19a5062469eb8409f5ab11a446d60de7157a1457e52a076ce' + __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt32MultiArray' + __hash__: ClassVar[str] = 'RIHS01_6c2577c7ad3cbdcc2164a41c12f1d5ad314ea320f3fb1ee47e78019fe16bb5b0' -class Byte(msgspec.Struct, frozen=True, kw_only=True): +class Int16(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/Byte' - __hash__: ClassVar[str] = 'RIHS01_41e1a3345f73fe93ede006da826a6ee274af23dd4653976ff249b0f44e3e798f' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Int16' + __hash__: ClassVar[str] = 'RIHS01_1dcc3464e47c288a55f943a389d337cdb06804de3f5cd7a266b0de718eee17e5' -class Int32(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 +class String(msgspec.Struct, frozen=True, kw_only=True): + data: str = "" - __msgtype__: ClassVar[str] = 'std_msgs/msg/Int32' - __hash__: ClassVar[str] = 'RIHS01_b6578ded3c58c626cfe8d1a6fb6e04f706f97e9f03d2727c9ff4e74b1cef0deb' + __msgtype__: ClassVar[str] = 'std_msgs/msg/String' + __hash__: ClassVar[str] = 'RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18' -class UInt8MultiArray(msgspec.Struct, frozen=True, kw_only=True): +class Float32(msgspec.Struct, frozen=True, kw_only=True): + data: float = 0.0 + + __msgtype__: ClassVar[str] = 'std_msgs/msg/Float32' + __hash__: ClassVar[str] = 'RIHS01_7170d3d8f841f7be3172ce5f4f59f3a4d7f63b0447e8b33327601ad64d83d6e2' + +class ByteMultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "std_msgs.MultiArrayLayout | None" = None data: bytes = b"" - __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt8MultiArray' - __hash__: ClassVar[str] = 'RIHS01_5687e861b8d307a5e48b7515467ae7a5fc2daf805bd0ce6d8e9e604bade9f385' + __msgtype__: ClassVar[str] = 'std_msgs/msg/ByteMultiArray' + __hash__: ClassVar[str] = 'RIHS01_972fec7f50ab3c1d06783c228e79e8a9a509021708c511c059926261ada901d4' -class MultiArrayDimension(msgspec.Struct, frozen=True, kw_only=True): - label: str = "" - size: int = 0 - stride: int = 0 +class UInt16(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/MultiArrayDimension' - __hash__: ClassVar[str] = 'RIHS01_5e773a60a4c7fc8a54985f307c7837aa2994252a126c301957a24e31282c9cbe' + __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt16' + __hash__: ClassVar[str] = 'RIHS01_08a406e4b022bc22e907f985d6a9e9dd1d4fbecae573549cf49350113e7757b1' class Int32MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "std_msgs.MultiArrayLayout | None" = None @@ -75,17 +95,20 @@ class Int32MultiArray(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'std_msgs/msg/Int32MultiArray' __hash__: ClassVar[str] = 'RIHS01_84a7346323525d1b4dfca899df3820f245e54009dac5a6b69217d14fdefd1701' -class UInt8(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 +class Float32MultiArray(msgspec.Struct, frozen=True, kw_only=True): + layout: "std_msgs.MultiArrayLayout | None" = None + data: list[float] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt8' - __hash__: ClassVar[str] = 'RIHS01_6138bd83d8c3569cb80a667db03cfc1629f529fee79d944c39c34e352e72f010' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Float32MultiArray' + __hash__: ClassVar[str] = 'RIHS01_0599f6f85b4bfca379873a0b4375a0aca022156bd2d7021275d116ed1fa8bfe0' -class UInt64(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 +class MultiArrayDimension(msgspec.Struct, frozen=True, kw_only=True): + label: str = "" + size: int = 0 + stride: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt64' - __hash__: ClassVar[str] = 'RIHS01_fbdc52018fc13755dce18024d1a671c856aa8b4aaf63adfb095b608f98e8c943' + __msgtype__: ClassVar[str] = 'std_msgs/msg/MultiArrayDimension' + __hash__: ClassVar[str] = 'RIHS01_5e773a60a4c7fc8a54985f307c7837aa2994252a126c301957a24e31282c9cbe' class Header(msgspec.Struct, frozen=True, kw_only=True): stamp: "builtin_interfaces.Time | None" = msgspec.field(default_factory=lambda: {'sec': 0, 'nanosec': 0}) @@ -94,16 +117,11 @@ class Header(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'std_msgs/msg/Header' __hash__: ClassVar[str] = 'RIHS01_f49fb3ae2cf070f793645ff749683ac6b06203e41c891e17701b1cb597ce6a01' -class Empty(msgspec.Struct, frozen=True, kw_only=True): - - __msgtype__: ClassVar[str] = 'std_msgs/msg/Empty' - __hash__: ClassVar[str] = 'RIHS01_20b625256f32d5dbc0d04fee44f43c41e51c70d3502f84b4a08e7a9c26a96312' - -class UInt16(msgspec.Struct, frozen=True, kw_only=True): +class Int8(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt16' - __hash__: ClassVar[str] = 'RIHS01_08a406e4b022bc22e907f985d6a9e9dd1d4fbecae573549cf49350113e7757b1' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Int8' + __hash__: ClassVar[str] = 'RIHS01_26525065a403d972cb672f0777e333f0c799ad444ae5fcd79e43d1e73bd0f440' class Float64(msgspec.Struct, frozen=True, kw_only=True): data: float = 0.0 @@ -111,19 +129,6 @@ class Float64(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'std_msgs/msg/Float64' __hash__: ClassVar[str] = 'RIHS01_705ba9c3d1a09df43737eb67095534de36fd426c0587779bda2bc51fe790182a' -class Char(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 - - __msgtype__: ClassVar[str] = 'std_msgs/msg/Char' - __hash__: ClassVar[str] = 'RIHS01_3ad2d04dd29ba19d04b16659afa3ccaedd691914b02a64e82e252f2fa6a586a9' - -class MultiArrayLayout(msgspec.Struct, frozen=True, kw_only=True): - dim: list["std_msgs.MultiArrayDimension"] = msgspec.field(default_factory=list) - data_offset: int = 0 - - __msgtype__: ClassVar[str] = 'std_msgs/msg/MultiArrayLayout' - __hash__: ClassVar[str] = 'RIHS01_4c66e6f78e740ac103a94cf63259f968e48c617e7699e829b63c21a5cb50dac6' - class Int64MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "std_msgs.MultiArrayLayout | None" = None data: list[int] = msgspec.field(default_factory=list) @@ -131,39 +136,29 @@ class Int64MultiArray(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'std_msgs/msg/Int64MultiArray' __hash__: ClassVar[str] = 'RIHS01_e60f9fe34d697f0939ad49d33158693c1277fbac0e2f04b7c2995dc21c89b422' -class Float32(msgspec.Struct, frozen=True, kw_only=True): - data: float = 0.0 - - __msgtype__: ClassVar[str] = 'std_msgs/msg/Float32' - __hash__: ClassVar[str] = 'RIHS01_7170d3d8f841f7be3172ce5f4f59f3a4d7f63b0447e8b33327601ad64d83d6e2' - -class Int8MultiArray(msgspec.Struct, frozen=True, kw_only=True): - layout: "std_msgs.MultiArrayLayout | None" = None - data: list[int] = msgspec.field(default_factory=list) +class UInt64(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/Int8MultiArray' - __hash__: ClassVar[str] = 'RIHS01_f21998d4b492abd63330765d75d5831238d400740386f651f13a872a4d2188db' + __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt64' + __hash__: ClassVar[str] = 'RIHS01_fbdc52018fc13755dce18024d1a671c856aa8b4aaf63adfb095b608f98e8c943' -class Float32MultiArray(msgspec.Struct, frozen=True, kw_only=True): - layout: "std_msgs.MultiArrayLayout | None" = None - data: list[float] = msgspec.field(default_factory=list) +class UInt8(msgspec.Struct, frozen=True, kw_only=True): + data: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/Float32MultiArray' - __hash__: ClassVar[str] = 'RIHS01_0599f6f85b4bfca379873a0b4375a0aca022156bd2d7021275d116ed1fa8bfe0' + __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt8' + __hash__: ClassVar[str] = 'RIHS01_6138bd83d8c3569cb80a667db03cfc1629f529fee79d944c39c34e352e72f010' -class UInt32MultiArray(msgspec.Struct, frozen=True, kw_only=True): - layout: "std_msgs.MultiArrayLayout | None" = None - data: list[int] = msgspec.field(default_factory=list) +class Empty(msgspec.Struct, frozen=True, kw_only=True): - __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt32MultiArray' - __hash__: ClassVar[str] = 'RIHS01_6c2577c7ad3cbdcc2164a41c12f1d5ad314ea320f3fb1ee47e78019fe16bb5b0' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Empty' + __hash__: ClassVar[str] = 'RIHS01_20b625256f32d5dbc0d04fee44f43c41e51c70d3502f84b4a08e7a9c26a96312' -class UInt16MultiArray(msgspec.Struct, frozen=True, kw_only=True): +class Int16MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "std_msgs.MultiArrayLayout | None" = None data: list[int] = msgspec.field(default_factory=list) - __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt16MultiArray' - __hash__: ClassVar[str] = 'RIHS01_94fe73428ec63baecc774f8fb82406123e9291cf728f1b7c91caf5335129492b' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Int16MultiArray' + __hash__: ClassVar[str] = 'RIHS01_b58810e8e5b90fb19a5062469eb8409f5ab11a446d60de7157a1457e52a076ce' class Int64(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 @@ -171,31 +166,36 @@ class Int64(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'std_msgs/msg/Int64' __hash__: ClassVar[str] = 'RIHS01_8cd1048c2f186b6bd9a92472dc1ce51723c0833a221e2b7aecfff111774f4b49' -class UInt32(msgspec.Struct, frozen=True, kw_only=True): - data: int = 0 +class MultiArrayLayout(msgspec.Struct, frozen=True, kw_only=True): + dim: list["std_msgs.MultiArrayDimension"] = msgspec.field(default_factory=list) + data_offset: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt32' - __hash__: ClassVar[str] = 'RIHS01_a5c874829b752bc5fa190024b0ad76f578cc278271e855c7d02a818b3516fb4a' + __msgtype__: ClassVar[str] = 'std_msgs/msg/MultiArrayLayout' + __hash__: ClassVar[str] = 'RIHS01_4c66e6f78e740ac103a94cf63259f968e48c617e7699e829b63c21a5cb50dac6' -class Float64MultiArray(msgspec.Struct, frozen=True, kw_only=True): +class UInt8MultiArray(msgspec.Struct, frozen=True, kw_only=True): layout: "std_msgs.MultiArrayLayout | None" = None - data: list[float] = msgspec.field(default_factory=list) + data: bytes = b"" - __msgtype__: ClassVar[str] = 'std_msgs/msg/Float64MultiArray' - __hash__: ClassVar[str] = 'RIHS01_1025ddc6b9552d191f89ef1a8d2f60f3d373e28b283d8891ddcc974e8c55397f' + __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt8MultiArray' + __hash__: ClassVar[str] = 'RIHS01_5687e861b8d307a5e48b7515467ae7a5fc2daf805bd0ce6d8e9e604bade9f385' -class Int16(msgspec.Struct, frozen=True, kw_only=True): +class UInt64MultiArray(msgspec.Struct, frozen=True, kw_only=True): + layout: "std_msgs.MultiArrayLayout | None" = None + data: list[int] = msgspec.field(default_factory=list) + + __msgtype__: ClassVar[str] = 'std_msgs/msg/UInt64MultiArray' + __hash__: ClassVar[str] = 'RIHS01_fc1c685c2f76bdc6983da025cb25d2db5fb5157b059e300f6d957d86f981b366' + +class Int32(msgspec.Struct, frozen=True, kw_only=True): data: int = 0 - __msgtype__: ClassVar[str] = 'std_msgs/msg/Int16' - __hash__: ClassVar[str] = 'RIHS01_1dcc3464e47c288a55f943a389d337cdb06804de3f5cd7a266b0de718eee17e5' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Int32' + __hash__: ClassVar[str] = 'RIHS01_b6578ded3c58c626cfe8d1a6fb6e04f706f97e9f03d2727c9ff4e74b1cef0deb' -class ColorRGBA(msgspec.Struct, frozen=True, kw_only=True): - r: float = 0.0 - g: float = 0.0 - b: float = 0.0 - a: float = 0.0 +class Bool(msgspec.Struct, frozen=True, kw_only=True): + data: bool = False - __msgtype__: ClassVar[str] = 'std_msgs/msg/ColorRGBA' - __hash__: ClassVar[str] = 'RIHS01_77a7a5b9ae477306097665106e0413ba74440245b1f3d0c6d6405fe5c7813fe8' + __msgtype__: ClassVar[str] = 'std_msgs/msg/Bool' + __hash__: ClassVar[str] = 'RIHS01_feb91e995ff9ebd09c0cb3d2aed18b11077585839fb5db80193b62d74528f6c9' diff --git a/crates/hiroz-msgs/python/hiroz_msgs_py/types/type_description_interfaces.py b/crates/hiroz-msgs/python/hiroz_msgs_py/types/type_description_interfaces.py index fcbbc3b7..7a979a01 100644 --- a/crates/hiroz-msgs/python/hiroz_msgs_py/types/type_description_interfaces.py +++ b/crates/hiroz-msgs/python/hiroz_msgs_py/types/type_description_interfaces.py @@ -2,14 +2,12 @@ import msgspec from typing import ClassVar -class FieldType(msgspec.Struct, frozen=True, kw_only=True): - type_id: int = 0 - capacity: int = 0 - string_capacity: int = 0 - nested_type_name: str = "" +class KeyValue(msgspec.Struct, frozen=True, kw_only=True): + key: str = "" + value: str = "" - __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/FieldType' - __hash__: ClassVar[str] = 'RIHS01_a70b6dd919645a03a3586f7f821defbc886ea3e531a1d95cc0f380a3973ccaa6' + __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/KeyValue' + __hash__: ClassVar[str] = 'RIHS01_274fe56bf14f33c7512e34c646a37579ee36779f745f049a9760763e817f0c42' class TypeSource(msgspec.Struct, frozen=True, kw_only=True): type_name: str = "" @@ -26,12 +24,22 @@ class IndividualTypeDescription(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/IndividualTypeDescription' __hash__: ClassVar[str] = 'RIHS01_55c827d86c3c141bdd318fe6c22e11190e4d3b37c8f4f9751a084aa05ce96560' -class KeyValue(msgspec.Struct, frozen=True, kw_only=True): - key: str = "" - value: str = "" +class Field(msgspec.Struct, frozen=True, kw_only=True): + name: str = "" + type: "type_description_interfaces.FieldType | None" = None + default_value: str = "" - __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/KeyValue' - __hash__: ClassVar[str] = 'RIHS01_274fe56bf14f33c7512e34c646a37579ee36779f745f049a9760763e817f0c42' + __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/Field' + __hash__: ClassVar[str] = 'RIHS01_c0b01379cd4226281285ccaf6be46653968f855f7c5e41614ff5d7a854efef7c' + +class FieldType(msgspec.Struct, frozen=True, kw_only=True): + type_id: int = 0 + capacity: int = 0 + string_capacity: int = 0 + nested_type_name: str = "" + + __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/FieldType' + __hash__: ClassVar[str] = 'RIHS01_a70b6dd919645a03a3586f7f821defbc886ea3e531a1d95cc0f380a3973ccaa6' class TypeDescription(msgspec.Struct, frozen=True, kw_only=True): type_description: "type_description_interfaces.IndividualTypeDescription | None" = None @@ -40,14 +48,6 @@ class TypeDescription(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/TypeDescription' __hash__: ClassVar[str] = 'RIHS01_739f2508c9fa3a6f330913ff5b9d25fb74159a077da71e1087f51a60c12a080b' -class Field(msgspec.Struct, frozen=True, kw_only=True): - name: str = "" - type: "type_description_interfaces.FieldType | None" = None - default_value: str = "" - - __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/Field' - __hash__: ClassVar[str] = 'RIHS01_c0b01379cd4226281285ccaf6be46653968f855f7c5e41614ff5d7a854efef7c' - class GetTypeDescriptionRequest(msgspec.Struct, frozen=True, kw_only=True): type_name: str = "" type_hash: str = "" @@ -66,3 +66,9 @@ class GetTypeDescriptionResponse(msgspec.Struct, frozen=True, kw_only=True): __msgtype__: ClassVar[str] = 'type_description_interfaces/msg/GetTypeDescriptionResponse' __hash__: ClassVar[str] = 'RIHS01_69b9c19c1021405984cc60dbbb1edceb147a6538b411d812ba6afabeed962cd5' +class GetTypeDescription: + """Service grouping type. Use GetTypeDescription.Request and GetTypeDescription.Response.""" + __srvtype__: ClassVar[str] = 'type_description_interfaces/srv/GetTypeDescription' + Request: ClassVar[type] = GetTypeDescriptionRequest + Response: ClassVar[type] = GetTypeDescriptionResponse + diff --git a/crates/hiroz-py/examples/action_demo.py b/crates/hiroz-py/examples/action_demo.py index b6237f67..da5939a4 100644 --- a/crates/hiroz-py/examples/action_demo.py +++ b/crates/hiroz-py/examples/action_demo.py @@ -101,8 +101,10 @@ def run_client(ctx, action: str, target: int, cancel_after: float | None): action, CountToGoal, CountToResult, CountToFeedback ) - # Give server time to advertise - time.sleep(1.0) + # Wait for the action server instead of sleeping (P1). + if not client.wait_for_server(timeout=5.0): + print("CLIENT:ERROR:server unavailable", flush=True) + sys.exit(1) print(f"CLIENT:SEND_GOAL:{target}", flush=True) handle = client.send_goal(CountToGoal(target=target)) diff --git a/crates/hiroz-py/examples/service_demo.py b/crates/hiroz-py/examples/service_demo.py index 52da5a17..00f9fa4d 100644 --- a/crates/hiroz-py/examples/service_demo.py +++ b/crates/hiroz-py/examples/service_demo.py @@ -10,7 +10,6 @@ import argparse import sys -import time import hiroz_py from hiroz_py import example_interfaces @@ -20,7 +19,8 @@ def run_server(ctx, service: str, max_requests: int): """Run the AddTwoInts service server.""" node = ctx.create_node("add_two_ints_server").build() - server = node.create_server(service, example_interfaces.AddTwoIntsRequest) + # rclpy-style service grouping type (AddTwoInts.Request / .Response). + server = node.create_server(service, example_interfaces.AddTwoInts) print("SERVER:READY", flush=True) @@ -44,18 +44,21 @@ def run_server(ctx, service: str, max_requests: int): def run_client(ctx, service: str, a: int, b: int, timeout: float): """Run the AddTwoInts service client.""" node = ctx.create_node("add_two_ints_client").build() - client = node.create_client(service, example_interfaces.AddTwoIntsRequest) + client = node.create_client(service, example_interfaces.AddTwoInts) - # Wait for service discovery - time.sleep(1.0) + # Wait for the server to appear instead of sleeping (P1). + if not client.wait_for_service(timeout=5.0): + print("CLIENT:ERROR:service unavailable", flush=True) + sys.exit(1) print(f"CLIENT:REQUEST:{a}+{b}", flush=True) - req = example_interfaces.AddTwoIntsRequest(a=a, b=b) + req = example_interfaces.AddTwoInts.Request(a=a, b=b) try: resp = client.call(req, timeout=timeout) print(f"CLIENT:RESPONSE:{resp.sum}", flush=True) - except RuntimeError as e: + except hiroz_py.HirozError as e: + # TimeoutError is a subclass of HirozError; catch the base to cover both. print(f"CLIENT:ERROR:{e}", flush=True) sys.exit(1) diff --git a/crates/hiroz-py/examples/topic_demo.py b/crates/hiroz-py/examples/topic_demo.py index aa127a41..6b4d0fd8 100644 --- a/crates/hiroz-py/examples/topic_demo.py +++ b/crates/hiroz-py/examples/topic_demo.py @@ -24,6 +24,9 @@ def run_talker(ctx, topic: str, count: int, interval: float): print(f"Talker started. Publishing to {topic}...") + # Wait for at least one subscriber instead of racing (P1). + pub.wait_for_subscription(count=1, timeout=5.0) + i = 0 while count == 0 or i < count: message = f"Hello from Python {i}" diff --git a/crates/hiroz-py/python/hiroz_py/__init__.py b/crates/hiroz-py/python/hiroz_py/__init__.py index 84fe7d2b..3c051907 100644 --- a/crates/hiroz-py/python/hiroz_py/__init__.py +++ b/crates/hiroz-py/python/hiroz_py/__init__.py @@ -33,3 +33,54 @@ QOS_SENSOR_DATA: Final[QosProfile] = QosProfile.sensor_data() QOS_PARAMETERS: Final[QosProfile] = QosProfile.parameters() QOS_SERVICES: Final[QosProfile] = QosProfile.services() + + +# --------------------------------------------------------------------------- +# rclpy-style method aliases (P3) +# +# hiroz keeps its native names (create_subscriber / create_server) as the +# canonical API; these aliases let rclpy code read naturally. create_service +# is a true alias because create_server now supports rclpy's optional +# callback= form (P6) in addition to pull mode. +# --------------------------------------------------------------------------- + +ZNode.create_subscription = ZNode.create_subscriber # type: ignore[attr-defined] +ZNode.create_service = ZNode.create_server # type: ignore[attr-defined] + + +# --------------------------------------------------------------------------- +# QoS policy enum holders (P8) +# +# String-valued so they parse straight through QosProfile, while giving users +# discoverable, typo-proof constants instead of bare strings. Mirrors rclpy's +# rclpy.qos.ReliabilityPolicy / DurabilityPolicy / HistoryPolicy / LivelinessPolicy. +# --------------------------------------------------------------------------- + + +class ReliabilityPolicy: + """QoS reliability policy constants.""" + + RELIABLE: Final[str] = "reliable" + BEST_EFFORT: Final[str] = "best_effort" + + +class DurabilityPolicy: + """QoS durability policy constants.""" + + VOLATILE: Final[str] = "volatile" + TRANSIENT_LOCAL: Final[str] = "transient_local" + + +class HistoryPolicy: + """QoS history policy constants.""" + + KEEP_LAST: Final[str] = "keep_last" + KEEP_ALL: Final[str] = "keep_all" + + +class LivelinessPolicy: + """QoS liveliness policy constants.""" + + AUTOMATIC: Final[str] = "automatic" + MANUAL_BY_TOPIC: Final[str] = "manual_by_topic" + MANUAL_BY_NODE: Final[str] = "manual_by_node" diff --git a/crates/hiroz-py/python/hiroz_py/__init__.pyi b/crates/hiroz-py/python/hiroz_py/__init__.pyi index a355850e..52eb280e 100644 --- a/crates/hiroz-py/python/hiroz_py/__init__.pyi +++ b/crates/hiroz-py/python/hiroz_py/__init__.pyi @@ -79,6 +79,30 @@ QOS_SENSOR_DATA: Final[QosProfile] = QosProfile.sensor_data() QOS_PARAMETERS: Final[QosProfile] = QosProfile.parameters() QOS_SERVICES: Final[QosProfile] = QosProfile.services() +# --------------------------------------------------------------------------- +# QoS policy enum holders (P8) +# --------------------------------------------------------------------------- + +class ReliabilityPolicy: + RELIABLE: Final[str] + BEST_EFFORT: Final[str] + +class DurabilityPolicy: + VOLATILE: Final[str] + TRANSIENT_LOCAL: Final[str] + +class HistoryPolicy: + KEEP_LAST: Final[str] + KEEP_ALL: Final[str] + +class LivelinessPolicy: + AUTOMATIC: Final[str] + MANUAL_BY_TOPIC: Final[str] + MANUAL_BY_NODE: Final[str] + +# A QoS argument: a QosProfile, an int depth shorthand, or a legacy dict. +QosLike = QosProfile | int | dict[str, object] + # --------------------------------------------------------------------------- # GoalStatus # --------------------------------------------------------------------------- @@ -175,30 +199,44 @@ class ZNode: self, topic: str, msg_type: Any, - qos: QosProfile | dict[str, object] | None = None, + qos: QosLike | None = None, ) -> ZPublisher: ... def create_subscriber( self, topic: str, msg_type: Any, - qos: QosProfile | dict[str, object] | None = None, + qos: QosLike | None = None, + callback: Any | None = None, + ) -> ZSubscriber: ... + # rclpy-style alias for create_subscriber (P3). + def create_subscription( + self, + topic: str, + msg_type: Any, + qos: QosLike | None = None, callback: Any | None = None, ) -> ZSubscriber: ... def create_client(self, service: str, srv_type: Any) -> ZClient: ... - def create_server(self, service: str, srv_type: Any) -> ZServer: ... + def create_server( + self, service: str, srv_type: Any, callback: Any | None = None + ) -> ZServer: ... + # rclpy-style alias for create_server (P3); pass callback= for callback mode (P6). + def create_service( + self, service: str, srv_type: Any, callback: Any | None = None + ) -> ZServer: ... def create_action_client( self, action_name: str, goal_type: Any, - result_type: Any, - feedback_type: Any, + result_type: Any | None = None, + feedback_type: Any | None = None, ) -> ZActionClient: ... def create_action_server( self, action_name: str, goal_type: Any, - result_type: Any, - feedback_type: Any, + result_type: Any | None = None, + feedback_type: Any | None = None, ) -> ZActionServer: ... def get_topic_names_and_types(self) -> list[tuple[str, str]]: ... def get_node_names(self) -> list[tuple[str, str]]: ... @@ -213,6 +251,9 @@ class ZNode: class ZPublisher: def publish(self, data: Any) -> None: ... def publish_raw(self, data: bytes) -> None: ... + def wait_for_subscription( + self, count: int = 1, timeout: float | None = None + ) -> bool: ... def get_type_name(self) -> str: ... # --------------------------------------------------------------------------- @@ -236,6 +277,7 @@ class ZSubscriber: class ZClient: def call(self, data: Any, timeout: float | None = None) -> Any: ... + def wait_for_service(self, timeout: float | None = None) -> bool: ... def get_type_name(self) -> str: ... # --------------------------------------------------------------------------- @@ -253,6 +295,7 @@ class ZServer: class ZActionClient: def send_goal(self, goal: Any) -> ActionGoalHandle: ... + def wait_for_server(self, timeout: float | None = None) -> bool: ... @property def goal_type(self) -> Any: ... diff --git a/crates/hiroz-py/src/action.rs b/crates/hiroz-py/src/action.rs index 5bda54c4..ff07c2f6 100644 --- a/crates/hiroz-py/src/action.rs +++ b/crates/hiroz-py/src/action.rs @@ -139,20 +139,28 @@ pub struct PyZActionClient { goal_type: Py, result_type: Py, feedback_type: Py, + /// Shared graph + the action's `send_goal` service name, used by `wait_for_server`. + graph: Arc, + send_goal_service: String, } impl PyZActionClient { + #[allow(clippy::too_many_arguments)] pub fn new( inner: RawActionClient, goal_type: Py, result_type: Py, feedback_type: Py, + graph: Arc, + send_goal_service: String, ) -> Self { Self { inner: Arc::new(inner), goal_type, result_type, feedback_type, + graph, + send_goal_service, } } } @@ -180,7 +188,7 @@ impl PyZActionClient { tokio::time::timeout(Duration::from_secs(11), client.send_goal(goal_msg)) .await .map_err(|_| { - pyo3::exceptions::PyRuntimeError::new_err( + crate::error::TimeoutError::new_err( "send_goal timed out: no action server responded", ) })? @@ -243,6 +251,21 @@ impl PyZActionClient { }) } + /// Wait until an action server for this action is available. + /// + /// Mirrors rclpy's `ActionClient.wait_for_server(timeout_sec)`. Polls the + /// discovery graph for the action's `send_goal` service. Returns True if a + /// server was found before `timeout`, False otherwise. + /// + /// Args: + /// timeout: Maximum seconds to wait. None waits forever. + #[pyo3(signature = (timeout=None))] + fn wait_for_server(&self, py: Python, timeout: Option) -> bool { + py.allow_threads(|| { + crate::graph::wait_for_service_server(&self.graph, &self.send_goal_service, timeout) + }) + } + /// Get the goal type class (for debugging). #[getter] fn goal_type(&self, py: Python) -> PyObject { diff --git a/crates/hiroz-py/src/error.rs b/crates/hiroz-py/src/error.rs index 5d8d1cd3..166c4f8e 100644 --- a/crates/hiroz-py/src/error.rs +++ b/crates/hiroz-py/src/error.rs @@ -8,6 +8,30 @@ pyo3::create_exception!(hiroz_py, TimeoutError, HirozError); pyo3::create_exception!(hiroz_py, SerializationError, HirozError); pyo3::create_exception!(hiroz_py, TypeMismatchError, HirozError); +/// Returns true if the error looks like a timeout (best-effort string sniff). +/// +/// The core does not yet expose a dedicated `Timeout` error variant, so we +/// classify on the rendered message. Centralized here so every call site +/// (service `call`, action `send_goal`, …) classifies identically. +pub(crate) fn is_timeout_error(e: &anyhow::Error) -> bool { + let s = e.to_string().to_lowercase(); + s.contains("timeout") || s.contains("timed out") +} + +/// Map a core error to the right Python exception. +/// +/// Timeout-shaped errors become `hiroz_py.TimeoutError`; everything else +/// becomes `hiroz_py.HirozError`. Use this for blocking calls that raise on +/// failure (e.g. `ZClient.call`). Methods whose documented contract is to +/// return `None` on timeout should keep doing so rather than calling this. +pub(crate) fn map_call_error(e: anyhow::Error) -> PyErr { + if is_timeout_error(&e) { + TimeoutError::new_err(format!("{:#}", e)) + } else { + HirozError::new_err(format!("{:#}", e)) + } +} + /// Trait for converting Rust errors to Python exceptions pub(crate) trait IntoPyErr { fn into_pyerr(self) -> PyErr; diff --git a/crates/hiroz-py/src/graph.rs b/crates/hiroz-py/src/graph.rs index 62fcd011..52069b47 100644 --- a/crates/hiroz-py/src/graph.rs +++ b/crates/hiroz-py/src/graph.rs @@ -3,6 +3,35 @@ use hiroz::entity::EndpointKind; use hiroz::graph::Graph; use std::sync::Arc; +use std::time::{Duration, Instant}; + +/// Poll interval for discovery waits. Matches the ~50ms cadence rclpy uses +/// internally for its wait-for-service spin. +const POLL_INTERVAL: Duration = Duration::from_millis(50); + +/// Block until at least one service server matching `service_name` is visible +/// in the graph, or `timeout` (seconds) elapses. `None` waits forever. +/// +/// Must be called with the GIL released (`py.allow_threads`) so it does not +/// stall other Python threads while sleeping. Returns true if a server appeared. +pub(crate) fn wait_for_service_server( + graph: &Arc, + service_name: &str, + timeout: Option, +) -> bool { + let deadline = timeout.map(|t| Instant::now() + Duration::from_secs_f64(t)); + loop { + if graph.count(EndpointKind::Service, service_name) > 0 { + return true; + } + if let Some(d) = deadline + && Instant::now() >= d + { + return false; + } + std::thread::sleep(POLL_INTERVAL); + } +} /// Python-accessible graph discovery methods. /// diff --git a/crates/hiroz-py/src/node.rs b/crates/hiroz-py/src/node.rs index 6e43eb4d..ea802866 100644 --- a/crates/hiroz-py/src/node.rs +++ b/crates/hiroz-py/src/node.rs @@ -113,6 +113,108 @@ fn extract_service_type_from_request_class( Ok((srv_type, type_info)) } +/// Extract service type info from either a service grouping class (P4, rclpy-style) +/// or a bare Request class (back-compat). +/// +/// A grouping class exposes `__srvtype__` (e.g. `"example_interfaces/srv/AddTwoInts"`) +/// plus `Request` / `Response` member classes. We read the type hash from the +/// `Request` member. Anything without `__srvtype__` falls through to the legacy +/// string-munging path on the Request class itself. +fn extract_service_type_info(srv_type: &Bound<'_, PyAny>) -> PyResult<(String, TypeInfo)> { + if let Ok(srvtype_attr) = srv_type.getattr("__srvtype__") + && let Ok(srv_type_str) = srvtype_attr.extract::() + { + // Grouping class: pull the type hash from the Request member. + let request_cls = srv_type.getattr("Request").map_err(|_| { + pyo3::exceptions::PyTypeError::new_err( + "Service grouping class with __srvtype__ must define a Request member", + ) + })?; + let type_hash = request_cls + .getattr("__hash__") + .ok() + .and_then(|v| v.extract::().ok()) + .and_then(|s| TypeHash::from_rihs_string(&s)) + .unwrap_or_else(TypeHash::zero); + let rust_type_name = python_type_to_rust_type(&srv_type_str); + return Ok((srv_type_str, TypeInfo::new(&rust_type_name, type_hash))); + } + // Back-compat: bare Request class. + extract_service_type_from_request_class(srv_type) +} + +/// If `topic` is not a string but `msg_type` is, the caller almost certainly used +/// the rclpy positional order `(msg_type, topic)`. Raise a self-explaining error +/// instead of a confusing downstream type failure (P2). +fn reject_swapped_args( + topic: &Bound<'_, PyAny>, + msg_type: &Bound<'_, PyAny>, + func: &str, +) -> PyResult<()> { + let topic_is_str = topic.is_instance_of::(); + let msg_is_str = msg_type.is_instance_of::(); + if !topic_is_str && msg_is_str { + return Err(pyo3::exceptions::PyTypeError::new_err(format!( + "arguments look swapped — hiroz uses ({func}(topic, msg_type, ...)) but rclpy uses \ + (msg_type, topic, ...). Pass by keyword: {func}(topic=..., msg_type=...)" + ))); + } + Ok(()) +} + +/// Resolve a topic argument to a `String`, with a clear error if it isn't a str. +fn extract_topic(topic: &Bound<'_, PyAny>) -> PyResult { + topic.extract::().map_err(|_| { + pyo3::exceptions::PyTypeError::new_err( + "topic must be a string (e.g. \"/chatter\"). Pass by keyword if unsure: topic=...", + ) + }) +} + +/// Extract Goal/Result/Feedback classes from either an action grouping class +/// (P7, rclpy-style — exposes `__actiontype__`, `Goal`, `Result`, `Feedback`) +/// or fall back to three explicitly-passed classes. +/// +/// Returns the three member classes as owned `PyObject`s. +fn resolve_action_types( + action_type: &Bound<'_, PyAny>, + result_type: Option<&Bound<'_, PyAny>>, + feedback_type: Option<&Bound<'_, PyAny>>, +) -> PyResult<(PyObject, PyObject, PyObject)> { + // Grouping class path: a single action type with member classes. + if action_type.hasattr("__actiontype__").unwrap_or(false) { + let goal = action_type.getattr("Goal").map_err(|_| { + pyo3::exceptions::PyTypeError::new_err( + "Action grouping class with __actiontype__ must define a Goal member", + ) + })?; + let result = action_type.getattr("Result").map_err(|_| { + pyo3::exceptions::PyTypeError::new_err( + "Action grouping class with __actiontype__ must define a Result member", + ) + })?; + let feedback = action_type.getattr("Feedback").map_err(|_| { + pyo3::exceptions::PyTypeError::new_err( + "Action grouping class with __actiontype__ must define a Feedback member", + ) + })?; + return Ok((goal.unbind(), result.unbind(), feedback.unbind())); + } + + // Back-compat: three separate classes. + let (Some(result), Some(feedback)) = (result_type, feedback_type) else { + return Err(pyo3::exceptions::PyTypeError::new_err( + "create_action_*: pass either a single action grouping class (with __actiontype__) \ + or all three of goal_type, result_type, feedback_type", + )); + }; + Ok(( + action_type.clone().unbind(), + result.clone().unbind(), + feedback.clone().unbind(), + )) +} + #[pyclass(name = "ZNodeBuilder")] pub struct PyZNodeBuilder { pub(crate) ctx: Arc, @@ -189,10 +291,12 @@ impl PyZNode { #[pyo3(signature = (topic, msg_type, qos=None))] fn create_publisher( &self, - topic: String, + topic: &Bound<'_, PyAny>, msg_type: &Bound<'_, PyAny>, qos: Option<&Bound<'_, PyAny>>, ) -> PyResult { + reject_swapped_args(topic, msg_type, "create_publisher")?; + let topic = extract_topic(topic)?; let (msg_type_str, type_info) = extract_type_info_from_class(msg_type)?; let qos_profile = extract_qos(qos)?; @@ -213,11 +317,13 @@ impl PyZNode { fn create_subscriber( &mut self, _py: Python, - topic: String, + topic: &Bound<'_, PyAny>, msg_type: &Bound<'_, PyAny>, qos: Option<&Bound<'_, PyAny>>, callback: Option, ) -> PyResult { + reject_swapped_args(topic, msg_type, "create_subscriber")?; + let topic = extract_topic(topic)?; let (msg_type_str, type_info) = extract_type_info_from_class(msg_type)?; let qos_profile = extract_qos(qos)?; @@ -262,16 +368,25 @@ impl PyZNode { } } - /// Create a service client + /// Create a service client. + /// + /// `srv_type` may be a service grouping class (rclpy-style, e.g. + /// `example_interfaces.AddTwoInts`) or the bare Request class (back-compat). fn create_client(&self, service: String, srv_type: &Bound<'_, PyAny>) -> PyResult { - let (srv_type_str, type_info) = extract_service_type_from_request_class(srv_type)?; + let (srv_type_str, type_info) = extract_service_type_info(srv_type)?; let client_builder = self .inner .create_client_impl::(&service, Some(type_info)); let zclient = client_builder.build().map_err(|e| e.into_pyerr())?; let wrapper = GenericClientWrapper::new(zclient); - Ok(PyZClient::new(Box::new(wrapper), srv_type_str)) + let qualified = self.qualify_service_name(&service); + Ok(PyZClient::new( + Box::new(wrapper), + srv_type_str, + Arc::clone(self.inner.graph()), + qualified, + )) } // -- Graph discovery methods -- @@ -310,22 +425,29 @@ impl PyZNode { /// `__msgtype__` and `__hash__` attributes (from `hiroz_msgs_py`). /// /// Returns a `ZActionClient` for sending goals and receiving results. + #[pyo3(signature = (action_name, goal_type, result_type=None, feedback_type=None))] fn create_action_client( &self, py: Python, action_name: String, goal_type: &Bound<'_, PyAny>, - result_type: &Bound<'_, PyAny>, - feedback_type: &Bound<'_, PyAny>, + result_type: Option<&Bound<'_, PyAny>>, + feedback_type: Option<&Bound<'_, PyAny>>, ) -> PyResult { + let (goal_obj, result_obj, feedback_obj) = + resolve_action_types(goal_type, result_type, feedback_type)?; + let goal_b = goal_obj.bind(py); + let result_b = result_obj.bind(py); + let feedback_b = feedback_obj.bind(py); + // __msgtype__ is still required (validates the class); __hash__ is optional. - extract_type_info_from_class(goal_type)?; - extract_type_info_from_class(result_type)?; - extract_type_info_from_class(feedback_type)?; + extract_type_info_from_class(goal_b)?; + extract_type_info_from_class(result_b)?; + extract_type_info_from_class(feedback_b)?; - let goal_ti = try_extract_type_info(goal_type); - let result_ti = try_extract_type_info(result_type); - let feedback_ti = try_extract_type_info(feedback_type); + let goal_ti = try_extract_type_info(goal_b); + let result_ti = try_extract_type_info(result_b); + let feedback_ti = try_extract_type_info(feedback_b); let node = Arc::clone(&self.inner); let rt = get_tokio_rt(); @@ -347,11 +469,20 @@ impl PyZNode { .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string())) })?; + // The action server advertises a `/_action/send_goal` service; + // wait_for_server polls the graph for it. + let send_goal_service = format!( + "{}/_action/send_goal", + self.qualify_service_name(&action_name) + ); + Ok(PyZActionClient::new( client, - goal_type.clone().unbind(), - result_type.clone().unbind(), - feedback_type.clone().unbind(), + goal_obj.clone_ref(py), + result_obj.clone_ref(py), + feedback_obj.clone_ref(py), + Arc::clone(self.inner.graph()), + send_goal_service, )) } @@ -361,22 +492,29 @@ impl PyZNode { /// `__msgtype__` and `__hash__` attributes (from `hiroz_msgs_py`). /// /// Returns a `ZActionServer` for receiving and executing goals. + #[pyo3(signature = (action_name, goal_type, result_type=None, feedback_type=None))] fn create_action_server( &self, py: Python, action_name: String, goal_type: &Bound<'_, PyAny>, - result_type: &Bound<'_, PyAny>, - feedback_type: &Bound<'_, PyAny>, + result_type: Option<&Bound<'_, PyAny>>, + feedback_type: Option<&Bound<'_, PyAny>>, ) -> PyResult { + let (goal_obj, result_obj, feedback_obj) = + resolve_action_types(goal_type, result_type, feedback_type)?; + let goal_b = goal_obj.bind(py); + let result_b = result_obj.bind(py); + let feedback_b = feedback_obj.bind(py); + // __msgtype__ is still required (validates the class); __hash__ is optional. - extract_type_info_from_class(goal_type)?; - extract_type_info_from_class(result_type)?; - extract_type_info_from_class(feedback_type)?; + extract_type_info_from_class(goal_b)?; + extract_type_info_from_class(result_b)?; + extract_type_info_from_class(feedback_b)?; - let goal_ti = try_extract_type_info(goal_type); - let result_ti = try_extract_type_info(result_type); - let feedback_ti = try_extract_type_info(feedback_type); + let goal_ti = try_extract_type_info(goal_b); + let result_ti = try_extract_type_info(result_b); + let feedback_ti = try_extract_type_info(feedback_b); let node = Arc::clone(&self.inner); let rt = get_tokio_rt(); @@ -400,9 +538,9 @@ impl PyZNode { Ok(PyZActionServer::new( server, - goal_type.clone().unbind(), - result_type.clone().unbind(), - feedback_type.clone().unbind(), + goal_obj.clone_ref(py), + result_obj.clone_ref(py), + feedback_obj.clone_ref(py), )) } @@ -422,15 +560,47 @@ impl PyZNode { Ok(()) } - /// Create a service server - fn create_server(&self, service: String, srv_type: &Bound<'_, PyAny>) -> PyResult { - let (srv_type_str, type_info) = extract_service_type_from_request_class(srv_type)?; + /// Create a service server. + /// + /// `srv_type` may be a service grouping class (rclpy-style) or the bare + /// Request class (back-compat). + /// + /// If `callback` is provided, the server runs in callback mode: a background + /// thread receives each request, invokes `callback(request)`, and sends the + /// returned value as the response. The caller never calls `take_request` / + /// `send_response`. If `callback` is None (default), the server is in pull + /// mode and the caller drives it via `take_request` / `send_response`. + #[pyo3(signature = (service, srv_type, callback=None))] + fn create_server( + &self, + service: String, + srv_type: &Bound<'_, PyAny>, + callback: Option, + ) -> PyResult { + let (srv_type_str, type_info) = extract_service_type_info(srv_type)?; let server_builder = self .inner .create_service_impl::(&service, Some(type_info)); let zserver = server_builder.build().map_err(|e| e.into_pyerr())?; let wrapper = GenericServerWrapper::new(zserver); - Ok(PyZServer::new(Box::new(wrapper), srv_type_str)) + + match callback { + Some(cb) => Ok(PyZServer::new_with_callback( + Arc::new(wrapper), + srv_type_str, + cb, + )), + None => Ok(PyZServer::new(Box::new(wrapper), srv_type_str)), + } + } +} + +impl PyZNode { + /// Qualify a service name against the node's namespace/name so the result + /// matches the entries the discovery graph stores. Absolute names pass through. + fn qualify_service_name(&self, service: &str) -> String { + hiroz::topic_name::qualify_topic_name(service, self.inner.namespace(), self.inner.name()) + .unwrap_or_else(|_| service.to_string()) } } diff --git a/crates/hiroz-py/src/pubsub.rs b/crates/hiroz-py/src/pubsub.rs index dbd5b179..0325dabd 100644 --- a/crates/hiroz-py/src/pubsub.rs +++ b/crates/hiroz-py/src/pubsub.rs @@ -40,6 +40,25 @@ impl PyZPublisher { self.inner.publish(data.into()).map_err(|e| e.into_pyerr()) } + /// Wait until at least `count` subscriptions match this publisher. + /// + /// Mirrors rclpy's discovery-wait pattern and removes the need for + /// `time.sleep(...)` before publishing. Returns True if `count` + /// subscriptions were matched before `timeout`, False otherwise. + /// + /// Args: + /// count: Number of subscriptions to wait for (default 1). + /// timeout: Maximum seconds to wait. None waits effectively forever. + #[pyo3(signature = (count=1, timeout=None))] + fn wait_for_subscription(&self, py: Python, count: usize, timeout: Option) -> bool { + // None → wait "forever"; cap at a large but finite duration so the + // background thread can still observe interpreter shutdown. + let dur = timeout + .map(Duration::from_secs_f64) + .unwrap_or(Duration::from_secs(60 * 60 * 24 * 365)); + py.allow_threads(|| self.inner.wait_for_subscription(count, dur)) + } + /// Get the topic name (for debugging) unsafe fn get_type_name(&self) -> String { self.type_name.clone() diff --git a/crates/hiroz-py/src/qos.rs b/crates/hiroz-py/src/qos.rs index 516ef4b2..eaa721f9 100644 --- a/crates/hiroz-py/src/qos.rs +++ b/crates/hiroz-py/src/qos.rs @@ -360,12 +360,28 @@ pub fn extract_qos(qos: Option<&Bound<'_, PyAny>>) -> PyResult { if let Ok(profile) = obj.extract::>() { return Ok(profile.inner); } + // rclpy-style int depth shorthand: `qos=10` == KeepLast(10). + // Checked before dict so a bare int is accepted anywhere a QoS is. + // `bool` is a subclass of `int` in Python; exclude it explicitly so + // `qos=True` is a clear type error rather than a depth of 1. + if !obj.is_instance_of::() + && let Ok(depth) = obj.extract::() + { + let non_zero = NonZeroUsize::new(depth).ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err( + "qos depth shorthand must be greater than 0", + ) + })?; + let mut qos = QOS_DEFAULT; + qos.history = QosHistory::KeepLast(non_zero); + return Ok(qos); + } // Fall back to dict if let Ok(dict) = obj.downcast::() { return qos_from_pydict(dict); } Err(pyo3::exceptions::PyTypeError::new_err( - "qos must be a QosProfile or dict", + "qos must be a QosProfile, an int (depth shorthand), or a dict", )) } } diff --git a/crates/hiroz-py/src/service.rs b/crates/hiroz-py/src/service.rs index aa8b7781..1e9534e8 100644 --- a/crates/hiroz-py/src/service.rs +++ b/crates/hiroz-py/src/service.rs @@ -1,7 +1,10 @@ use crate::traits::{RawClient, RawServer}; +use hiroz::graph::Graph; use hiroz::service::RequestId; use pyo3::prelude::*; use pyo3::types::PyDict; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; /// Python wrapper for service client @@ -10,16 +13,26 @@ pub struct PyZClient { inner: Box, request_type_name: String, response_type_name: String, + /// Shared graph + fully-qualified service name, used by `wait_for_service`. + graph: Arc, + service_name: String, } impl PyZClient { - pub fn new(inner: Box, service_type: String) -> Self { + pub fn new( + inner: Box, + service_type: String, + graph: Arc, + service_name: String, + ) -> Self { let request_type_name = format!("{}_Request", service_type); let response_type_name = format!("{}_Response", service_type); Self { inner, request_type_name, response_type_name, + graph, + service_name, } } } @@ -40,10 +53,25 @@ impl PyZClient { let cdr_bytes = py .allow_threads(|| self.inner.call_serialized(&cdr_bytes, timeout_duration)) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + .map_err(crate::error::map_call_error)?; hiroz_msgs::deserialize_from_cdr(&self.response_type_name, py, &cdr_bytes) } + /// Wait until a service server for this service is available. + /// + /// Mirrors rclpy's `Client.wait_for_service(timeout_sec)`. Polls the + /// discovery graph until a matching server appears. Returns True if a + /// server was found before `timeout`, False otherwise. + /// + /// Args: + /// timeout: Maximum seconds to wait. None waits forever. + #[pyo3(signature = (timeout=None))] + fn wait_for_service(&self, py: Python, timeout: Option) -> bool { + py.allow_threads(|| { + crate::graph::wait_for_service_server(&self.graph, &self.service_name, timeout) + }) + } + /// Get the service type name (for debugging) unsafe fn get_type_name(&self) -> String { format!( @@ -53,12 +81,37 @@ impl PyZClient { } } -/// Python wrapper for service server +/// Background-thread state for a callback-mode server (P6). +/// +/// Holds an `Arc` to the underlying server (keeping its Zenoh queryable alive) +/// and a stop flag the worker thread checks each poll. Dropping this signals the +/// thread to stop and joins it. +struct CallbackServerState { + stop: Arc, + handle: Option>, + _server: Arc, +} + +impl Drop for CallbackServerState { + fn drop(&mut self) { + self.stop.store(true, Ordering::Relaxed); + if let Some(h) = self.handle.take() { + let _ = h.join(); + } + } +} + +/// Python wrapper for service server. +/// +/// Pull mode (default): `inner` is `Some`; the caller drives `take_request` / +/// `send_response`. Callback mode (P6): `inner` is `None` and a background +/// thread (held in `_callback`) services requests via the user callback. #[pyclass(name = "ZServer")] pub struct PyZServer { - inner: std::sync::Mutex>, + inner: Option>>, request_type_name: String, response_type_name: String, + _callback: Option, } impl PyZServer { @@ -66,11 +119,110 @@ impl PyZServer { let request_type_name = format!("{}_Request", service_type); let response_type_name = format!("{}_Response", service_type); Self { - inner: std::sync::Mutex::new(inner), + inner: Some(std::sync::Mutex::new(inner)), + request_type_name, + response_type_name, + _callback: None, + } + } + + /// Build a callback-mode server: a background thread receives each request, + /// calls `callback(request)`, and sends the returned object as the response. + pub fn new_with_callback( + server: Arc, + service_type: String, + callback: PyObject, + ) -> Self { + let request_type_name = format!("{}_Request", service_type); + let response_type_name = format!("{}_Response", service_type); + + let stop = Arc::new(AtomicBool::new(false)); + let handle = spawn_callback_loop( + Arc::clone(&server), + request_type_name.clone(), + response_type_name.clone(), + callback, + Arc::clone(&stop), + ); + + Self { + inner: None, request_type_name, response_type_name, + _callback: Some(CallbackServerState { + stop, + handle: Some(handle), + _server: server, + }), } } + + fn require_pull(&self) -> PyResult<&std::sync::Mutex>> { + self.inner.as_ref().ok_or_else(|| { + pyo3::exceptions::PyRuntimeError::new_err( + "This server runs in callback mode; take_request/send_response are unavailable. \ + Create it without a callback to use pull mode.", + ) + }) + } +} + +/// Spawn the worker thread for a callback-mode server. +fn spawn_callback_loop( + server: Arc, + request_type_name: String, + response_type_name: String, + callback: PyObject, + stop: Arc, +) -> std::thread::JoinHandle<()> { + std::thread::spawn(move || { + while !stop.load(Ordering::Relaxed) { + // Poll for a request without holding the GIL. + match server.try_take_request_serialized() { + Ok(Some((request_id, request_bytes))) => { + Python::with_gil(|py| { + let req_obj = match hiroz_msgs::deserialize_from_cdr( + &request_type_name, + py, + &request_bytes, + ) { + Ok(o) => o, + Err(e) => { + eprintln!("hiroz_py: request deserialize error: {}", e); + return; + } + }; + let resp_obj = match callback.call1(py, (req_obj,)) { + Ok(o) => o, + Err(e) => { + eprintln!("hiroz_py: service callback error: {}", e); + return; + } + }; + let resp_bytes = match hiroz_msgs::serialize_to_cdr( + &response_type_name, + py, + resp_obj.bind(py), + ) { + Ok(b) => b, + Err(e) => { + eprintln!("hiroz_py: response serialize error: {}", e); + return; + } + }; + if let Err(e) = server.send_response_serialized(&resp_bytes, &request_id) { + eprintln!("hiroz_py: send_response error: {}", e); + } + }); + } + Ok(None) => std::thread::sleep(Duration::from_millis(2)), + Err(e) => { + eprintln!("hiroz_py: service poll error: {}", e); + std::thread::sleep(Duration::from_millis(50)); + } + } + } + }) } #[allow(unsafe_op_in_unsafe_fn)] @@ -78,9 +230,9 @@ impl PyZServer { impl PyZServer { /// Receive the next service request (blocking) unsafe fn take_request(&self, py: Python) -> PyResult<(PyObject, PyObject)> { + let mutex = self.require_pull()?; let result = py.allow_threads(|| { - let inner = self - .inner + let inner = mutex .lock() .map_err(|e| anyhow::anyhow!("Lock error: {}", e))?; inner.take_request_serialized() @@ -120,9 +272,9 @@ impl PyZServer { source_timestamp: 0, }; + let mutex = self.require_pull()?; py.allow_threads(|| { - let inner = self - .inner + let inner = mutex .lock() .map_err(|e| anyhow::anyhow!("Lock error: {}", e))?; inner.send_response_serialized(&cdr_bytes, &key) diff --git a/crates/hiroz-py/src/traits.rs b/crates/hiroz-py/src/traits.rs index a624a809..5d6fc39a 100644 --- a/crates/hiroz-py/src/traits.rs +++ b/crates/hiroz-py/src/traits.rs @@ -10,6 +10,9 @@ use crate::raw_bytes::{RawBytesCdrSerdes, RawBytesMessage, RawBytesService}; pub(crate) trait RawPublisher: Send + Sync { /// Publish pre-serialized data fn publish(&self, data: ZBytes) -> Result<()>; + /// Block until at least `count` subscriptions are matched, or `timeout` elapses. + /// Returns true if the count was reached. Delegates to the core liveliness-based wait. + fn wait_for_subscription(&self, count: usize, timeout: Duration) -> bool; } /// Type-erased subscriber trait for Python interop @@ -43,6 +46,12 @@ impl RawPublisher for GenericPubWrapper { .publish_serialized(data) .map_err(|e| anyhow::anyhow!(e)) } + + fn wait_for_subscription(&self, count: usize, timeout: Duration) -> bool { + // The core method is async; block on it using the shared runtime. + // Callers release the GIL around this via `py.allow_threads`. + crate::action::get_tokio_rt().block_on(self.inner.wait_for_subscription(count, timeout)) + } } /// Generic subscriber wrapper using RawBytesMessage @@ -103,6 +112,9 @@ pub(crate) trait RawClient: Send + Sync { /// Type-erased server trait for Python interop pub(crate) trait RawServer: Send + Sync { fn take_request_serialized(&self) -> Result<(RequestId, Vec)>; + /// Non-blocking variant: returns None if no request is queued. + /// Used by the optional callback-mode server loop. + fn try_take_request_serialized(&self) -> Result)>>; fn send_response_serialized(&self, data: &[u8], request_id: &RequestId) -> Result<()>; } @@ -174,6 +186,29 @@ impl RawServer for GenericServerWrapper { Ok((request_id, request.0)) } + fn try_take_request_serialized(&self) -> Result)>> { + let mut server = self + .inner + .lock() + .map_err(|e| anyhow::anyhow!("Failed to lock server: {}", e))?; + + match server + .try_take_request() + .map_err(|e| anyhow::anyhow!("Failed to poll request: {}", e))? + { + Some(request) => { + let (request, reply) = request.into_parts(); + let request_id = reply.id().clone(); + self.pending + .lock() + .map_err(|e| anyhow::anyhow!("Failed to lock pending replies: {}", e))? + .insert(request_id.clone(), reply); + Ok(Some((request_id, request.0))) + } + None => Ok(None), + } + } + fn send_response_serialized(&self, data: &[u8], request_id: &RequestId) -> Result<()> { let response = RawBytesMessage(data.to_vec()); diff --git a/crates/hiroz-py/tests/test_rclpy_alignment.py b/crates/hiroz-py/tests/test_rclpy_alignment.py new file mode 100644 index 00000000..ac790e26 --- /dev/null +++ b/crates/hiroz-py/tests/test_rclpy_alignment.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +"""Tests for the rclpy-alignment features (P1-P8).""" + +import time + +import pytest + +import hiroz_py +from hiroz_py import example_interfaces, std_msgs + + +@pytest.fixture(scope="module") +def ctx(): + c = hiroz_py.ZContextBuilder().with_domain_id(0).build() + yield c + + +# --- P8: QoS enum constants + int depth shorthand --- + + +def test_p8_policy_constants(): + assert hiroz_py.ReliabilityPolicy.RELIABLE == "reliable" + assert hiroz_py.ReliabilityPolicy.BEST_EFFORT == "best_effort" + assert hiroz_py.DurabilityPolicy.VOLATILE == "volatile" + assert hiroz_py.DurabilityPolicy.TRANSIENT_LOCAL == "transient_local" + assert hiroz_py.HistoryPolicy.KEEP_LAST == "keep_last" + assert hiroz_py.HistoryPolicy.KEEP_ALL == "keep_all" + assert hiroz_py.LivelinessPolicy.AUTOMATIC == "automatic" + + +def test_p8_int_depth_shorthand(ctx): + node = ctx.create_node("p8_int").build() + # qos=10 should be accepted as a depth shorthand. + pub = node.create_publisher("/p8_topic", std_msgs.String, qos=10) + assert pub is not None + + +def test_p8_policy_constants_in_qos(ctx): + node = ctx.create_node("p8_policy").build() + qos = hiroz_py.QosProfile( + reliability=hiroz_py.ReliabilityPolicy.BEST_EFFORT, + history=hiroz_py.HistoryPolicy.KEEP_LAST, + depth=5, + ) + assert qos.reliability == "best_effort" + pub = node.create_publisher("/p8_policy_topic", std_msgs.String, qos=qos) + assert pub is not None + + +# --- P2: swapped-argument smart error --- + + +def test_p2_swapped_args_publisher(ctx): + node = ctx.create_node("p2_pub").build() + with pytest.raises(TypeError, match="swapped"): + # rclpy order: (msg_type, topic) -> should be rejected with a clear error. + node.create_publisher(std_msgs.String, "/chatter") + + +def test_p2_swapped_args_subscriber(ctx): + node = ctx.create_node("p2_sub").build() + with pytest.raises(TypeError, match="swapped"): + node.create_subscriber(std_msgs.String, "/chatter") + + +def test_p2_keyword_args_work(ctx): + node = ctx.create_node("p2_kw").build() + # Keyword args work regardless of historical order. + pub = node.create_publisher(msg_type=std_msgs.String, topic="/p2_kw_topic") + assert pub is not None + + +# --- P3: method aliases --- + + +def test_p3_create_subscription_alias(): + assert hiroz_py.ZNode.create_subscription is hiroz_py.ZNode.create_subscriber + + +def test_p3_create_service_alias(): + assert hiroz_py.ZNode.create_service is hiroz_py.ZNode.create_server + + +# --- P4: service grouping class --- + + +def test_p4_grouping_class_attributes(): + assert ( + example_interfaces.AddTwoInts.__srvtype__ == "example_interfaces/srv/AddTwoInts" + ) + assert example_interfaces.AddTwoInts.Request is example_interfaces.AddTwoIntsRequest + assert ( + example_interfaces.AddTwoInts.Response is example_interfaces.AddTwoIntsResponse + ) + + +def test_p4_client_accepts_grouping_class(ctx): + node = ctx.create_node("p4_client").build() + client = node.create_client("/p4_add", example_interfaces.AddTwoInts) + assert client is not None + + +def test_p4_client_accepts_bare_request(ctx): + # Back-compat: bare Request class still works. + node = ctx.create_node("p4_client_bc").build() + client = node.create_client("/p4_add_bc", example_interfaces.AddTwoIntsRequest) + assert client is not None + + +# --- P5: custom exception types --- + + +def test_p5_exception_hierarchy(): + assert issubclass(hiroz_py.TimeoutError, hiroz_py.HirozError) + assert issubclass(hiroz_py.SerializationError, hiroz_py.HirozError) + assert issubclass(hiroz_py.TypeMismatchError, hiroz_py.HirozError) + + +def test_p5_call_failure_is_hiroz_error(ctx): + node = ctx.create_node("p5_client").build() + client = node.create_client("/p5_nonexistent", example_interfaces.AddTwoInts) + req = example_interfaces.AddTwoInts.Request(a=1, b=2) + with pytest.raises(hiroz_py.HirozError): + client.call(req, timeout=1.0) + + +# --- P1 + P4 + P6: end-to-end service with wait_for_service and callback mode --- + + +def test_p1_p6_callback_service_end_to_end(ctx): + node = ctx.create_node("p6_node").build() + + def handle(req): + return example_interfaces.AddTwoInts.Response(sum=req.a + req.b) + + # P6: callback-mode server (no take_request loop). + server = node.create_server( + "/p6_add", example_interfaces.AddTwoInts, callback=handle + ) + assert server is not None + + client = node.create_client("/p6_add", example_interfaces.AddTwoInts) + # P1: wait for the server instead of sleeping. + assert client.wait_for_service(timeout=5.0), "server should be discoverable" + + resp = client.call(example_interfaces.AddTwoInts.Request(a=4, b=38), timeout=5.0) + assert resp.sum == 42 + + +def test_p1_wait_for_service_timeout_returns_false(ctx): + node = ctx.create_node("p1_wait_to").build() + client = node.create_client("/p1_never", example_interfaces.AddTwoInts) + t0 = time.time() + assert client.wait_for_service(timeout=0.5) is False + assert time.time() - t0 >= 0.4 + + +# --- P1: wait_for_subscription end-to-end --- + + +def test_p1_wait_for_subscription(ctx): + node = ctx.create_node("p1_pubsub").build() + pub = node.create_publisher("/p1_chatter", std_msgs.String) + received = [] + + def cb(msg): + received.append(msg.data) + + node.create_subscriber("/p1_chatter", std_msgs.String, callback=cb) + + assert pub.wait_for_subscription(count=1, timeout=5.0), "subscription should match" + + pub.publish(std_msgs.String(data="hello")) + deadline = time.time() + 3.0 + while not received and time.time() < deadline: + time.sleep(0.05) + assert received == ["hello"] + + +# --- P7: action grouping class detection (inline, Python-to-Python) --- + + +def test_p7_action_grouping_class(ctx): + import msgspec + from typing import ClassVar + + class CountToGoal(msgspec.Struct): + __msgtype__: ClassVar[str] = "p7_demo/msg/CountToGoal" + target: int = 0 + + class CountToResult(msgspec.Struct): + __msgtype__: ClassVar[str] = "p7_demo/msg/CountToResult" + final_count: int = 0 + + class CountToFeedback(msgspec.Struct): + __msgtype__: ClassVar[str] = "p7_demo/msg/CountToFeedback" + current: int = 0 + + class CountTo: + __actiontype__: ClassVar[str] = "p7_demo/action/CountTo" + Goal = CountToGoal + Result = CountToResult + Feedback = CountToFeedback + + node = ctx.create_node("p7_action").build() + # Single grouping class instead of three positional types. + client = node.create_action_client("/p7_count", CountTo) + assert client is not None + server = node.create_action_server("/p7_count", CountTo) + assert server is not None diff --git a/crates/hiroz-py/tests/test_service.py b/crates/hiroz-py/tests/test_service.py index a9438c1a..4990bdfc 100644 --- a/crates/hiroz-py/tests/test_service.py +++ b/crates/hiroz-py/tests/test_service.py @@ -93,9 +93,9 @@ def test_timeout_handling(client): req = example_interfaces.AddTwoIntsRequest(a=1, b=2) try: timeout_client.call(req, timeout=1.0) - assert False, "Expected timeout error, but call succeeded" - except RuntimeError: - pass # expected: call timed out + assert False, "Expected an error, but call succeeded" + except hiroz_py.HirozError: + pass # expected: call failed (no server / timeout). TimeoutError is a subclass. print("✓ Timeout handling works")