use crate::msg::ZMessage;
use hiroz_cdr::{CdrBuffer, CdrDeserialize, CdrReader, CdrSerialize, CdrSerializedSize, CdrWriter};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::SystemTime;
pub mod client;
pub mod driver;
pub mod macros;
pub mod messages;
pub mod server;
pub mod state;
pub use server::{Accepted, Executing, Requested};
pub type ClientGoalHandle<A, S = client::goal_state::Active> = client::GoalHandle<A, S>;
pub trait ZAction: Send + Sync + 'static {
type Goal: ZMessage + Clone + Send + Sync + serde::Serialize + for<'de> serde::Deserialize<'de>;
type Result: ZMessage
+ Clone
+ Send
+ Sync
+ serde::Serialize
+ for<'de> serde::Deserialize<'de>;
type Feedback: ZMessage + Clone + serde::Serialize + for<'de> serde::Deserialize<'de>;
fn name() -> &'static str;
fn send_goal_type_info() -> crate::entity::TypeInfo {
crate::entity::TypeInfo::new(
&format!("{}/_action/SendGoal", Self::name()),
crate::entity::TypeHash::zero(),
)
}
fn get_result_type_info() -> crate::entity::TypeInfo {
crate::entity::TypeInfo::new(
&format!("{}/_action/GetResult", Self::name()),
crate::entity::TypeHash::zero(),
)
}
fn cancel_goal_type_info() -> crate::entity::TypeInfo {
crate::entity::TypeInfo::new(
"action_msgs/srv/CancelGoal",
crate::entity::TypeHash::zero(),
)
}
fn feedback_type_info() -> crate::entity::TypeInfo {
crate::entity::TypeInfo::new(
&format!("{}/_FeedbackMessage", Self::name()),
crate::entity::TypeHash::zero(),
)
}
fn status_type_info() -> crate::entity::TypeInfo {
crate::entity::TypeInfo::new(
"action_msgs/msg/GoalStatusArray",
crate::entity::TypeHash::zero(),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct GoalId([u8; 16]);
impl GoalId {
pub fn new() -> Self {
let mut uuid = [0u8; 16];
uuid.copy_from_slice(&uuid::Uuid::new_v4().as_bytes()[..]);
Self(uuid)
}
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self(bytes)
}
pub fn is_valid(&self) -> bool {
self.0.iter().any(|&x| x != 0)
}
pub fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}
impl Default for GoalId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for GoalId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let uuid = uuid::Uuid::from_bytes(self.0);
write!(f, "{}", uuid.hyphenated())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(i8)]
#[serde(try_from = "i8", into = "i8")]
pub enum GoalStatus {
Unknown = 0,
Accepted = 1,
Executing = 2,
Canceling = 3,
Succeeded = 4,
Canceled = 5,
Aborted = 6,
}
impl GoalStatus {
pub fn is_active(&self) -> bool {
matches!(self, Self::Accepted | Self::Executing | Self::Canceling)
}
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Succeeded | Self::Canceled | Self::Aborted)
}
}
impl TryFrom<i8> for GoalStatus {
type Error = String;
fn try_from(value: i8) -> Result<Self, Self::Error> {
match value {
0 => Ok(GoalStatus::Unknown),
1 => Ok(GoalStatus::Accepted),
2 => Ok(GoalStatus::Executing),
3 => Ok(GoalStatus::Canceling),
4 => Ok(GoalStatus::Succeeded),
5 => Ok(GoalStatus::Canceled),
6 => Ok(GoalStatus::Aborted),
_ => Err(format!("Invalid GoalStatus value: {}", value)),
}
}
}
impl From<GoalStatus> for i8 {
fn from(status: GoalStatus) -> i8 {
status as i8
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoalInfo {
pub goal_id: GoalId,
pub stamp: Time,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct Time {
pub sec: i32,
pub nanosec: u32,
}
impl Time {
pub fn now() -> Self {
let duration = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
Self {
sec: duration.as_secs() as i32,
nanosec: duration.subsec_nanos(),
}
}
pub fn zero() -> Self {
Self { sec: 0, nanosec: 0 }
}
}
impl GoalInfo {
pub fn new(goal_id: GoalId) -> Self {
Self {
goal_id,
stamp: Time::now(),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GoalEvent {
Execute,
CancelGoal,
Succeed,
Abort,
Canceled,
}
pub fn transition_goal_state(current: GoalStatus, event: GoalEvent) -> GoalStatus {
match (current, event) {
(GoalStatus::Accepted, GoalEvent::Execute) => GoalStatus::Executing,
(GoalStatus::Accepted, GoalEvent::CancelGoal) => GoalStatus::Canceling,
(GoalStatus::Executing, GoalEvent::CancelGoal) => GoalStatus::Canceling,
(GoalStatus::Executing, GoalEvent::Succeed) => GoalStatus::Succeeded,
(GoalStatus::Executing, GoalEvent::Abort) => GoalStatus::Aborted,
(GoalStatus::Canceling, GoalEvent::Canceled) => GoalStatus::Canceled,
(GoalStatus::Canceling, GoalEvent::Succeed) => GoalStatus::Succeeded,
(GoalStatus::Canceling, GoalEvent::Abort) => GoalStatus::Aborted,
_ => GoalStatus::Unknown,
}
}
impl CdrSerialize for GoalId {
#[inline]
fn cdr_serialize<BO: byteorder::ByteOrder, B: CdrBuffer>(&self, w: &mut CdrWriter<'_, BO, B>) {
self.0.cdr_serialize(w);
}
}
impl CdrDeserialize for GoalId {
#[inline]
fn cdr_deserialize<'de, BO: byteorder::ByteOrder>(
r: &mut CdrReader<'de, BO>,
) -> hiroz_cdr::Result<Self> {
Ok(GoalId(<[u8; 16]>::cdr_deserialize(r)?))
}
}
impl CdrSerializedSize for GoalId {
#[inline]
fn cdr_serialized_size(&self, pos: usize) -> usize {
self.0.cdr_serialized_size(pos)
}
}
impl CdrSerialize for GoalStatus {
#[inline]
fn cdr_serialize<BO: byteorder::ByteOrder, B: CdrBuffer>(&self, w: &mut CdrWriter<'_, BO, B>) {
(*self as i8).cdr_serialize(w);
}
}
impl CdrDeserialize for GoalStatus {
#[inline]
fn cdr_deserialize<'de, BO: byteorder::ByteOrder>(
r: &mut CdrReader<'de, BO>,
) -> hiroz_cdr::Result<Self> {
let v = i8::cdr_deserialize(r)?;
GoalStatus::try_from(v).map_err(hiroz_cdr::error::Error::Custom)
}
}
impl CdrSerializedSize for GoalStatus {
#[inline]
fn cdr_serialized_size(&self, pos: usize) -> usize {
pos + 1
}
}
impl CdrSerialize for Time {
#[inline]
fn cdr_serialize<BO: byteorder::ByteOrder, B: CdrBuffer>(&self, w: &mut CdrWriter<'_, BO, B>) {
self.sec.cdr_serialize(w);
self.nanosec.cdr_serialize(w);
}
}
impl CdrDeserialize for Time {
#[inline]
fn cdr_deserialize<'de, BO: byteorder::ByteOrder>(
r: &mut CdrReader<'de, BO>,
) -> hiroz_cdr::Result<Self> {
Ok(Time {
sec: i32::cdr_deserialize(r)?,
nanosec: u32::cdr_deserialize(r)?,
})
}
}
impl CdrSerializedSize for Time {
#[inline]
fn cdr_serialized_size(&self, pos: usize) -> usize {
let p = self.sec.cdr_serialized_size(pos);
self.nanosec.cdr_serialized_size(p)
}
}
impl CdrSerialize for GoalInfo {
#[inline]
fn cdr_serialize<BO: byteorder::ByteOrder, B: CdrBuffer>(&self, w: &mut CdrWriter<'_, BO, B>) {
self.goal_id.cdr_serialize(w);
self.stamp.cdr_serialize(w);
}
}
impl CdrDeserialize for GoalInfo {
#[inline]
fn cdr_deserialize<'de, BO: byteorder::ByteOrder>(
r: &mut CdrReader<'de, BO>,
) -> hiroz_cdr::Result<Self> {
Ok(GoalInfo {
goal_id: GoalId::cdr_deserialize(r)?,
stamp: Time::cdr_deserialize(r)?,
})
}
}
impl CdrSerializedSize for GoalInfo {
#[inline]
fn cdr_serialized_size(&self, pos: usize) -> usize {
let p = self.goal_id.cdr_serialized_size(pos);
self.stamp.cdr_serialized_size(p)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_goal_id_display_is_hyphenated_uuid() {
let bytes = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f,
];
let id = GoalId(bytes);
let s = format!("{}", id);
assert_eq!(s, "00010203-0405-0607-0809-0a0b0c0d0e0f");
}
#[test]
fn test_goal_status_variants_are_distinct() {
assert_ne!(GoalStatus::Unknown, GoalStatus::Accepted);
assert_ne!(GoalStatus::Executing, GoalStatus::Succeeded);
assert_ne!(GoalStatus::Canceled, GoalStatus::Aborted);
}
}