init
This commit is contained in:
commit
79fd6c5af4
21 changed files with 4107 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
**/target
|
||||||
|
Cargo.lock
|
||||||
3
Cargo.toml
Normal file
3
Cargo.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = ["canopen"]
|
||||||
0
README.md
Normal file
0
README.md
Normal file
17
canopen/Cargo.toml
Normal file
17
canopen/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "canopen"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embedded-can = "0.4"
|
||||||
|
ux = "0.1"
|
||||||
|
heapless = "0.8"
|
||||||
|
derive_more = { version = "2", default-features = false, features = ["from"]}
|
||||||
|
num_enum = { version = "0.7", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
async-channel = "2.5"
|
||||||
|
async-io = "2.5"
|
||||||
|
smol = "2.0"
|
||||||
|
futures = { version = "0.3" }
|
||||||
88
canopen/src/can.rs
Normal file
88
canopen/src/can.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
use core::pin::Pin;
|
||||||
|
use core::task::{Context, Poll};
|
||||||
|
use embedded_can::{Frame, Id};
|
||||||
|
use heapless::Vec;
|
||||||
|
|
||||||
|
pub trait CanTx {
|
||||||
|
type Error;
|
||||||
|
type Frame: Frame;
|
||||||
|
|
||||||
|
fn send(&mut self, frame: Self::Frame) -> impl Future<Output = Result<(), Self::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CanRx {
|
||||||
|
type Error;
|
||||||
|
type Frame: Frame;
|
||||||
|
|
||||||
|
fn poll_recv(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<Self::Frame, Self::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CanBody {
|
||||||
|
RemoteTransmissionRequest(CanBodyRtr),
|
||||||
|
Data(CanBodyData),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanBody {
|
||||||
|
pub fn new_remote(dlc: usize) -> Option<Self> {
|
||||||
|
if dlc <= 8 {
|
||||||
|
Some(Self::RemoteTransmissionRequest(CanBodyRtr {
|
||||||
|
size: dlc as _,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_frame<F: Frame>(self, id: Id) -> F {
|
||||||
|
match self {
|
||||||
|
Self::Data(x) => F::new(id, x.data.as_slice()).unwrap(),
|
||||||
|
Self::RemoteTransmissionRequest(x) => F::new_remote(id, x.size as _).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_frame<F: Frame>(frame: &F) -> Self {
|
||||||
|
if frame.is_data_frame() {
|
||||||
|
Self::Data(CanBodyData {
|
||||||
|
data: Vec::from_slice(frame.data()).unwrap(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Self::RemoteTransmissionRequest(CanBodyRtr {
|
||||||
|
size: frame.dlc() as _,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_slice(array: &[u8]) -> Option<Self> {
|
||||||
|
Some(Self::Data(CanBodyData {
|
||||||
|
data: Vec::from_slice(&array).ok()?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
pub fn data_from_array<const N: usize>(array: [u8; N]) -> Self {
|
||||||
|
assert!(N <= 8);
|
||||||
|
Self::Data(CanBodyData {
|
||||||
|
data: Vec::from_slice(&array).unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn data(&self) -> Option<&[u8]> {
|
||||||
|
match self {
|
||||||
|
Self::Data(data) => Some(data.data.as_slice()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn is_data_frame(&self) -> bool {
|
||||||
|
matches!(self, Self::Data(_))
|
||||||
|
}
|
||||||
|
pub const fn is_remote_frame(&self) -> bool {
|
||||||
|
matches!(self, Self::RemoteTransmissionRequest(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CanBodyRtr {
|
||||||
|
pub size: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CanBodyData {
|
||||||
|
pub data: Vec<u8, 8>,
|
||||||
|
}
|
||||||
22
canopen/src/communication.rs
Normal file
22
canopen/src/communication.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use crate::can::CanBody;
|
||||||
|
|
||||||
|
pub mod emcy;
|
||||||
|
pub mod nmt;
|
||||||
|
pub mod pdo;
|
||||||
|
pub mod sdo;
|
||||||
|
|
||||||
|
pub trait DecodeCommunicationObject {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait EncodeCommunicationObject {
|
||||||
|
fn encode(self) -> CanBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DecodeError {
|
||||||
|
InvalidFrameType,
|
||||||
|
InvalidFormat,
|
||||||
|
InvalidValue,
|
||||||
|
}
|
||||||
59
canopen/src/communication/emcy.rs
Normal file
59
canopen/src/communication/emcy.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use ux::u40;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
can::CanBody,
|
||||||
|
communication::{DecodeCommunicationObject, DecodeError, EncodeCommunicationObject},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum EmcyCommunicationObject {
|
||||||
|
Write(EmcyWrite),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.7.3.1 Protocol EMCY write
|
||||||
|
pub struct EmcyWrite {
|
||||||
|
pub emergency_error_code: u16,
|
||||||
|
pub error_register: u8,
|
||||||
|
pub manufacturer_error_code: u40,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for EmcyWrite {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let emergency_error_code = self.emergency_error_code.to_be_bytes();
|
||||||
|
let manufacturer_error_code = &u64::from(self.manufacturer_error_code).to_be_bytes()[3..];
|
||||||
|
CanBody::data_from_array([
|
||||||
|
emergency_error_code[0],
|
||||||
|
emergency_error_code[1],
|
||||||
|
self.error_register,
|
||||||
|
manufacturer_error_code[0],
|
||||||
|
manufacturer_error_code[1],
|
||||||
|
manufacturer_error_code[2],
|
||||||
|
manufacturer_error_code[3],
|
||||||
|
manufacturer_error_code[4],
|
||||||
|
manufacturer_error_code[5],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for EmcyWrite {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let payload = body.data().ok_or(DecodeError::InvalidFrameType)?;
|
||||||
|
if payload.len() != 8 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
let emergency_error_code = u16::from_be_bytes([payload[0], payload[1]]);
|
||||||
|
let error_register = payload[2];
|
||||||
|
let Ok(manufacturer_error_code) = u40::try_from(u64::from_be_bytes([
|
||||||
|
0, 0, 0, payload[3], payload[4], payload[5], payload[6], payload[7],
|
||||||
|
])) else {
|
||||||
|
unreachable!("u64 with 24 leading zeros to fit into u40");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
emergency_error_code,
|
||||||
|
error_register,
|
||||||
|
manufacturer_error_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
231
canopen/src/communication/nmt.rs
Normal file
231
canopen/src/communication/nmt.rs
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
use crate::{
|
||||||
|
NodeId,
|
||||||
|
can::{CanBody, CanBodyRtr},
|
||||||
|
communication::{DecodeCommunicationObject, DecodeError, EncodeCommunicationObject},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum NmtCommunicationObjectMS {
|
||||||
|
StartRemoteNode(NmtStartRemoteNode),
|
||||||
|
StopRemoteNode(NmtStopRemoteNode),
|
||||||
|
EnterPreoperational(NmtEnterPreoperational),
|
||||||
|
ResetNode(NmtResetNode),
|
||||||
|
ResetCommunication(NmtResetCommunication),
|
||||||
|
NodeGuardingReq(NmtNodeGuardingReq),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NmtCommunicationObjectSM {
|
||||||
|
NodeGuardingRes(NmtNodeGuardingRes),
|
||||||
|
Heartbeat(NmtHeartbeat),
|
||||||
|
Bootup(NmtBootup),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NmtCommunicationObjectSM {
|
||||||
|
pub fn serialize(self) -> CanBody {
|
||||||
|
/*
|
||||||
|
match self {
|
||||||
|
Self::StartRemoteNode(x) => x.serialize(),
|
||||||
|
Self::StopRemoteNode(x) => x.serialize(),
|
||||||
|
Self::EnterPreoperational(x) => x.serialize(),
|
||||||
|
Self::ResetNode(x) => x.serialize(),
|
||||||
|
Self::ResetCommunication(x) => x.serialize(),
|
||||||
|
Self::NodeGuardingReq(x) => x.serialize(),
|
||||||
|
Self::NodeGuardingRes(x) => x.serialize(),
|
||||||
|
Self::Heartbeat(x) => x.serialize(),
|
||||||
|
//Self::Bootup(x) => x.serialize(),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeCommunicationObject for NmtCommunicationObjectMS {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let payload = body.data().ok_or(DecodeError::InvalidFrameType)?;
|
||||||
|
if payload.len() != 2 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
let node = NodeId::new(payload[1]).ok_or(DecodeError::InvalidValue)?;
|
||||||
|
match payload[0] {
|
||||||
|
1 => Ok(Self::StartRemoteNode(NmtStartRemoteNode { node })),
|
||||||
|
2 => Ok(Self::StopRemoteNode(NmtStopRemoteNode { node })),
|
||||||
|
128 => Ok(Self::EnterPreoperational(NmtEnterPreoperational { node })),
|
||||||
|
129 => Ok(Self::ResetNode(NmtResetNode { node })),
|
||||||
|
130 => Ok(Self::ResetCommunication(NmtResetCommunication { node })),
|
||||||
|
_ => Err(DecodeError::InvalidFormat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.1.1 Protocol start remote node
|
||||||
|
pub struct NmtStartRemoteNode {
|
||||||
|
pub node: NodeId,
|
||||||
|
}
|
||||||
|
impl EncodeCommunicationObject for NmtStartRemoteNode {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
CanBody::data_from_array([1, self.node.0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.1.2 Protocol stop remote node
|
||||||
|
pub struct NmtStopRemoteNode {
|
||||||
|
pub node: NodeId,
|
||||||
|
}
|
||||||
|
impl EncodeCommunicationObject for NmtStopRemoteNode {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
CanBody::data_from_array([2, self.node.0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.1.3 Protocol enter pre-operational
|
||||||
|
pub struct NmtEnterPreoperational {
|
||||||
|
pub node: NodeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for NmtEnterPreoperational {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
CanBody::data_from_array([128, self.node.0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.1.4 Protocol reset node
|
||||||
|
pub struct NmtResetNode {
|
||||||
|
pub node: NodeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for NmtResetNode {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
CanBody::data_from_array([129, self.node.0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.1.5 Protocol reset communication
|
||||||
|
pub struct NmtResetCommunication {
|
||||||
|
pub node: NodeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for NmtResetCommunication {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
CanBody::data_from_array([130, self.node.0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.2.1 Protocol node guarding
|
||||||
|
pub struct NmtNodeGuardingReq;
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for NmtNodeGuardingReq {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
//TODO is size necessary?
|
||||||
|
CanBody::RemoteTransmissionRequest(CanBodyRtr { size: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.2.1 Protocol node guarding
|
||||||
|
pub struct NmtNodeGuardingRes {
|
||||||
|
pub toggle_bit: bool,
|
||||||
|
pub state: NmtNodeGuardingResState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum NmtNodeGuardingResState {
|
||||||
|
Stopped,
|
||||||
|
Operational,
|
||||||
|
PreOperational,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for NmtNodeGuardingRes {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let state = match self.state {
|
||||||
|
NmtNodeGuardingResState::Stopped => 4,
|
||||||
|
NmtNodeGuardingResState::Operational => 5,
|
||||||
|
NmtNodeGuardingResState::PreOperational => 127,
|
||||||
|
};
|
||||||
|
CanBody::data_from_array([state])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for NmtNodeGuardingRes {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let payload = body.data().ok_or(DecodeError::InvalidFormat)?;
|
||||||
|
if payload.len() != 1 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
let toggle_bit = (payload[0] >> 7) & 1 == 1;
|
||||||
|
let state = match payload[0] & 0b01111111 {
|
||||||
|
4 => NmtNodeGuardingResState::Stopped,
|
||||||
|
5 => NmtNodeGuardingResState::Operational,
|
||||||
|
127 => NmtNodeGuardingResState::PreOperational,
|
||||||
|
_ => return Err(DecodeError::InvalidFormat),
|
||||||
|
};
|
||||||
|
Ok(Self { toggle_bit, state })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.2.2 Protocol heartbeat
|
||||||
|
pub struct NmtHeartbeat {
|
||||||
|
pub state: NmtHeartbeatState,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NmtHeartbeatState {
|
||||||
|
BootUp,
|
||||||
|
Stopped,
|
||||||
|
Operational,
|
||||||
|
PreOperational,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for NmtHeartbeat {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let state = match self.state {
|
||||||
|
NmtHeartbeatState::BootUp => 0,
|
||||||
|
NmtHeartbeatState::Stopped => 4,
|
||||||
|
NmtHeartbeatState::Operational => 5,
|
||||||
|
NmtHeartbeatState::PreOperational => 127,
|
||||||
|
};
|
||||||
|
CanBody::data_from_array([state])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for NmtHeartbeat {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let payload = body.data().ok_or(DecodeError::InvalidFrameType)?;
|
||||||
|
if payload.len() != 1 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
let state = match payload[0] {
|
||||||
|
0 => NmtHeartbeatState::BootUp,
|
||||||
|
4 => NmtHeartbeatState::Stopped,
|
||||||
|
5 => NmtHeartbeatState::Operational,
|
||||||
|
127 => NmtHeartbeatState::PreOperational,
|
||||||
|
_ => return Err(DecodeError::InvalidFormat),
|
||||||
|
};
|
||||||
|
Ok(Self { state })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.8.3.3 Protocol boot-up
|
||||||
|
pub struct NmtBootup;
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for NmtBootup {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
CanBody::data_from_array([0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for NmtBootup {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let payload = body.data().ok_or(DecodeError::InvalidFrameType)?;
|
||||||
|
if payload.len() != 1 || payload[0] != 0 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
239
canopen/src/communication/pdo.rs
Normal file
239
canopen/src/communication/pdo.rs
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
use heapless::Vec;
|
||||||
|
use ux::u9;
|
||||||
|
|
||||||
|
pub mod consumer;
|
||||||
|
pub mod producer;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
can::{CanBody, CanBodyData, CanBodyRtr},
|
||||||
|
communication::{DecodeCommunicationObject, DecodeError, EncodeCommunicationObject},
|
||||||
|
};
|
||||||
|
pub enum PdoCommunicationObject {
|
||||||
|
Write(PdoWrite),
|
||||||
|
ReadReq(PdoReadReq),
|
||||||
|
ReadRes(PdoReadRes),
|
||||||
|
// MuxedWriteSource(MpdoWriteSource),
|
||||||
|
// MuxedWriteDestination(MpdoWriteDestination),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PdoId(pub u9);
|
||||||
|
|
||||||
|
/// CiA 301 7.2.2.5.1 Protocol PDO write
|
||||||
|
pub struct PdoWrite {
|
||||||
|
/// application data
|
||||||
|
/// `|data| > 0`
|
||||||
|
// TODO maybe introduce BoundedVec<u8, 1, 8> that has min and max const parameterss
|
||||||
|
pub data: Vec<u8, 8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for PdoWrite {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
CanBody::Data(CanBodyData { data: self.data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for PdoWrite {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let CanBody::Data(data) = body else {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
};
|
||||||
|
Ok(Self { data: data.data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.2.5.2 Protocol PDO read
|
||||||
|
pub struct PdoReadReq;
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for PdoReadReq {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
//TODO is the size needed?
|
||||||
|
CanBody::RemoteTransmissionRequest(CanBodyRtr { size: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for PdoReadReq {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let CanBody::RemoteTransmissionRequest(_) = body else {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
};
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.2.5.2 Protocol PDO read
|
||||||
|
pub struct PdoReadRes {
|
||||||
|
/// application data
|
||||||
|
/// `|data| > 0`
|
||||||
|
// TODO maybe introduce BoundedVec<u8, 1, 8> that has min and max const parameterss
|
||||||
|
pub data: Vec<u8, 8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for PdoReadRes {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
CanBody::Data(CanBodyData { data: self.data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for PdoReadRes {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let CanBody::Data(data) = body else {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
};
|
||||||
|
Ok(Self { data: data.data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
/// CiA 301 7.2.3.3.2 Protocol MPDO read
|
||||||
|
pub struct MpdoWriteSource {
|
||||||
|
/// producer of the MPDO transmission
|
||||||
|
pub source: NodeId,
|
||||||
|
/// index/sub-index that is used to identify the data on the transmitting CANopen device
|
||||||
|
pub multiplexer: u24,
|
||||||
|
/// process data filled to fit 32-Bit
|
||||||
|
pub data: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for MpdoWriteSource {
|
||||||
|
fn encode(self) -> Result<(crate::CobId, Option<crate::Payload>), Error> {
|
||||||
|
let cob_id = CobId::Pdo(self.pdo_channel, self.source);
|
||||||
|
//TODO is big endian correct encoding?
|
||||||
|
let multiplexer = &u32::from(self.multiplexer).to_be_bytes()[1..3];
|
||||||
|
Ok((
|
||||||
|
cob_id,
|
||||||
|
Some(Payload::from_array([
|
||||||
|
u8::from(self.destination.get_raw()),
|
||||||
|
multiplexer[0],
|
||||||
|
multiplexer[1],
|
||||||
|
multiplexer[3],
|
||||||
|
self.data[0],
|
||||||
|
self.data[1],
|
||||||
|
self.data[2],
|
||||||
|
self.data[3],
|
||||||
|
])),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for MpdoWriteSource {
|
||||||
|
fn decode(cob_id: crate::CobId, payload: Option<crate::Payload>) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let CobId::Pdo(pdo_channel, destination) = cob_id else {
|
||||||
|
return Err(Error::InvalidCobId);
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = payload.ok_or(Error::InvalidFormat)?.as_array::<8>()?;
|
||||||
|
let address = data[0];
|
||||||
|
let address_type = (address >> 7) & 1;
|
||||||
|
|
||||||
|
if address_type == 0 {
|
||||||
|
let source = address & 0b1111111;
|
||||||
|
let source = NodeId::from_raw(
|
||||||
|
u7::try_from(source).expect("7-Bit masked integer to fit into u7"),
|
||||||
|
);
|
||||||
|
|
||||||
|
//TODO is big endian correct encoding?
|
||||||
|
let multiplexer = u32::from_be_bytes([0, data[1], data[2], data[3]]);
|
||||||
|
let multiplexer =
|
||||||
|
u24::try_from(multiplexer).expect("u32 with 8 leading zeros to fit into u24");
|
||||||
|
let data = [data[4], data[5], data[6], data[7]];
|
||||||
|
Ok(Self {
|
||||||
|
pdo_channel,
|
||||||
|
destination,
|
||||||
|
source,
|
||||||
|
multiplexer,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.3.3.2 Protocol MPDO read
|
||||||
|
pub struct MpdoWriteDestination {
|
||||||
|
pub pdo_channel: PdoChannel,
|
||||||
|
pub source: NodeId,
|
||||||
|
/// This communicaton object targets all CANopen devices in the network that are configured for MPDO reception in destination
|
||||||
|
/// addressing mode if `addr=None`. Otherwise it targets the CANopen device with the `node_id` if
|
||||||
|
/// `addr=Some(node_id)`.
|
||||||
|
pub destination: Option<NodeId>,
|
||||||
|
/// index/sub-index that is used to identify the data on the receiving CANopen device
|
||||||
|
pub multiplexer: u24,
|
||||||
|
/// process data filled to fit 32-Bit
|
||||||
|
pub data: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for MpdoWriteDestination {
|
||||||
|
fn encode(self) -> Result<(crate::CobId, Option<crate::Payload>), Error> {
|
||||||
|
//TODO is big endian correct encoding?
|
||||||
|
let multiplexer = &u32::from(self.multiplexer).to_be_bytes()[1..3];
|
||||||
|
Ok((
|
||||||
|
CobId::Pdo(self.pdo_channel, self.source),
|
||||||
|
Some(Payload::from_array([
|
||||||
|
0b10000000
|
||||||
|
| u8::from(
|
||||||
|
self.destination
|
||||||
|
.as_ref()
|
||||||
|
.map(NodeId::get_raw)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
),
|
||||||
|
multiplexer[0],
|
||||||
|
multiplexer[1],
|
||||||
|
multiplexer[3],
|
||||||
|
self.data[0],
|
||||||
|
self.data[1],
|
||||||
|
self.data[2],
|
||||||
|
self.data[3],
|
||||||
|
])),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for MpdoWriteDestination {
|
||||||
|
fn decode(cob_id: crate::CobId, payload: Option<crate::Payload>) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let CobId::Pdo(pdo_channel, source) = cob_id else {
|
||||||
|
return Err(Error::InvalidCobId);
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = payload.ok_or(Error::InvalidFormat)?.as_array::<8>()?;
|
||||||
|
let address = data[0];
|
||||||
|
let address_type = (address >> 7) & 1;
|
||||||
|
|
||||||
|
if address_type == 1 {
|
||||||
|
let destination = address & 0b1111111;
|
||||||
|
let destination = match destination {
|
||||||
|
0 => None,
|
||||||
|
x => Some(NodeId::from_raw(
|
||||||
|
u7::try_from(x).expect("7-Bit masked integer to fit into u7"),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO is big endian correct encoding?
|
||||||
|
let multiplexer = u32::from_be_bytes([0, data[1], data[2], data[3]]);
|
||||||
|
let multiplexer =
|
||||||
|
u24::try_from(multiplexer).expect("u32 with 8 leading zeros to fit into u24");
|
||||||
|
let data = [data[4], data[5], data[6], data[7]];
|
||||||
|
Ok(Self {
|
||||||
|
pdo_channel,
|
||||||
|
source,
|
||||||
|
destination,
|
||||||
|
multiplexer,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
31
canopen/src/communication/pdo/consumer.rs
Normal file
31
canopen/src/communication/pdo/consumer.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use core::iter::Iterator;
|
||||||
|
|
||||||
|
use crate::object::{cia301::PdoMapping, dictionary::ObjectDictionary};
|
||||||
|
pub enum Error<OD: ObjectDictionary> {
|
||||||
|
ObjectDictionary(OD::Error),
|
||||||
|
OutOfBounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pdo_consume<'a, OD: ObjectDictionary>(
|
||||||
|
data: [u8; 8],
|
||||||
|
mappings: impl Iterator<Item = &'a PdoMapping>,
|
||||||
|
od: &mut OD,
|
||||||
|
) -> Result<(), Error<OD>> {
|
||||||
|
let mut bit_idx = 0;
|
||||||
|
let value: u64 = u64::from_le_bytes(data);
|
||||||
|
for mapping in mappings {
|
||||||
|
if bit_idx + mapping.length > 64 {
|
||||||
|
return Err(Error::OutOfBounds);
|
||||||
|
}
|
||||||
|
let mask = (1 << mapping.length) - 1;
|
||||||
|
let obj_val = (value >> bit_idx) & mask;
|
||||||
|
|
||||||
|
let data = obj_val.to_le_bytes();
|
||||||
|
|
||||||
|
od.write(mapping.index, mapping.sub_index, &data)
|
||||||
|
.map_err(Error::ObjectDictionary)?;
|
||||||
|
|
||||||
|
bit_idx += mapping.length;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
40
canopen/src/communication/pdo/producer.rs
Normal file
40
canopen/src/communication/pdo/producer.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
use core::iter::Iterator;
|
||||||
|
|
||||||
|
use crate::object::{cia301::PdoMapping, dictionary::ObjectDictionary};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error<CAN: CanDriver, OD: ObjectDictionary> {
|
||||||
|
Can(CAN::Error),
|
||||||
|
ObjectDictionary(OD::Error),
|
||||||
|
OutOfBounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CanDriver {
|
||||||
|
type Error;
|
||||||
|
fn send(&mut self, data: [u8; 8]) -> impl Future<Output = Result<(), Self::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pdo_transmit<'a, CAN: CanDriver, OD: ObjectDictionary>(
|
||||||
|
mappings: impl Iterator<Item = &'a PdoMapping>,
|
||||||
|
can: &mut CAN,
|
||||||
|
od: &mut OD,
|
||||||
|
) -> Result<(), Error<CAN, OD>> {
|
||||||
|
let mut bit_idx = 0;
|
||||||
|
let mut value: u64 = 0;
|
||||||
|
for mapping in mappings {
|
||||||
|
if bit_idx + mapping.length > 64 {
|
||||||
|
return Err(Error::OutOfBounds);
|
||||||
|
}
|
||||||
|
let mut buf = [0u8; 8];
|
||||||
|
od.read(mapping.index, mapping.sub_index, &mut buf)
|
||||||
|
.map_err(Error::ObjectDictionary)?;
|
||||||
|
|
||||||
|
let mask = (1 << mapping.length) - 1;
|
||||||
|
let obj_val = u64::from_le_bytes(buf) & mask;
|
||||||
|
value |= obj_val << bit_idx;
|
||||||
|
bit_idx += mapping.length
|
||||||
|
}
|
||||||
|
let data = value.to_le_bytes();
|
||||||
|
can.send(data).await.map_err(Error::Can)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
513
canopen/src/communication/sdo.rs
Normal file
513
canopen/src/communication/sdo.rs
Normal file
|
|
@ -0,0 +1,513 @@
|
||||||
|
use crate::{
|
||||||
|
can::CanBody,
|
||||||
|
communication::{DecodeCommunicationObject, DecodeError, EncodeCommunicationObject},
|
||||||
|
};
|
||||||
|
use derive_more::From;
|
||||||
|
use heapless::Vec;
|
||||||
|
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
|
pub mod server;
|
||||||
|
|
||||||
|
#[derive(From)]
|
||||||
|
pub enum SdoCommunicationObjectReq {
|
||||||
|
DownloadInitiateReq(SdoDownloadInitiateReq),
|
||||||
|
DownloadSegmentReq(SdoDownloadSegmentReq),
|
||||||
|
UploadInitiateReq(SdoUploadInitiateReq),
|
||||||
|
UploadSegmentReq(SdoUploadSegmentReq),
|
||||||
|
Abort(SdoAbort),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeCommunicationObject for SdoCommunicationObjectReq {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let body_data = body.data().ok_or(DecodeError::InvalidFrameType)?;
|
||||||
|
if body_data.len() != 8 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
let mut data = [0u8; 8];
|
||||||
|
data.copy_from_slice(body_data);
|
||||||
|
let ccs = (data[0] >> 5) & 0b111;
|
||||||
|
match ccs {
|
||||||
|
0 => Ok(Self::DownloadSegmentReq(SdoDownloadSegmentReq::decode(
|
||||||
|
&data,
|
||||||
|
)?)),
|
||||||
|
1 => Ok(Self::DownloadInitiateReq(SdoDownloadInitiateReq::decode(
|
||||||
|
&data,
|
||||||
|
)?)),
|
||||||
|
2 => Ok(Self::UploadInitiateReq(SdoUploadInitiateReq::decode(
|
||||||
|
&data,
|
||||||
|
)?)),
|
||||||
|
3 => Ok(Self::UploadSegmentReq(SdoUploadSegmentReq::decode(&data)?)),
|
||||||
|
4 => Ok(Self::Abort(SdoAbort::decode(&data)?)),
|
||||||
|
_ => Err(DecodeError::InvalidFormat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoCommunicationObjectReq {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
match self {
|
||||||
|
Self::DownloadInitiateReq(x) => x.encode(),
|
||||||
|
Self::DownloadSegmentReq(x) => x.encode(),
|
||||||
|
Self::UploadInitiateReq(x) => x.encode(),
|
||||||
|
Self::UploadSegmentReq(x) => x.encode(),
|
||||||
|
Self::Abort(x) => x.encode(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(From)]
|
||||||
|
pub enum SdoCommunicationObjectRes {
|
||||||
|
DownloadInitiateRes(SdoDownloadInitiateRes),
|
||||||
|
DownloadSegmentRes(SdoDownloadSegmentRes),
|
||||||
|
UploadInitiateRes(SdoUploadInitiateRes),
|
||||||
|
UploadSegmentRes(SdoUploadSegmentRes),
|
||||||
|
Abort(SdoAbort),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoCommunicationObjectRes {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
match self {
|
||||||
|
Self::DownloadInitiateRes(x) => x.encode(),
|
||||||
|
Self::DownloadSegmentRes(x) => x.encode(),
|
||||||
|
Self::UploadInitiateRes(x) => x.encode(),
|
||||||
|
Self::UploadSegmentRes(x) => x.encode(),
|
||||||
|
Self::Abort(x) => x.encode(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeCommunicationObject for SdoCommunicationObjectRes {
|
||||||
|
fn decode(body: CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let body_data = body.data().ok_or(DecodeError::InvalidFormat)?;
|
||||||
|
if body_data.len() != 8 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
let mut data = [0u8; 8];
|
||||||
|
data.copy_from_slice(body_data);
|
||||||
|
let scs = (data[0] >> 5) & 0b111;
|
||||||
|
match scs {
|
||||||
|
2 => Ok(Self::UploadInitiateRes(SdoUploadInitiateRes::decode(
|
||||||
|
&data,
|
||||||
|
)?)),
|
||||||
|
1 => Ok(Self::DownloadSegmentRes(SdoDownloadSegmentRes::decode(
|
||||||
|
&data,
|
||||||
|
)?)),
|
||||||
|
0 => Ok(Self::UploadSegmentRes(SdoUploadSegmentRes::decode(&data)?)),
|
||||||
|
3 => Ok(Self::DownloadInitiateRes(SdoDownloadInitiateRes::decode(
|
||||||
|
&data,
|
||||||
|
)?)),
|
||||||
|
4 => Ok(Self::Abort(SdoAbort::decode(&data)?)),
|
||||||
|
_ => Err(DecodeError::InvalidFormat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.3 Protocol SDO download initiate
|
||||||
|
pub struct SdoDownloadInitiateReq {
|
||||||
|
pub transfer_type: SdoDownloadInitiateType,
|
||||||
|
pub index: u16,
|
||||||
|
pub subindex: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SdoDownloadInitiateType {
|
||||||
|
NormalIndicated { bytes_to_download: u32 },
|
||||||
|
NormalNotIndicated,
|
||||||
|
ExpeditedIndicated { data: Vec<u8, 4> },
|
||||||
|
ExpeditedNotIndicated { data: Vec<u8, 4> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoDownloadInitiateReq {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let (n, e, s, data): (u8, u8, u8, [u8; 4]) = match self.transfer_type {
|
||||||
|
SdoDownloadInitiateType::NormalNotIndicated => (0, 0, 0, [0, 0, 0, 0]),
|
||||||
|
SdoDownloadInitiateType::NormalIndicated { bytes_to_download } => {
|
||||||
|
(0, 0, 1, bytes_to_download.to_le_bytes())
|
||||||
|
}
|
||||||
|
SdoDownloadInitiateType::ExpeditedNotIndicated { data } => todo!(),
|
||||||
|
SdoDownloadInitiateType::ExpeditedIndicated { data } => {
|
||||||
|
let mut data_arr = [0; 4];
|
||||||
|
data_arr[0..data.len()].copy_from_slice(&data);
|
||||||
|
(4 - data.len() as u8, 1, 1, data_arr)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let index = self.index.to_le_bytes();
|
||||||
|
let body = [
|
||||||
|
0b00100000 | (n << 2) | (e << 1) | s,
|
||||||
|
index[0],
|
||||||
|
index[1],
|
||||||
|
self.subindex,
|
||||||
|
data[0],
|
||||||
|
data[1],
|
||||||
|
data[2],
|
||||||
|
data[3],
|
||||||
|
];
|
||||||
|
|
||||||
|
CanBody::data_from_array(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoDownloadInitiateReq {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let n = (data[0] >> 2) & 0b11;
|
||||||
|
let e = data[0] & 0b10 == 0b10;
|
||||||
|
let s = data[0] & 0b1 == 1;
|
||||||
|
let index = u16::from_le_bytes([data[1], data[2]]);
|
||||||
|
let subindex = data[3];
|
||||||
|
let data = [data[4], data[5], data[6], data[7]];
|
||||||
|
|
||||||
|
let transfer_type = match (e, s) {
|
||||||
|
(false, false) => SdoDownloadInitiateType::NormalNotIndicated,
|
||||||
|
(false, true) => SdoDownloadInitiateType::NormalIndicated {
|
||||||
|
bytes_to_download: u32::from_le_bytes(data),
|
||||||
|
},
|
||||||
|
(true, false) => todo!(),
|
||||||
|
(true, true) => SdoDownloadInitiateType::ExpeditedIndicated {
|
||||||
|
data: Vec::from_slice(&data[0..((4 - n) as usize)])
|
||||||
|
.expect("slice not longer than 4"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
transfer_type,
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.3 Protocol SDO download initiate
|
||||||
|
pub struct SdoDownloadInitiateRes {
|
||||||
|
pub index: u16,
|
||||||
|
pub subindex: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoDownloadInitiateRes {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let index = self.index.to_le_bytes();
|
||||||
|
CanBody::data_from_array([0b01100000, index[0], index[1], self.subindex, 0, 0, 0, 0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoDownloadInitiateRes {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let index = u16::from_le_bytes([data[1], data[2]]);
|
||||||
|
let subindex = data[3];
|
||||||
|
Ok(Self { index, subindex })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.4 Protocol SDO download segment
|
||||||
|
pub struct SdoDownloadSegmentReq {
|
||||||
|
pub toggle_bit: bool,
|
||||||
|
pub no_more_segments: bool,
|
||||||
|
pub data: Vec<u8, 7>,
|
||||||
|
}
|
||||||
|
impl EncodeCommunicationObject for SdoDownloadSegmentReq {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let n: u8 = 7 - self.data.len() as u8;
|
||||||
|
let c = match self.no_more_segments {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
};
|
||||||
|
let t = match self.toggle_bit {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut seg_data = [0; 8];
|
||||||
|
seg_data[0] = (t << 4) | (n << 1) | c;
|
||||||
|
seg_data[1..self.data.len() + 1].copy_from_slice(&self.data);
|
||||||
|
|
||||||
|
CanBody::data_from_array(seg_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoDownloadSegmentReq {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let t = (data[0] >> 4) & 0b1;
|
||||||
|
let n = (data[0] >> 1) & 0b111;
|
||||||
|
let c = data[0] & 0b1;
|
||||||
|
let seg_data = &data[1..];
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
toggle_bit: t == 1,
|
||||||
|
no_more_segments: c == 1,
|
||||||
|
data: Vec::from_slice(&seg_data[0..((7 - n) as usize)])
|
||||||
|
.expect("seg_data to fit into vec"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.4 Protocol SDO download segment
|
||||||
|
pub struct SdoDownloadSegmentRes {
|
||||||
|
pub toggle_bit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoDownloadSegmentRes {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let t = match self.toggle_bit {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
};
|
||||||
|
CanBody::data_from_array([0b00100000 | (t << 4), 0, 0, 0, 0, 0, 0, 0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoDownloadSegmentRes {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let toggle_bit = (data[0] >> 4) & 1 == 1;
|
||||||
|
Ok(Self { toggle_bit })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.6 Protocol SDO upload initiate
|
||||||
|
pub struct SdoUploadInitiateReq {
|
||||||
|
pub index: u16,
|
||||||
|
pub subindex: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoUploadInitiateReq {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let index = self.index.to_le_bytes();
|
||||||
|
CanBody::data_from_array([0b01000000, index[0], index[1], self.subindex, 0, 0, 0, 0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoUploadInitiateReq {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let index = u16::from_le_bytes([data[1], data[2]]);
|
||||||
|
let subindex = data[3];
|
||||||
|
Ok(Self { index, subindex })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.6 Protocol SDO upload initiate
|
||||||
|
pub struct SdoUploadInitiateRes {
|
||||||
|
pub index: u16,
|
||||||
|
pub subindex: u8,
|
||||||
|
pub transfer_type: SdoUploadInitiateType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SdoUploadInitiateType {
|
||||||
|
NormalIndicated { bytes_to_upload: u32 },
|
||||||
|
NormalNotIndicated,
|
||||||
|
ExpeditedIndicated { data: Vec<u8, 4> },
|
||||||
|
ExpeditedNotIndicated { data: [u8; 4] },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoUploadInitiateRes {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let (n, e, s, data): (u8, u8, u8, [u8; 4]) = match self.transfer_type {
|
||||||
|
SdoUploadInitiateType::NormalNotIndicated => (0, 0, 0, [0, 0, 0, 0]),
|
||||||
|
SdoUploadInitiateType::NormalIndicated { bytes_to_upload } => {
|
||||||
|
(0, 0, 1, bytes_to_upload.to_le_bytes())
|
||||||
|
}
|
||||||
|
SdoUploadInitiateType::ExpeditedNotIndicated { data } => (0, 1, 0, data),
|
||||||
|
SdoUploadInitiateType::ExpeditedIndicated { data } => {
|
||||||
|
let mut data_arr = [0; 4];
|
||||||
|
data_arr[0..data.len()].copy_from_slice(&data);
|
||||||
|
(4 - data.len() as u8, 1, 1, data_arr)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let index = self.index.to_le_bytes();
|
||||||
|
let payload = [
|
||||||
|
0b01000000 | (n << 2) | (e << 1) | s,
|
||||||
|
index[0],
|
||||||
|
index[1],
|
||||||
|
self.subindex,
|
||||||
|
data[0],
|
||||||
|
data[1],
|
||||||
|
data[2],
|
||||||
|
data[3],
|
||||||
|
];
|
||||||
|
|
||||||
|
CanBody::data_from_array(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoUploadInitiateRes {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let n = (data[0] >> 2) & 0b11;
|
||||||
|
let e = (data[0] >> 1) & 0b1 == 1;
|
||||||
|
let s = data[0] & 0b1 == 1;
|
||||||
|
let index = u16::from_le_bytes([data[1], data[2]]);
|
||||||
|
let subindex = data[3];
|
||||||
|
|
||||||
|
let data = [data[4], data[5], data[6], data[7]];
|
||||||
|
|
||||||
|
let transfer_type = match (e, s) {
|
||||||
|
(false, false) => SdoUploadInitiateType::NormalNotIndicated,
|
||||||
|
(false, true) => SdoUploadInitiateType::NormalIndicated {
|
||||||
|
bytes_to_upload: u32::from_le_bytes(data),
|
||||||
|
},
|
||||||
|
(true, false) => SdoUploadInitiateType::ExpeditedNotIndicated { data },
|
||||||
|
(true, true) => SdoUploadInitiateType::ExpeditedIndicated {
|
||||||
|
data: Vec::from_slice(&data[0..((4 - n) as usize)])
|
||||||
|
.expect("slice not longer than 4"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
transfer_type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.7 Protocol SDO upload segment
|
||||||
|
pub struct SdoUploadSegmentReq {
|
||||||
|
pub toggle_bit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoUploadSegmentReq {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let t = match self.toggle_bit {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
};
|
||||||
|
CanBody::data_from_array([0b01100000 | (t << 4), 0, 0, 0, 0, 0, 0, 0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoUploadSegmentReq {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let toggle_bit = (data[0] >> 4) & 1 == 1;
|
||||||
|
Ok(Self { toggle_bit })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.7 Protocol SDO upload segment
|
||||||
|
pub struct SdoUploadSegmentRes {
|
||||||
|
pub toggle_bit: bool,
|
||||||
|
pub no_more_segments: bool,
|
||||||
|
pub data: Vec<u8, 7>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoUploadSegmentRes {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let n: u8 = 7 - self.data.len() as u8;
|
||||||
|
let c = match self.no_more_segments {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
};
|
||||||
|
let t = match self.toggle_bit {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut seg_data = [0; 7];
|
||||||
|
seg_data[0..self.data.len()].copy_from_slice(&self.data);
|
||||||
|
|
||||||
|
CanBody::data_from_array([
|
||||||
|
(t << 4) | (n << 1) | c,
|
||||||
|
seg_data[0],
|
||||||
|
seg_data[1],
|
||||||
|
seg_data[2],
|
||||||
|
seg_data[3],
|
||||||
|
seg_data[4],
|
||||||
|
seg_data[5],
|
||||||
|
seg_data[6],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoUploadSegmentRes {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let t = (data[0] >> 4) & 0b1;
|
||||||
|
let n = (data[0] >> 1) & 0b111;
|
||||||
|
let c = data[0] & 0b1;
|
||||||
|
let seg_data = &data[1..];
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
toggle_bit: t == 1,
|
||||||
|
no_more_segments: c == 1,
|
||||||
|
data: Vec::from_slice(&seg_data[0..((7 - n) as usize)])
|
||||||
|
.expect("seg_data to fit into vec"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CiA 301 7.2.4.3.17 SDO abort
|
||||||
|
pub struct SdoAbort {
|
||||||
|
pub index: u16,
|
||||||
|
pub subindex: u8,
|
||||||
|
pub abort_code: SdoAbortCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromPrimitive, IntoPrimitive, Clone, Copy)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum SdoAbortCode {
|
||||||
|
ToggleBitNotAlternated = 0x0503_0000,
|
||||||
|
SdoProtocolTimedOut = 0x0504_0000,
|
||||||
|
CommandSpecifierInvalid = 0x0504_0001,
|
||||||
|
InvalidBlockSize = 0x0504_0002,
|
||||||
|
InvalidSequenceNumber = 0x0504_0003,
|
||||||
|
CrcError = 0x0504_0004,
|
||||||
|
OutOfMemory = 0x0504_0005,
|
||||||
|
UnsupportedAccess = 0x0601_0000,
|
||||||
|
ReadToWriteOnly = 0x0601_0001,
|
||||||
|
WriteToReadOnly = 0x0601_0002,
|
||||||
|
ObjectDoesNotExist = 0x0602_0000,
|
||||||
|
ObjectCannotBeMapped = 0x0604_0041,
|
||||||
|
WouldExceedPdoLength = 0x0604_0042,
|
||||||
|
ParameterIncompatible = 0x0604_0043,
|
||||||
|
InternalIncompatible = 0x0604_0047,
|
||||||
|
AccessFailedDueToHardwareError = 0x0606_0000,
|
||||||
|
DataTypeMismatch = 0x0607_0010,
|
||||||
|
DataTypeMismatchTooLong = 0x0607_0012,
|
||||||
|
DataTypeMismatchTooShort = 0x607_0013,
|
||||||
|
SubIndexDoesNotExist = 0x0609_0011,
|
||||||
|
InvalidValue = 0x0609_0030,
|
||||||
|
InvalidValueTooHigh = 0x0609_0031,
|
||||||
|
InvalidValueTooShort = 0x0609_0032,
|
||||||
|
MaxLessThanMin = 0x0609_0036,
|
||||||
|
ResourceNotAvailableSdoConn = 0x060A_0023,
|
||||||
|
General = 0x0800_0000,
|
||||||
|
CannotTransfer = 0x0800_0020,
|
||||||
|
CannotTransferBecauseLocalControl = 0x0800_0021,
|
||||||
|
CannotTransferBecauseState = 0x0800_0022,
|
||||||
|
DynamicGenerationFailed = 0x0800_0023,
|
||||||
|
NoData = 0x0800_0024,
|
||||||
|
#[num_enum(catch_all)]
|
||||||
|
Other(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<core::convert::Infallible> for SdoAbortCode {
|
||||||
|
fn from(_value: core::convert::Infallible) -> Self {
|
||||||
|
unreachable!("Infallible never happens")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodeCommunicationObject for SdoAbort {
|
||||||
|
fn encode(self) -> CanBody {
|
||||||
|
let index = self.index.to_le_bytes();
|
||||||
|
let abort_code = u32::from(self.abort_code).to_le_bytes();
|
||||||
|
|
||||||
|
CanBody::data_from_array([
|
||||||
|
0b10000000,
|
||||||
|
index[0],
|
||||||
|
index[1],
|
||||||
|
self.subindex,
|
||||||
|
abort_code[0],
|
||||||
|
abort_code[1],
|
||||||
|
abort_code[2],
|
||||||
|
abort_code[3],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SdoAbort {
|
||||||
|
pub(crate) fn decode(data: &[u8; 8]) -> Result<Self, DecodeError> {
|
||||||
|
let cs = (data[0] >> 5) & 0b111;
|
||||||
|
|
||||||
|
let index = u16::from_le_bytes([data[1], data[2]]);
|
||||||
|
let subindex = data[3];
|
||||||
|
|
||||||
|
let abort_code = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||||
|
let abort_code = SdoAbortCode::from_primitive(abort_code);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
291
canopen/src/communication/sdo/client.rs
Normal file
291
canopen/src/communication/sdo/client.rs
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
use core::unreachable;
|
||||||
|
use heapless::Vec;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
communication::sdo::{
|
||||||
|
SdoAbort, SdoAbortCode, SdoCommunicationObjectReq, SdoCommunicationObjectRes,
|
||||||
|
SdoDownloadInitiateReq, SdoDownloadInitiateType, SdoDownloadSegmentReq,
|
||||||
|
SdoUploadInitiateReq, SdoUploadInitiateType, SdoUploadSegmentReq,
|
||||||
|
},
|
||||||
|
timer::Timer,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error<CAN: CanDriver, TIM: Timer> {
|
||||||
|
/// An error occured in the CAN driver
|
||||||
|
Can(CAN::Error),
|
||||||
|
|
||||||
|
Timer(TIM::Error),
|
||||||
|
|
||||||
|
/// The server aborted the transfer
|
||||||
|
ServerAbort(SdoAbortCode),
|
||||||
|
|
||||||
|
/// The transferred object is larger than the provided buffer
|
||||||
|
OutOfSpace,
|
||||||
|
|
||||||
|
/// The server failed to respond in time
|
||||||
|
Timeout,
|
||||||
|
|
||||||
|
/// The toggle bits didn't match
|
||||||
|
ToggleBitMismatch,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CanDriver {
|
||||||
|
type Error;
|
||||||
|
fn send(
|
||||||
|
&mut self,
|
||||||
|
co: impl Into<SdoCommunicationObjectReq>,
|
||||||
|
) -> impl Future<Output = Result<(), Self::Error>>;
|
||||||
|
fn recv(&self) -> impl Future<Output = Result<SdoCommunicationObjectRes, Self::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn upload<CAN: CanDriver, TIM: Timer>(
|
||||||
|
index: u16,
|
||||||
|
subindex: u8,
|
||||||
|
buf: &mut [u8],
|
||||||
|
can: &mut CAN,
|
||||||
|
timer: &mut TIM,
|
||||||
|
) -> Result<usize, Error<CAN, TIM>> {
|
||||||
|
can.send(SdoUploadInitiateReq { index, subindex })
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(frame) = timer
|
||||||
|
.timeout_after_ms(1000, can.recv())
|
||||||
|
.await
|
||||||
|
.map_err(Error::Timer)?
|
||||||
|
else {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code: SdoAbortCode::SdoProtocolTimedOut,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::Timeout);
|
||||||
|
};
|
||||||
|
match frame.map_err(Error::Can)? {
|
||||||
|
SdoCommunicationObjectRes::UploadInitiateRes(co) => {
|
||||||
|
match co.transfer_type {
|
||||||
|
SdoUploadInitiateType::ExpeditedIndicated { data } => {
|
||||||
|
if buf.len() < data.len() {
|
||||||
|
return Err(Error::OutOfSpace);
|
||||||
|
}
|
||||||
|
buf[..data.len()].copy_from_slice(&data);
|
||||||
|
return Ok(data.len());
|
||||||
|
}
|
||||||
|
SdoUploadInitiateType::ExpeditedNotIndicated { data } => {
|
||||||
|
if buf.len() < data.len() {
|
||||||
|
return Err(Error::OutOfSpace);
|
||||||
|
}
|
||||||
|
buf[..data.len()].copy_from_slice(&data);
|
||||||
|
return Ok(data.len());
|
||||||
|
}
|
||||||
|
SdoUploadInitiateType::NormalIndicated { bytes_to_upload }
|
||||||
|
if bytes_to_upload > buf.len() as u32 =>
|
||||||
|
{
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code: SdoAbortCode::OutOfMemory,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::OutOfSpace);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut toggle_bit = false;
|
||||||
|
let mut buf_end = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
can.send(SdoUploadSegmentReq { toggle_bit })
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(frame) = timer
|
||||||
|
.timeout_after_ms(1000, can.recv())
|
||||||
|
.await
|
||||||
|
.map_err(Error::Timer)?
|
||||||
|
else {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code: SdoAbortCode::SdoProtocolTimedOut,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::Timeout);
|
||||||
|
};
|
||||||
|
let frame = frame.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
match frame {
|
||||||
|
SdoCommunicationObjectRes::UploadSegmentRes(co) => {
|
||||||
|
if toggle_bit != toggle_bit {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code: SdoAbortCode::ToggleBitNotAlternated,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::ToggleBitMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_buf_end = buf_end + co.data.len();
|
||||||
|
if new_buf_end > buf.len() {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code: SdoAbortCode::OutOfMemory,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::OutOfSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[buf_end..new_buf_end].copy_from_slice(&co.data);
|
||||||
|
buf_end = new_buf_end;
|
||||||
|
|
||||||
|
if co.no_more_segments {
|
||||||
|
return Ok(buf_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SdoCommunicationObjectRes::Abort(co) => {
|
||||||
|
return Err(Error::ServerAbort(co.abort_code));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle_bit = !toggle_bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SdoCommunicationObjectRes::Abort(co) => return Err(Error::ServerAbort(co.abort_code)),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn download<CAN: CanDriver, TIM: Timer>(
|
||||||
|
index: u16,
|
||||||
|
subindex: u8,
|
||||||
|
buf: &[u8],
|
||||||
|
can: &mut CAN,
|
||||||
|
timer: &mut TIM,
|
||||||
|
) -> Result<(), Error<CAN, TIM>> {
|
||||||
|
let (expedited, transfer_type) = if buf.len() <= 4 {
|
||||||
|
let Ok(data) = Vec::from_slice(buf) else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
(true, SdoDownloadInitiateType::ExpeditedIndicated { data })
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
SdoDownloadInitiateType::NormalIndicated {
|
||||||
|
bytes_to_download: buf.len() as u32,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
can.send(SdoDownloadInitiateReq {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
transfer_type,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(frame) = timer
|
||||||
|
.timeout_after_ms(1000, can.recv())
|
||||||
|
.await
|
||||||
|
.map_err(Error::Timer)?
|
||||||
|
else {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code: SdoAbortCode::SdoProtocolTimedOut,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::Timeout);
|
||||||
|
};
|
||||||
|
let frame = frame.map_err(Error::Can)?;
|
||||||
|
match frame {
|
||||||
|
SdoCommunicationObjectRes::DownloadInitiateRes(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SdoCommunicationObjectRes::Abort(co) => {
|
||||||
|
return Err(Error::ServerAbort(co.abort_code));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expedited {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut toggle_bit = false;
|
||||||
|
let total_segments = buf.len().div_ceil(7);
|
||||||
|
for (segment_idx, segment) in buf.chunks(7).enumerate() {
|
||||||
|
let Ok(data) = Vec::from_slice(segment) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
can.send(SdoDownloadSegmentReq {
|
||||||
|
toggle_bit,
|
||||||
|
no_more_segments: segment_idx == total_segments - 1,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(frame) = timer
|
||||||
|
.timeout_after_ms(1000, can.recv())
|
||||||
|
.await
|
||||||
|
.map_err(Error::Timer)?
|
||||||
|
else {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code: SdoAbortCode::SdoProtocolTimedOut,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::Timeout);
|
||||||
|
};
|
||||||
|
let frame = frame.map_err(Error::Can)?;
|
||||||
|
match frame {
|
||||||
|
SdoCommunicationObjectRes::DownloadSegmentRes(co) => {
|
||||||
|
if co.toggle_bit != toggle_bit {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
abort_code: SdoAbortCode::ToggleBitNotAlternated,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::ToggleBitMismatch);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SdoCommunicationObjectRes::Abort(co) => {
|
||||||
|
return Err(Error::ServerAbort(co.abort_code));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle_bit = !toggle_bit;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
313
canopen/src/communication/sdo/server.rs
Normal file
313
canopen/src/communication/sdo/server.rs
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
use core::unreachable;
|
||||||
|
use heapless::Vec;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
communication::{
|
||||||
|
DecodeCommunicationObject, DecodeError,
|
||||||
|
sdo::{
|
||||||
|
SdoAbort, SdoAbortCode, SdoCommunicationObjectRes, SdoDownloadInitiateReq,
|
||||||
|
SdoDownloadInitiateRes, SdoDownloadInitiateType, SdoDownloadSegmentReq,
|
||||||
|
SdoDownloadSegmentRes, SdoUploadInitiateReq, SdoUploadInitiateRes,
|
||||||
|
SdoUploadInitiateType, SdoUploadSegmentReq, SdoUploadSegmentRes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
object::dictionary::ObjectDictionary,
|
||||||
|
timer::Timer,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum Error<CAN: CanDriver, OD: ObjectDictionary, T: Timer> {
|
||||||
|
/// An error occured in the CAN Driver
|
||||||
|
Can(CAN::Error),
|
||||||
|
|
||||||
|
/// An error occured in the Object Dictionary
|
||||||
|
ObjectDictionary(OD::Error),
|
||||||
|
|
||||||
|
Timer(T::Error),
|
||||||
|
|
||||||
|
/// The SDO client aborted the transfer
|
||||||
|
ClientAbort(SdoAbortCode),
|
||||||
|
|
||||||
|
/// The transferred object is larger than the provided buffer
|
||||||
|
OutOfSpace,
|
||||||
|
|
||||||
|
/// The toggle bit from the client didn't meet expectations
|
||||||
|
ToggleBitMismatch,
|
||||||
|
|
||||||
|
/// The client failed to answer in time
|
||||||
|
Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum InitReq {
|
||||||
|
Download(SdoDownloadInitiateReq),
|
||||||
|
Upload(SdoUploadInitiateReq),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeCommunicationObject for InitReq {
|
||||||
|
fn decode(body: crate::can::CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let body_data = body.data().ok_or(DecodeError::InvalidFrameType)?;
|
||||||
|
if body_data.len() != 8 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
let mut data = [0u8; 8];
|
||||||
|
data.copy_from_slice(body_data);
|
||||||
|
let ccs = (data[0] >> 5) & 0b111;
|
||||||
|
match ccs {
|
||||||
|
1 => Ok(Self::Download(SdoDownloadInitiateReq::decode(&data)?)),
|
||||||
|
2 => Ok(Self::Upload(SdoUploadInitiateReq::decode(&data)?)),
|
||||||
|
_ => Err(DecodeError::InvalidFormat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Req {
|
||||||
|
DownloadSegment(SdoDownloadSegmentReq),
|
||||||
|
UploadSegment(SdoUploadSegmentReq),
|
||||||
|
Abort(SdoAbort),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeCommunicationObject for Req {
|
||||||
|
fn decode(body: crate::can::CanBody) -> Result<Self, DecodeError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let body_data = body.data().ok_or(DecodeError::InvalidFrameType)?;
|
||||||
|
if body_data.len() != 8 {
|
||||||
|
return Err(DecodeError::InvalidFormat);
|
||||||
|
}
|
||||||
|
let mut data = [0u8; 8];
|
||||||
|
data.copy_from_slice(body_data);
|
||||||
|
let ccs = (data[0] >> 5) & 0b111;
|
||||||
|
match ccs {
|
||||||
|
0 => Ok(Self::DownloadSegment(SdoDownloadSegmentReq::decode(&data)?)),
|
||||||
|
3 => Ok(Self::UploadSegment(SdoUploadSegmentReq::decode(&data)?)),
|
||||||
|
4 => Ok(Self::Abort(SdoAbort::decode(&data)?)),
|
||||||
|
_ => Err(DecodeError::InvalidFormat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CanDriver {
|
||||||
|
type Error;
|
||||||
|
fn send(
|
||||||
|
&mut self,
|
||||||
|
co: impl Into<SdoCommunicationObjectRes>,
|
||||||
|
) -> impl Future<Output = Result<(), Self::Error>>;
|
||||||
|
fn recv(&mut self) -> impl Future<Output = Result<Req, Self::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sdo_server<CAN: CanDriver, OD: ObjectDictionary, TIM: Timer>(
|
||||||
|
init: &InitReq,
|
||||||
|
buf: &mut [u8],
|
||||||
|
can: &mut CAN,
|
||||||
|
od: &mut OD,
|
||||||
|
tim: &mut TIM,
|
||||||
|
) -> Result<(), Error<CAN, OD, TIM>> {
|
||||||
|
match init {
|
||||||
|
InitReq::Download(co) => {
|
||||||
|
let expedited_data = match &co.transfer_type {
|
||||||
|
SdoDownloadInitiateType::ExpeditedIndicated { data } => Some(data.as_slice()),
|
||||||
|
SdoDownloadInitiateType::ExpeditedNotIndicated { data } => Some(data.as_slice()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(data) = expedited_data {
|
||||||
|
if let Err(err) = od.write(co.index, co.subindex, data) {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
abort_code: todo!(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::ObjectDictionary(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
can.send(SdoDownloadInitiateRes {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
can.send(SdoDownloadInitiateRes {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
let mut expected_toggle_bit = false;
|
||||||
|
let mut buf_end = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(frame) = tim
|
||||||
|
.timeout_after_ms(1000, can.recv())
|
||||||
|
.await
|
||||||
|
.map_err(Error::Timer)?
|
||||||
|
else {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
abort_code: SdoAbortCode::SdoProtocolTimedOut,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::Timeout);
|
||||||
|
};
|
||||||
|
let frame = frame.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
match frame {
|
||||||
|
Req::DownloadSegment(seg_co) => {
|
||||||
|
if seg_co.toggle_bit != expected_toggle_bit {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
abort_code: SdoAbortCode::ToggleBitNotAlternated,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::ToggleBitMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_buf_end = buf_end + seg_co.data.len();
|
||||||
|
if new_buf_end > buf.len() {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
abort_code: SdoAbortCode::OutOfMemory,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::OutOfSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[buf_end..new_buf_end].copy_from_slice(seg_co.data.as_slice());
|
||||||
|
|
||||||
|
buf_end = new_buf_end;
|
||||||
|
|
||||||
|
if seg_co.no_more_segments {
|
||||||
|
if let Err(err) = od.write(co.index, co.subindex, &buf[..buf_end]) {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
abort_code: todo!(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::ObjectDictionary(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
can.send(SdoDownloadSegmentRes {
|
||||||
|
toggle_bit: seg_co.toggle_bit,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
if seg_co.no_more_segments {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_toggle_bit = !expected_toggle_bit;
|
||||||
|
}
|
||||||
|
Req::Abort(co) => return Err(Error::ClientAbort(co.abort_code)),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InitReq::Upload(co) => {
|
||||||
|
let buf_end = match od.read(co.index, co.subindex, buf) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(err) => {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
abort_code: todo!(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::ObjectDictionary(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if buf_end <= 4 {
|
||||||
|
let Ok(data) = Vec::from_slice(&buf[..buf_end]) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
can.send(SdoUploadInitiateRes {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
transfer_type: SdoUploadInitiateType::ExpeditedIndicated { data },
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
can.send(SdoUploadInitiateRes {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
transfer_type: SdoUploadInitiateType::NormalIndicated {
|
||||||
|
bytes_to_upload: buf_end as _,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
|
||||||
|
let mut expected_toggle_bit = false;
|
||||||
|
|
||||||
|
let total_segments = buf_end.div_ceil(7);
|
||||||
|
for (segment_idx, segment) in buf[..buf_end].chunks(7).enumerate() {
|
||||||
|
let Some(frame) = tim
|
||||||
|
.timeout_after_ms(1000, can.recv())
|
||||||
|
.await
|
||||||
|
.map_err(Error::Timer)?
|
||||||
|
else {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
abort_code: SdoAbortCode::SdoProtocolTimedOut,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::Timeout);
|
||||||
|
};
|
||||||
|
let frame = frame.map_err(Error::Can)?;
|
||||||
|
match frame {
|
||||||
|
Req::UploadSegment(seg_co) => {
|
||||||
|
if seg_co.toggle_bit != expected_toggle_bit {
|
||||||
|
can.send(SdoAbort {
|
||||||
|
index: co.index,
|
||||||
|
subindex: co.subindex,
|
||||||
|
abort_code: SdoAbortCode::ToggleBitNotAlternated,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
return Err(Error::ToggleBitMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(data) = Vec::from_slice(segment) else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
can.send(SdoUploadSegmentRes {
|
||||||
|
toggle_bit: seg_co.toggle_bit,
|
||||||
|
data,
|
||||||
|
no_more_segments: segment_idx == total_segments - 1,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::Can)?;
|
||||||
|
}
|
||||||
|
Req::Abort(co) => return Err(Error::ClientAbort(co.abort_code)),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
expected_toggle_bit = !expected_toggle_bit;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
canopen/src/lib.rs
Normal file
75
canopen/src/lib.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#![cfg_attr(not(test), no_std)]
|
||||||
|
|
||||||
|
use embedded_can::{Id, StandardId};
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub mod can;
|
||||||
|
pub mod communication;
|
||||||
|
pub mod node;
|
||||||
|
pub mod object;
|
||||||
|
pub mod timer;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
|
||||||
|
pub struct NodeId(u8);
|
||||||
|
impl NodeId {
|
||||||
|
pub const unsafe fn new_unchecked(value: u8) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
pub const fn new(value: u8) -> Option<Self> {
|
||||||
|
if value == 0 || value > 0b1111111 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CobId;
|
||||||
|
impl CobId {
|
||||||
|
pub const fn nmt() -> Id {
|
||||||
|
Id::Standard(StandardId::new(0).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn sync() -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x80).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn nmt_error_control(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x700 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn emcy(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x80 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn default_sdo_rx(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x600 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn default_sdo_tx(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x580 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn tpdo1(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x180 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn rpdo1(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x200 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn tpdo2(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x280 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn rpdo2(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x300 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn tpdo3(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x380 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn rpdo3(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x400 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn tpdo4(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x480 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
pub const fn rpdo4(node: NodeId) -> Id {
|
||||||
|
Id::Standard(StandardId::new(0x500 + node.0 as u16).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
828
canopen/src/node.rs
Normal file
828
canopen/src/node.rs
Normal file
|
|
@ -0,0 +1,828 @@
|
||||||
|
use crate::can::CanBody;
|
||||||
|
use crate::communication::nmt::{
|
||||||
|
NmtHeartbeat, NmtHeartbeatState, NmtNodeGuardingRes, NmtNodeGuardingResState,
|
||||||
|
};
|
||||||
|
use crate::communication::pdo::{self, consumer::pdo_consume, producer::pdo_transmit};
|
||||||
|
use crate::communication::sdo::{
|
||||||
|
self,
|
||||||
|
server::{InitReq, Req, sdo_server},
|
||||||
|
};
|
||||||
|
use crate::communication::{DecodeCommunicationObject, EncodeCommunicationObject};
|
||||||
|
use crate::object::cia301::{
|
||||||
|
CobIdEmcy, CobIdSync, CommunicationCyclePeriod, ConsumerHeartbeatTime, EmergencyConsumer,
|
||||||
|
GuardTime, InhibitTimeEmcy, LifeTimeFactor, PdoMapping, ProducerHeartbeatTime,
|
||||||
|
SynchronousCounterOverflowValue, SynchronousWindowLength,
|
||||||
|
};
|
||||||
|
use crate::object::{self, Object};
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use alloc::collections::binary_heap::BinaryHeap;
|
||||||
|
use alloc::vec;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::cmp::Ordering;
|
||||||
|
use core::convert::Infallible;
|
||||||
|
use core::marker::PhantomPinned;
|
||||||
|
use core::pin::Pin;
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
use core::task::{Context, Poll};
|
||||||
|
use core::unreachable;
|
||||||
|
use embedded_can::{Frame, Id};
|
||||||
|
|
||||||
|
use crate::{CobId, NodeId};
|
||||||
|
use crate::{
|
||||||
|
can::{CanRx, CanTx},
|
||||||
|
object::dictionary::ObjectDictionary,
|
||||||
|
timer::Timer,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct HeapFrame<F: Frame>(pub F);
|
||||||
|
impl<F: Frame> PartialEq<Self> for HeapFrame<F> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0.id().eq(&other.0.id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<F: Frame> Eq for HeapFrame<F> {}
|
||||||
|
impl<F: Frame> PartialOrd<Self> for HeapFrame<F> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
self.0.id().partial_cmp(&other.0.id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<F: Frame> Ord for HeapFrame<F> {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.0.id().cmp(&other.0.id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Error {}
|
||||||
|
|
||||||
|
pub enum CanIdMapping {
|
||||||
|
Sync,
|
||||||
|
Time,
|
||||||
|
Emcy(u8),
|
||||||
|
NmtHeartbeatConsumer(u8),
|
||||||
|
NmtNodeGuardClient(u8),
|
||||||
|
SdoServer(u8),
|
||||||
|
Rpdo(u16),
|
||||||
|
Tpdo(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum RunningTask {
|
||||||
|
EmcyConsumer(u8),
|
||||||
|
HeartbeatProducer,
|
||||||
|
HeartbeatConsumer(u8),
|
||||||
|
NodeGuardingServer,
|
||||||
|
NodeGuardingClient(u8),
|
||||||
|
SdoServer(u8),
|
||||||
|
PdoProducer(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CanOpenNode<CT: CanTx, CR: CanRx, D: ObjectDictionary + 'static, T: Timer> {
|
||||||
|
can_tx: CT,
|
||||||
|
can_rx: CR,
|
||||||
|
od: DefaultDictionary<D>,
|
||||||
|
timer: T,
|
||||||
|
node_id: NodeId,
|
||||||
|
running: BinarySearchMap<RunningTask, Pin<Box<Running<CR::Frame, D>>>>,
|
||||||
|
can_id_map: BinarySearchMap<Id, CanIdMapping>,
|
||||||
|
node_guarding_bit: bool,
|
||||||
|
state: NmtNodeGuardingResState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CT, CR, D, T> CanOpenNode<CT, CR, D, T>
|
||||||
|
where
|
||||||
|
CT: CanTx + Clone + 'static,
|
||||||
|
CT::Frame: 'static,
|
||||||
|
CR: CanRx,
|
||||||
|
CR::Frame: 'static,
|
||||||
|
D: ObjectDictionary,
|
||||||
|
T: Timer + Clone + 'static,
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
node_id: NodeId,
|
||||||
|
can_tx: CT,
|
||||||
|
can_rx: CR,
|
||||||
|
od: DefaultDictionary<D>,
|
||||||
|
timer: T,
|
||||||
|
) -> Self {
|
||||||
|
let mut can_id_map = BinarySearchMap::default();
|
||||||
|
|
||||||
|
for (idx, sdo_cfg) in od.sdo_server.iter() {
|
||||||
|
if sdo_cfg.server_to_client.valid && sdo_cfg.client_to_server.valid {
|
||||||
|
can_id_map.insert(sdo_cfg.client_to_server.id, CanIdMapping::SdoServer(*idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (idx, tpdo_cfg) in od.tpdos.iter() {
|
||||||
|
if tpdo_cfg.valid {
|
||||||
|
can_id_map.insert(tpdo_cfg.id, CanIdMapping::Tpdo(*idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (idx, rpdo_cfg) in od.rpdos.iter() {
|
||||||
|
if rpdo_cfg.valid {
|
||||||
|
can_id_map.insert(rpdo_cfg.id, CanIdMapping::Rpdo(*idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut running = BinarySearchMap::default();
|
||||||
|
|
||||||
|
running.insert(
|
||||||
|
RunningTask::HeartbeatProducer,
|
||||||
|
Running::new(|run| {
|
||||||
|
let mut can_tx = can_tx.clone();
|
||||||
|
let mut timer = timer.clone();
|
||||||
|
Box::new(async move {
|
||||||
|
loop {
|
||||||
|
let id = CobId::nmt_error_control(node_id);
|
||||||
|
can_tx
|
||||||
|
.send(
|
||||||
|
NmtHeartbeat {
|
||||||
|
state: match run.node_state() {
|
||||||
|
NmtNodeGuardingResState::PreOperational => {
|
||||||
|
NmtHeartbeatState::PreOperational
|
||||||
|
}
|
||||||
|
NmtNodeGuardingResState::Operational => {
|
||||||
|
NmtHeartbeatState::Operational
|
||||||
|
}
|
||||||
|
NmtNodeGuardingResState::Stopped => {
|
||||||
|
NmtHeartbeatState::Stopped
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.encode()
|
||||||
|
.to_frame(id),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let time = run.object_dictionary().heartbeat_producer.period;
|
||||||
|
timer.delay_ms(time as _).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
can_tx,
|
||||||
|
can_rx,
|
||||||
|
od,
|
||||||
|
timer,
|
||||||
|
node_id,
|
||||||
|
can_id_map,
|
||||||
|
node_guarding_bit: false,
|
||||||
|
state: NmtNodeGuardingResState::PreOperational,
|
||||||
|
running,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn obj_dict(&mut self) -> &mut D {
|
||||||
|
&mut self.od.extension
|
||||||
|
}
|
||||||
|
pub fn configure_sdo_server(&mut self, index: u8) -> Result<(), Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn configure_rpdo(&mut self, index: usize) -> Result<(), Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn configure_tpdo(&mut self, index: usize) -> Result<(), Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enter_preoperational(&mut self) {
|
||||||
|
self.state = NmtNodeGuardingResState::PreOperational;
|
||||||
|
}
|
||||||
|
pub fn enter_operational(&mut self) {
|
||||||
|
self.state = NmtNodeGuardingResState::Operational;
|
||||||
|
}
|
||||||
|
pub fn enter_stopped(&mut self) {
|
||||||
|
self.state = NmtNodeGuardingResState::Stopped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<CT, CR, D, T> Send for CanOpenNode<CT, CR, D, T>
|
||||||
|
where
|
||||||
|
CT: CanTx + Clone + 'static,
|
||||||
|
CR::Frame: 'static,
|
||||||
|
CR: CanRx,
|
||||||
|
D: ObjectDictionary + 'static,
|
||||||
|
T: Timer + Clone + 'static,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CT, CR, D, T> Future for CanOpenNode<CT, CR, D, T>
|
||||||
|
where
|
||||||
|
CT: CanTx + Clone + 'static,
|
||||||
|
CR::Frame: 'static,
|
||||||
|
CR: CanRx,
|
||||||
|
D: ObjectDictionary + 'static,
|
||||||
|
T: Timer + Clone + 'static,
|
||||||
|
{
|
||||||
|
type Output = Result<(), Error>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = unsafe { self.get_unchecked_mut() };
|
||||||
|
|
||||||
|
let mut one_ready = false;
|
||||||
|
|
||||||
|
if let Poll::Ready(poll_frame_res) =
|
||||||
|
unsafe { Pin::new_unchecked(&mut this.can_rx) }.poll_recv(cx)
|
||||||
|
{
|
||||||
|
one_ready = true;
|
||||||
|
if let Ok(frame) = poll_frame_res {
|
||||||
|
if frame.is_data_frame() && frame.id() == CobId::nmt() {
|
||||||
|
todo!("signal application")
|
||||||
|
}
|
||||||
|
if frame.is_remote_frame() && frame.id() == CobId::nmt_error_control(this.node_id) {
|
||||||
|
let mut can_tx = this.can_tx.clone();
|
||||||
|
let node_guarding_bit = this.node_guarding_bit;
|
||||||
|
this.node_guarding_bit = !this.node_guarding_bit;
|
||||||
|
let state = this.state;
|
||||||
|
let id = frame.id();
|
||||||
|
this.running.insert(
|
||||||
|
RunningTask::NodeGuardingServer,
|
||||||
|
Running::new(|_| {
|
||||||
|
Box::new(async move {
|
||||||
|
can_tx
|
||||||
|
.send(
|
||||||
|
NmtNodeGuardingRes {
|
||||||
|
toggle_bit: node_guarding_bit,
|
||||||
|
state: state,
|
||||||
|
}
|
||||||
|
.encode()
|
||||||
|
.to_frame(id),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
match this.can_id_map.get(&frame.id()) {
|
||||||
|
Some(CanIdMapping::SdoServer(idx)) => {
|
||||||
|
let Some(sdo_config) = this.od.sdo_server.get(idx) else {
|
||||||
|
unreachable!("can_id_map is synced with object dictionary");
|
||||||
|
};
|
||||||
|
let body = CanBody::from_frame(&frame);
|
||||||
|
if let Some(running_sdo_server) =
|
||||||
|
this.running.get_mut(&RunningTask::SdoServer(*idx))
|
||||||
|
{
|
||||||
|
running_sdo_server.as_mut().enqueue_frame(frame);
|
||||||
|
} else if let Ok(init_co) = InitReq::decode(body) {
|
||||||
|
let server_to_client: Id = sdo_config.server_to_client.id;
|
||||||
|
let can_tx = this.can_tx.clone();
|
||||||
|
let mut timer = this.timer.clone();
|
||||||
|
|
||||||
|
this.running.insert(RunningTask::SdoServer(*idx), {
|
||||||
|
Running::new(|mut run| {
|
||||||
|
Box::new(async move {
|
||||||
|
let mut can = SdoServerCan {
|
||||||
|
can_tx,
|
||||||
|
run: run.clone(),
|
||||||
|
server_to_client,
|
||||||
|
};
|
||||||
|
let mut buf = Box::new([0u8; 1024]);
|
||||||
|
sdo_server(
|
||||||
|
&init_co,
|
||||||
|
buf.as_mut(),
|
||||||
|
&mut can,
|
||||||
|
&mut run,
|
||||||
|
&mut timer,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(CanIdMapping::Rpdo(idx)) if frame.is_data_frame() => {
|
||||||
|
let Some(rpdo) = this.od.rpdos.get(idx) else {
|
||||||
|
unreachable!("can_id_map is synced with object dictionary");
|
||||||
|
};
|
||||||
|
let mut data = [0u8; 8];
|
||||||
|
data[..frame.data().len()].copy_from_slice(frame.data());
|
||||||
|
|
||||||
|
pdo_consume(data, rpdo.mappings.clone().values(), &mut this.od);
|
||||||
|
}
|
||||||
|
Some(CanIdMapping::Tpdo(idx)) if frame.is_remote_frame() => {
|
||||||
|
let Some(tpdo) = this.od.tpdos.get(idx) else {
|
||||||
|
unreachable!("can_id_map is synced with object dictionary");
|
||||||
|
};
|
||||||
|
//TODO implement correct PDO type handling
|
||||||
|
this.running.insert(RunningTask::PdoProducer(*idx), {
|
||||||
|
Running::new(|mut run| {
|
||||||
|
let mappings = tpdo.mappings.clone();
|
||||||
|
let server_to_client: Id = tpdo.id;
|
||||||
|
let can_tx = this.can_tx.clone();
|
||||||
|
Box::new(async move {
|
||||||
|
let mut can = PdoCan {
|
||||||
|
server_to_client,
|
||||||
|
can_tx,
|
||||||
|
};
|
||||||
|
pdo_transmit(mappings.values(), &mut can, &mut run).await;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// The frame is not interesting for this node
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!("error handling")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.running.retain(|_idx, running_sdo| {
|
||||||
|
match running_sdo.as_mut().poll(&mut this.od, this.state, cx) {
|
||||||
|
Poll::Pending => true,
|
||||||
|
Poll::Ready(Ok(_)) => {
|
||||||
|
one_ready = true;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => todo!("error handling"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if one_ready {
|
||||||
|
cx.waker().clone().wake();
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultDictionary<D: ObjectDictionary + 'static> {
|
||||||
|
cob_id_sync: CobIdSync,
|
||||||
|
communication_cycle_period: CommunicationCyclePeriod,
|
||||||
|
synchronous_window_length: SynchronousWindowLength,
|
||||||
|
synchronous_counter_overflow_value: SynchronousCounterOverflowValue,
|
||||||
|
|
||||||
|
/// CiA 301 object `0x100C`, `0x100D`
|
||||||
|
guard_time: GuardTime,
|
||||||
|
life_time_factor: LifeTimeFactor,
|
||||||
|
|
||||||
|
emcy_producer: CobIdEmcy,
|
||||||
|
inhibit_time_emcy: InhibitTimeEmcy,
|
||||||
|
|
||||||
|
/// CiA 301 object `0x1016`
|
||||||
|
heartbeat_consumer: BinarySearchMap<u8, ConsumerHeartbeatTime>,
|
||||||
|
|
||||||
|
/// CiA 301 object `0x1017`
|
||||||
|
heartbeat_producer: ProducerHeartbeatTime,
|
||||||
|
|
||||||
|
/// CiA 301 object `0x1028`
|
||||||
|
emcy_consumer: BinarySearchMap<u8, EmergencyConsumer>,
|
||||||
|
|
||||||
|
/// CiA 301 objects `0x1200` - `0x127F`
|
||||||
|
sdo_server: BinarySearchMap<u8, SdoConfiguration>,
|
||||||
|
|
||||||
|
/// CiA 301 objects `0x1400`-`0x15FF` and `0x1600`-`0x17FF`
|
||||||
|
rpdos: BinarySearchMap<u16, PdoConfiguration>,
|
||||||
|
|
||||||
|
/// CiA 301 objects `0x1800`-`0x19FF` and `0x1A00`-`0x1BFF`
|
||||||
|
tpdos: BinarySearchMap<u16, PdoConfiguration>,
|
||||||
|
|
||||||
|
pub extension: D,
|
||||||
|
}
|
||||||
|
impl<D: ObjectDictionary + 'static> DefaultDictionary<D> {
|
||||||
|
pub fn new(node_id: NodeId, extension: D) -> Self {
|
||||||
|
let mut sdo_server = BinarySearchMap::default();
|
||||||
|
sdo_server.insert(
|
||||||
|
0,
|
||||||
|
SdoConfiguration {
|
||||||
|
client_to_server: SdoCobId {
|
||||||
|
valid: true,
|
||||||
|
dynamic: false,
|
||||||
|
id: CobId::default_sdo_rx(node_id),
|
||||||
|
},
|
||||||
|
server_to_client: SdoCobId {
|
||||||
|
valid: true,
|
||||||
|
dynamic: false,
|
||||||
|
id: CobId::default_sdo_tx(node_id),
|
||||||
|
},
|
||||||
|
remote_node_id: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
cob_id_sync: CobIdSync {
|
||||||
|
generate: false,
|
||||||
|
id: CobId::sync(),
|
||||||
|
},
|
||||||
|
communication_cycle_period: CommunicationCyclePeriod { period: 1000 },
|
||||||
|
synchronous_window_length: SynchronousWindowLength,
|
||||||
|
synchronous_counter_overflow_value: SynchronousCounterOverflowValue,
|
||||||
|
guard_time: GuardTime { guard_time: 1000 },
|
||||||
|
life_time_factor: LifeTimeFactor {
|
||||||
|
life_time_factor: None,
|
||||||
|
},
|
||||||
|
emcy_producer: CobIdEmcy {
|
||||||
|
valid: true,
|
||||||
|
id: CobId::emcy(node_id),
|
||||||
|
},
|
||||||
|
inhibit_time_emcy: InhibitTimeEmcy { inhibit_time: None },
|
||||||
|
heartbeat_consumer: BinarySearchMap::default(),
|
||||||
|
heartbeat_producer: ProducerHeartbeatTime { period: 1000 },
|
||||||
|
emcy_consumer: BinarySearchMap::default(),
|
||||||
|
sdo_server,
|
||||||
|
rpdos: BinarySearchMap::default(),
|
||||||
|
tpdos: BinarySearchMap::default(),
|
||||||
|
extension,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: ObjectDictionary> ObjectDictionary for DefaultDictionary<D> {
|
||||||
|
type Error = object::Error;
|
||||||
|
|
||||||
|
fn read(&self, index: u16, subindex: u8, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||||
|
match index {
|
||||||
|
0x1005 => self.cob_id_sync.read(subindex, buf).map_err(Into::into),
|
||||||
|
0x1006 => self
|
||||||
|
.communication_cycle_period
|
||||||
|
.read(subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
0x1007 => todo!("synchronous_window_length"),
|
||||||
|
0x100C => self.guard_time.read(subindex, buf).map_err(Into::into),
|
||||||
|
0x100D => self
|
||||||
|
.life_time_factor
|
||||||
|
.read(subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
0x1014 => self.emcy_producer.read(subindex, buf).map_err(Into::into),
|
||||||
|
0x1015 => self
|
||||||
|
.inhibit_time_emcy
|
||||||
|
.read(subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
0x1016 => todo!("heartbeat_consumer"),
|
||||||
|
0x1017 => self
|
||||||
|
.heartbeat_producer
|
||||||
|
.read(subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
0x1019 => todo!("synchronous_counter_overflow_value"),
|
||||||
|
0x1028 => todo!("emcy_consumer"),
|
||||||
|
x if (0x1200..=0x127F).contains(&x) => unimplemented!("sdo_server"),
|
||||||
|
x if (0x1400..=0x15FF).contains(&x) || (0x1600..=0x17FF).contains(&x) => {
|
||||||
|
todo!("rpdo_server")
|
||||||
|
}
|
||||||
|
x if (0x1800..=0x19FF).contains(&x) || (0x1A00..=0x1BFF).contains(&x) => {
|
||||||
|
todo!("tpdo_server")
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => self
|
||||||
|
.extension
|
||||||
|
.read(index, subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, index: u16, subindex: u8, buf: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
match index {
|
||||||
|
0x1005 => self.cob_id_sync.write(subindex, buf).map_err(Into::into),
|
||||||
|
0x1006 => self
|
||||||
|
.communication_cycle_period
|
||||||
|
.write(subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
0x1007 => todo!("synchronous_window_length"),
|
||||||
|
0x100C => self.guard_time.write(subindex, buf).map_err(Into::into),
|
||||||
|
0x100D => self
|
||||||
|
.life_time_factor
|
||||||
|
.write(subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
0x1014 => self.emcy_producer.write(subindex, buf).map_err(Into::into),
|
||||||
|
0x1015 => self
|
||||||
|
.inhibit_time_emcy
|
||||||
|
.write(subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
0x1016 => todo!("heartbeat_consumer"),
|
||||||
|
0x1017 => self
|
||||||
|
.heartbeat_producer
|
||||||
|
.write(subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
0x1019 => todo!("synchronous_counter_overflow_value"),
|
||||||
|
0x1028 => todo!("emcy_consumer"),
|
||||||
|
x if (0x1200..=0x127F).contains(&x) => unimplemented!("sdo_server"),
|
||||||
|
x if (0x1400..=0x15FF).contains(&x) || (0x1600..=0x17FF).contains(&x) => {
|
||||||
|
todo!("rpdo_server")
|
||||||
|
}
|
||||||
|
x if (0x1800..=0x19FF).contains(&x) || (0x1A00..=0x1BFF).contains(&x) => {
|
||||||
|
todo!("tpdo_server")
|
||||||
|
}
|
||||||
|
_ => self
|
||||||
|
.extension
|
||||||
|
.write(index, subindex, buf)
|
||||||
|
.map_err(Into::into),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PdoConfiguration {
|
||||||
|
valid: bool,
|
||||||
|
id: Id,
|
||||||
|
mappings: BinarySearchMap<u8, PdoMapping>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SdoConfiguration {
|
||||||
|
pub client_to_server: SdoCobId,
|
||||||
|
pub server_to_client: SdoCobId,
|
||||||
|
pub remote_node_id: Option<NodeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct SdoCobId {
|
||||||
|
pub valid: bool,
|
||||||
|
pub dynamic: bool,
|
||||||
|
pub id: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Running<F: Frame, D: ObjectDictionary + 'static> {
|
||||||
|
frames: BinaryHeap<HeapFrame<F>>,
|
||||||
|
od: *mut DefaultDictionary<D>,
|
||||||
|
state: NmtNodeGuardingResState,
|
||||||
|
task: Box<dyn Future<Output = Result<(), Error>>>,
|
||||||
|
_pp: PhantomPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: Frame, D: ObjectDictionary + 'static> Running<F, D> {
|
||||||
|
pub fn new<FN: FnOnce(RunningRef<F, D>) -> Box<dyn Future<Output = Result<(), Error>>>>(
|
||||||
|
task_creation_fn: FN,
|
||||||
|
) -> Pin<Box<Self>> {
|
||||||
|
//SAEFTY is initialized below
|
||||||
|
let mut this = Box::<Self>::new_uninit();
|
||||||
|
let ptr = this.as_mut_ptr();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(&raw mut (*ptr).frames).write(BinaryHeap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(&raw mut (*ptr).od).write(core::ptr::null_mut());
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
(&raw mut (*ptr).state).write(NmtNodeGuardingResState::PreOperational);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = RunningRef(unsafe { NonNull::new_unchecked(ptr) });
|
||||||
|
unsafe {
|
||||||
|
(&raw mut (*ptr).task).write(task_creation_fn(ctx));
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
(&raw mut (*ptr)._pp).write(PhantomPinned);
|
||||||
|
}
|
||||||
|
|
||||||
|
Box::into_pin(unsafe { this.assume_init() })
|
||||||
|
}
|
||||||
|
fn enqueue_frame(self: Pin<&mut Self>, frame: F) {
|
||||||
|
//SAEFTY nothing is moved out
|
||||||
|
let this = unsafe { self.get_unchecked_mut() };
|
||||||
|
this.frames.push(HeapFrame(frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
od: &mut DefaultDictionary<D>,
|
||||||
|
state: NmtNodeGuardingResState,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<(), Error>> {
|
||||||
|
let this = unsafe { self.get_unchecked_mut() };
|
||||||
|
this.od = od;
|
||||||
|
this.state = state;
|
||||||
|
let task = unsafe { Pin::new_unchecked(this.task.as_mut()) };
|
||||||
|
let res = task.poll(cx);
|
||||||
|
this.od = core::ptr::null_mut();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RunningRef<F: Frame, D: ObjectDictionary + 'static>(NonNull<Running<F, D>>);
|
||||||
|
|
||||||
|
impl<F: Frame, D: ObjectDictionary + 'static> RunningRef<F, D> {
|
||||||
|
fn node_state(&self) -> NmtNodeGuardingResState {
|
||||||
|
let running = unsafe { self.0.as_ref() };
|
||||||
|
running.state
|
||||||
|
}
|
||||||
|
fn object_dictionary(&self) -> &DefaultDictionary<D> {
|
||||||
|
let running = unsafe { self.0.as_ref() };
|
||||||
|
let od = unsafe { &*running.od };
|
||||||
|
od
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: Frame, D: ObjectDictionary + 'static> Clone for RunningRef<F, D> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<F: Frame, D: ObjectDictionary + 'static> Copy for RunningRef<F, D> {}
|
||||||
|
|
||||||
|
impl<F: Frame, D: ObjectDictionary + 'static> CanRx for RunningRef<F, D> {
|
||||||
|
type Error = Infallible;
|
||||||
|
type Frame = F;
|
||||||
|
|
||||||
|
fn poll_recv(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<Self::Frame, Self::Error>> {
|
||||||
|
let this = self.get_mut();
|
||||||
|
// SAEFTY pointer is valid because the corresponding `Running` is pinned and not dropped.
|
||||||
|
let running = unsafe { this.0.as_mut() };
|
||||||
|
|
||||||
|
match running.frames.pop() {
|
||||||
|
Some(x) => Poll::Ready(Ok(x.0)),
|
||||||
|
None => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: Frame, D: ObjectDictionary + 'static> ObjectDictionary for RunningRef<F, D> {
|
||||||
|
type Error = object::Error;
|
||||||
|
|
||||||
|
fn read(&self, index: u16, subindex: u8, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||||
|
let running = unsafe { self.0.as_ref() };
|
||||||
|
let od: &DefaultDictionary<D> = unsafe { running.od.as_ref().unwrap() };
|
||||||
|
od.read(index, subindex, buf)
|
||||||
|
}
|
||||||
|
fn write(&mut self, index: u16, subindex: u8, buf: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
let running = unsafe { self.0.as_mut() };
|
||||||
|
let od: &mut DefaultDictionary<D> = unsafe { running.od.as_mut().unwrap() };
|
||||||
|
od.write(index, subindex, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SdoServerCan<CT: CanTx, F: Frame, D: ObjectDictionary + 'static> {
|
||||||
|
run: RunningRef<F, D>,
|
||||||
|
can_tx: CT,
|
||||||
|
server_to_client: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CT: CanTx, F: Frame, D: ObjectDictionary + 'static> sdo::server::CanDriver
|
||||||
|
for SdoServerCan<CT, F, D>
|
||||||
|
{
|
||||||
|
type Error = CT::Error;
|
||||||
|
|
||||||
|
fn send(
|
||||||
|
&mut self,
|
||||||
|
co: impl Into<crate::communication::sdo::SdoCommunicationObjectRes>,
|
||||||
|
) -> impl Future<Output = Result<(), Self::Error>> {
|
||||||
|
let body = co.into().encode();
|
||||||
|
let frame: CT::Frame = body.to_frame::<CT::Frame>(self.server_to_client);
|
||||||
|
self.can_tx.send(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recv(&mut self) -> impl Future<Output = Result<sdo::server::Req, Self::Error>> {
|
||||||
|
core::future::poll_fn(|cx| {
|
||||||
|
let run = Pin::new(&mut self.run);
|
||||||
|
match run.poll_recv(cx) {
|
||||||
|
Poll::Ready(Ok(frame)) => {
|
||||||
|
let body = CanBody::from_frame(&frame);
|
||||||
|
if let Ok(co) = Req::decode(body) {
|
||||||
|
Poll::Ready(Ok(co))
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PdoCan<CT: CanTx> {
|
||||||
|
can_tx: CT,
|
||||||
|
server_to_client: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CT: CanTx> pdo::producer::CanDriver for PdoCan<CT> {
|
||||||
|
type Error = CT::Error;
|
||||||
|
|
||||||
|
fn send(&mut self, data: [u8; 8]) -> impl Future<Output = Result<(), Self::Error>> {
|
||||||
|
let frame = CT::Frame::new(self.server_to_client, &data).unwrap();
|
||||||
|
self.can_tx.send(frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use embedded_can::Frame;
|
||||||
|
|
||||||
|
use super::CanOpenNode;
|
||||||
|
use crate::{
|
||||||
|
CobId, NodeId,
|
||||||
|
can::CanTx,
|
||||||
|
communication::sdo::client::upload,
|
||||||
|
node::DefaultDictionary,
|
||||||
|
object::dictionary::ObjectDictionary,
|
||||||
|
test::{AsyncIoTimer, CanFrame, SdoClientCan},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TestOd;
|
||||||
|
|
||||||
|
impl ObjectDictionary for TestOd {
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
fn read(&self, index: u16, subindex: u8, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||||
|
match (index, subindex) {
|
||||||
|
(0x1000, 0x00) => {
|
||||||
|
buf[0] = 0xFF;
|
||||||
|
Ok(1)
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, index: u16, subindex: u8, buf: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nmt() {
|
||||||
|
let (mut can0, can1) = crate::test::CanChannel::new();
|
||||||
|
let od: DefaultDictionary<TestOd> = DefaultDictionary::new(NodeId(1), TestOd::default());
|
||||||
|
let timer = AsyncIoTimer;
|
||||||
|
|
||||||
|
let node = CanOpenNode::new(NodeId(1), can1.clone(), can1, od, timer);
|
||||||
|
|
||||||
|
let mut client_can = SdoClientCan {
|
||||||
|
channel: can0.clone(),
|
||||||
|
server_to_client: CobId::default_sdo_tx(NodeId(1)),
|
||||||
|
client_to_server: CobId::default_sdo_rx(NodeId(1)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let task = smol::spawn(async move {
|
||||||
|
if node.await.is_err() {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
smol::block_on(async move {
|
||||||
|
let mut timer = AsyncIoTimer;
|
||||||
|
let mut buf = [0u8; 1024];
|
||||||
|
let Ok(buf_size) = upload(0x1000, 0x00, &mut buf, &mut client_can, &mut timer).await
|
||||||
|
else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
dbg!(&buf[..buf_size]);
|
||||||
|
|
||||||
|
can0.send(CanFrame::new(CobId::nmt(), &[127, 1]).unwrap())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
task.await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct BinarySearchMap<K, V>(Vec<(K, V)>);
|
||||||
|
|
||||||
|
impl<K: Ord, V> Default for BinarySearchMap<K, V> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Ord, V> BinarySearchMap<K, V> {
|
||||||
|
fn get(&self, key: &K) -> Option<&V> {
|
||||||
|
match self.0.binary_search_by(|x| x.0.cmp(&key)) {
|
||||||
|
Ok(x) => Some(&self.0[x].1),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_mut(&mut self, key: &K) -> Option<&mut V> {
|
||||||
|
match self.0.binary_search_by(|x| x.0.cmp(&key)) {
|
||||||
|
Ok(x) => Some(&mut self.0[x].1),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn insert(&mut self, key: K, value: V) -> Option<V> {
|
||||||
|
match self.0.binary_search_by(|x| x.0.cmp(&key)) {
|
||||||
|
Ok(x) => {
|
||||||
|
let mut item = (key, value);
|
||||||
|
core::mem::swap(&mut item, &mut self.0[x]);
|
||||||
|
Some(item.1)
|
||||||
|
}
|
||||||
|
Err(x) => {
|
||||||
|
self.0.insert(x, (key, value));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn remove(&mut self, key: &K) -> Option<V> {
|
||||||
|
match self.0.binary_search_by(|x| x.0.cmp(&key)) {
|
||||||
|
Ok(x) => Some(self.0.remove(x).1),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retain<F: FnMut(&K, &mut V) -> bool>(&mut self, mut f: F) {
|
||||||
|
self.0.retain_mut(|x| (f)(&x.0, &mut x.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
|
||||||
|
self.0.iter().map(|x| (&x.0, &x.1))
|
||||||
|
}
|
||||||
|
fn values(&self) -> impl Iterator<Item = &V> {
|
||||||
|
self.0.iter().map(|x| &x.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
77
canopen/src/object.rs
Normal file
77
canopen/src/object.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use crate::communication::sdo::SdoAbortCode;
|
||||||
|
|
||||||
|
pub mod cia301;
|
||||||
|
pub mod dictionary;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Error {
|
||||||
|
UnsupportedAccess,
|
||||||
|
ReadToWriteOnly,
|
||||||
|
WriteToReadOnly,
|
||||||
|
ObjectNotFound,
|
||||||
|
ObjectCannotBeMapped,
|
||||||
|
WouldExceedPdoLength,
|
||||||
|
ParameterIncompatibility,
|
||||||
|
InternalIncompatibility,
|
||||||
|
DataTypeMismatch,
|
||||||
|
SubobjectNotFound,
|
||||||
|
InvalidValue,
|
||||||
|
General,
|
||||||
|
OutOfMemory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<core::convert::Infallible> for Error {
|
||||||
|
fn from(_value: core::convert::Infallible) -> Self {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for SdoAbortCode {
|
||||||
|
fn from(val: Error) -> Self {
|
||||||
|
match val {
|
||||||
|
Error::UnsupportedAccess => Self::UnsupportedAccess,
|
||||||
|
Error::ReadToWriteOnly => Self::ReadToWriteOnly,
|
||||||
|
Error::WriteToReadOnly => Self::WriteToReadOnly,
|
||||||
|
Error::ObjectNotFound => Self::ObjectDoesNotExist,
|
||||||
|
Error::ObjectCannotBeMapped => Self::ObjectCannotBeMapped,
|
||||||
|
Error::WouldExceedPdoLength => Self::WouldExceedPdoLength,
|
||||||
|
Error::ParameterIncompatibility => Self::ParameterIncompatible,
|
||||||
|
Error::DataTypeMismatch => Self::DataTypeMismatch,
|
||||||
|
Error::SubobjectNotFound => Self::SubIndexDoesNotExist,
|
||||||
|
Error::InvalidValue => Self::InvalidValue,
|
||||||
|
Error::General => Self::General,
|
||||||
|
Error::OutOfMemory => Self::OutOfMemory,
|
||||||
|
Error::InternalIncompatibility => Self::InternalIncompatible,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Object {
|
||||||
|
const INDEX: u16;
|
||||||
|
|
||||||
|
fn write(&mut self, subindex: u8, buf: &[u8]) -> Result<(), Error>;
|
||||||
|
fn read(&self, subindex: u8, buf: &mut [u8]) -> Result<usize, Error>;
|
||||||
|
fn pdo_mapping(&self, subindex: u8) -> PdoMappingKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum Access {
|
||||||
|
ReadOnly,
|
||||||
|
ReadWrite,
|
||||||
|
WriteOnly,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum PdoMappingKind {
|
||||||
|
/// object may be mapped into a PDO
|
||||||
|
Optional,
|
||||||
|
/// object is part of the default mapping
|
||||||
|
Default,
|
||||||
|
/// object may be mapped into a TPDO and shall not be mapped into a RPDO
|
||||||
|
Tpdo,
|
||||||
|
/// object may be mapped into a RPDO and shall not be mapped into a TPDO
|
||||||
|
Rpdo,
|
||||||
|
/// object shall not be mapped into a PDO
|
||||||
|
No,
|
||||||
|
}
|
||||||
1097
canopen/src/object/cia301.rs
Normal file
1097
canopen/src/object/cia301.rs
Normal file
File diff suppressed because it is too large
Load diff
8
canopen/src/object/dictionary.rs
Normal file
8
canopen/src/object/dictionary.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
use crate::communication::sdo::SdoAbortCode;
|
||||||
|
|
||||||
|
pub trait ObjectDictionary {
|
||||||
|
type Error: Into<SdoAbortCode> + Into<super::Error>;
|
||||||
|
fn read(&self, index: u16, subindex: u8, buf: &mut [u8]) -> Result<usize, Self::Error>;
|
||||||
|
|
||||||
|
fn write(&mut self, index: u16, subindex: u8, buf: &[u8]) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
145
canopen/src/test.rs
Normal file
145
canopen/src/test.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
use async_channel::{Receiver, Sender};
|
||||||
|
use core::pin::Pin;
|
||||||
|
use core::task::{Context, Poll};
|
||||||
|
use embedded_can::{Frame, Id};
|
||||||
|
use futures::Stream;
|
||||||
|
|
||||||
|
use crate::can::{CanBody, CanRx, CanTx};
|
||||||
|
use crate::communication::sdo::{self, SdoCommunicationObjectRes};
|
||||||
|
use crate::communication::{DecodeCommunicationObject, EncodeCommunicationObject};
|
||||||
|
use crate::timer::Timer;
|
||||||
|
|
||||||
|
pub struct CanFrame {
|
||||||
|
id: Id,
|
||||||
|
body: CanBody,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame for CanFrame {
|
||||||
|
fn new(id: impl Into<embedded_can::Id>, data: &[u8]) -> Option<Self> {
|
||||||
|
Some(Self {
|
||||||
|
id: id.into(),
|
||||||
|
body: CanBody::from_slice(data)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_remote(id: impl Into<embedded_can::Id>, dlc: usize) -> Option<Self> {
|
||||||
|
Some(Self {
|
||||||
|
id: id.into(),
|
||||||
|
body: CanBody::new_remote(dlc)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_extended(&self) -> bool {
|
||||||
|
matches!(self.id, Id::Extended(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_remote_frame(&self) -> bool {
|
||||||
|
self.body.is_remote_frame()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> embedded_can::Id {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dlc(&self) -> usize {
|
||||||
|
match &self.body {
|
||||||
|
CanBody::Data(data) => data.data.len(),
|
||||||
|
CanBody::RemoteTransmissionRequest(rtr) => rtr.size as _,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> &[u8] {
|
||||||
|
match &self.body {
|
||||||
|
CanBody::Data(data) => &data.data,
|
||||||
|
CanBody::RemoteTransmissionRequest(_) => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CanChannel {
|
||||||
|
tx: Sender<CanFrame>,
|
||||||
|
rx: Receiver<CanFrame>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanChannel {
|
||||||
|
pub fn new() -> (Self, Self) {
|
||||||
|
let (tx1, rx1) = async_channel::unbounded();
|
||||||
|
let (tx2, rx2) = async_channel::unbounded();
|
||||||
|
|
||||||
|
(Self { tx: tx1, rx: rx2 }, Self { tx: tx2, rx: rx1 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanTx for CanChannel {
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
type Frame = CanFrame;
|
||||||
|
|
||||||
|
async fn send(&mut self, frame: Self::Frame) -> Result<(), Self::Error> {
|
||||||
|
self.tx.send(frame).await.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanRx for CanChannel {
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
type Frame = CanFrame;
|
||||||
|
|
||||||
|
fn poll_recv(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<Self::Frame, Self::Error>> {
|
||||||
|
let rx = unsafe { self.map_unchecked_mut(|x| &mut x.rx) };
|
||||||
|
|
||||||
|
match rx.poll_next(cx) {
|
||||||
|
Poll::Ready(x) => Poll::Ready(Ok(x.unwrap())),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AsyncIoTimer;
|
||||||
|
|
||||||
|
impl Timer for AsyncIoTimer {
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
async fn delay_ms(&mut self, millis: usize) -> Result<(), Self::Error> {
|
||||||
|
async_io::Timer::after(core::time::Duration::from_millis(millis as _)).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SdoClientCan {
|
||||||
|
pub(crate) channel: CanChannel,
|
||||||
|
pub(crate) client_to_server: Id,
|
||||||
|
pub(crate) server_to_client: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sdo::client::CanDriver for SdoClientCan {
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
fn send(
|
||||||
|
&mut self,
|
||||||
|
co: impl Into<crate::communication::sdo::SdoCommunicationObjectReq>,
|
||||||
|
) -> impl Future<Output = Result<(), Self::Error>> {
|
||||||
|
self.channel
|
||||||
|
.send(co.into().encode().to_frame(self.client_to_server))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn recv(
|
||||||
|
&self,
|
||||||
|
) -> Result<crate::communication::sdo::SdoCommunicationObjectRes, Self::Error> {
|
||||||
|
loop {
|
||||||
|
let frame = self.channel.rx.recv().await.unwrap();
|
||||||
|
let body = CanBody::from_frame(&frame);
|
||||||
|
if frame.id() == self.server_to_client {
|
||||||
|
if let Ok(co) = SdoCommunicationObjectRes::decode(body) {
|
||||||
|
return Ok(co);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
canopen/src/timer.rs
Normal file
28
canopen/src/timer.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use core::future::{Future, poll_fn};
|
||||||
|
use core::task::Poll;
|
||||||
|
|
||||||
|
pub trait Timer {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn delay_ms(&mut self, millis: usize) -> impl Future<Output = Result<(), Self::Error>>;
|
||||||
|
|
||||||
|
fn timeout_after_ms<F: Future>(
|
||||||
|
&mut self,
|
||||||
|
millis: usize,
|
||||||
|
future: F,
|
||||||
|
) -> impl Future<Output = Result<Option<F::Output>, Self::Error>> {
|
||||||
|
async move {
|
||||||
|
let mut delay = core::pin::pin!(self.delay_ms(millis));
|
||||||
|
let mut future = core::pin::pin!(future);
|
||||||
|
poll_fn(|cx| match delay.as_mut().poll(cx) {
|
||||||
|
Poll::Ready(Ok(())) => Poll::Ready(Ok(None)),
|
||||||
|
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
|
||||||
|
Poll::Pending => match future.as_mut().poll(cx) {
|
||||||
|
Poll::Ready(x) => Poll::Ready(Ok(Some(x))),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue