Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions integration_tests/device_configs/example1.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
device_name = "Example 1"
hardware_version = "v1.2.3"
software_version = "v2.1.0"
autostart = "disabled"

[identity]
vendor_id = 1234
Expand Down
2 changes: 2 additions & 0 deletions integration_tests/device_configs/example2_bootloader_app.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# A simple system with a bootloader
device_name = "Bootload Example"

autostart = "enabled"

[pdos]
num_rpdo = 4
num_tpdo = 4
Expand Down
2 changes: 2 additions & 0 deletions integration_tests/device_configs/example3_bootloader.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# A simple config for a bootloader
device_name = "Bootload Example"

autostart = "unsupported"

[pdos]
num_rpdo = 4
num_tpdo = 4
Expand Down
15 changes: 15 additions & 0 deletions integration_tests/tests/codegen_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! Tests that only validate the generated code
//!

use integration_tests::{object_dict1, object_dict2, object_dict3};
use zencan_node::object_dict::find_object;

#[test]
fn test_autostart_defaults() {
// Example 1 should default to disabled
assert_eq!(0, object_dict1::OBJECT5000.get_value());
// Example 2 should default to enabled
assert_eq!(1, object_dict2::OBJECT5000.get_value());
// Example 3 should have no object 5000
assert!(find_object(&object_dict3::OD_TABLE, 0x5000).is_none())
}
47 changes: 42 additions & 5 deletions zencan-common/src/device_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
//! device_name = "can-io"
//! software_version = "v0.0.1"
//! hardware_version = "rev1"
//!
//! # How frequently to send heartbeat messages (ms)
//! heartbeat_period = 1000
//!
//! # Sets the default value of the Auto-start object
//! autostart = "enabled"
//!
//! # Define 3 out of 4 device unique identifiers. These define the application/device, the fourth is
//! # the serial number, which must be provided at run-time by the application.
//! [identity]
Expand Down Expand Up @@ -172,7 +177,7 @@ pub enum LoadError {
}

fn mandatory_objects(config: &DeviceConfig) -> Vec<ObjectDefinition> {
vec![
let mut objects = vec![
ObjectDefinition {
index: 0x1000,
parameter_name: "Device Type".to_string(),
Expand Down Expand Up @@ -300,19 +305,29 @@ fn mandatory_objects(config: &DeviceConfig) -> Vec<ObjectDefinition> {
],
}),
},
ObjectDefinition {
];

let (create_autostart, default) = match config.autostart {
AutoStartConfig::Disabled => (true, 0),
AutoStartConfig::Enabled => (true, 1),
AutoStartConfig::Unsupported => (false, 0),
};
if create_autostart {
objects.push(ObjectDefinition {
index: 0x5000,
parameter_name: "Auto Start".to_string(),
application_callback: false,
object: Object::Var(VarDefinition {
data_type: DataType::UInt8,
access_type: AccessType::Rw.into(),
default_value: None,
default_value: Some(DefaultValue::Integer(default)),
pdo_mapping: PdoMappable::None,
persist: true,
}),
},
]
});
}

objects
}

fn pdo_objects(num_rpdo: usize, num_tpdo: usize) -> Vec<ObjectDefinition> {
Expand Down Expand Up @@ -519,6 +534,19 @@ fn default_true() -> bool {
true
}

/// Options for Autostart config
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AutoStartConfig {
/// Autostart is supported, but defaults to off
#[default]
Disabled,
/// Autostart defaults to enabled
Enabled,
/// Autostart is not supported -- no 0x5000 object will be created
Unsupported,
}

/// Represents the configuration parameters for a single PDO
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct PdoDefaultConfig {
Expand Down Expand Up @@ -656,6 +684,15 @@ pub struct DeviceConfig {
/// The name describing the type of device (e.g. a model)
pub device_name: String,

/// Configures support for the AutoStart (0x5000) object and its default value
///
/// Allowed values:
/// - 'unsupported': No autostart object is created
/// - 'disabled': An autostart object is created, and it defaults to disabled
/// - 'enabled': An autostart object is created, and it defaults to enabled
#[serde(default)]
pub autostart: AutoStartConfig,

/// Enables object storage commands (object 0x1010)
///
/// Default: true
Expand Down
2 changes: 1 addition & 1 deletion zencan-common/src/lss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ impl LssState {
/// number must be set by the application to a unique value. This can be done, e.g., using a UID
/// register on the MCU, or by loading a previously programmed value from flash. It is important
/// that each device on the bus have a unique identity.
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct LssIdentity {
/// A number indicating the vendor of the device
pub vendor_id: u32,
Expand Down
3 changes: 2 additions & 1 deletion zencan-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Human-friendly documentation of releases and what's changed in them for the zenc

### Added

- Support for TimeOfDay, TimeDifference, f64, u64, i64 object data types.
- Support for TimeOfDay, TimeDifference, f64, u64, i64 object data types (#42).
- Device config `autostart` field for configuring 0x5000 object default value (#47).

### Fixed

Expand Down
93 changes: 91 additions & 2 deletions zencan-node/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl<'a> Node<'a> {
let message_count = 0;
let sdo_server = SdoServer::new();
let lss_slave = LssSlave::new(LssConfig {
identity: read_identity(od).unwrap(),
identity: read_identity(od).unwrap_or_default(),
node_id,
store_supported: false,
});
Expand Down Expand Up @@ -463,7 +463,7 @@ impl<'a> Node<'a> {
fn boot_up(&mut self) {
// Reset the LSS slave with the new ID
self.lss_slave.update_config(LssConfig {
identity: read_identity(self.od).unwrap(),
identity: read_identity(self.od).unwrap_or_default(),
node_id: self.node_id,
store_supported: self.callbacks.store_node_config.is_some(),
});
Expand All @@ -488,3 +488,92 @@ impl<'a> Node<'a> {
}
}
}

#[cfg(test)]
mod tests {
use zencan_common::{
nmt::NmtState,
objects::{ObjectCode, SubInfo},
CanMessage, NodeId,
};

use crate::{
object_dict::{ODEntry, ProvidesSubObjects, ScalarField, SubObjectAccess},
priority_queue::PriorityQueue,
Callbacks, Node, NodeMbox, NodeState,
};

struct AutoStartObject {
value: ScalarField<u8>,
}

impl AutoStartObject {
pub fn new(value: u8) -> Self {
Self {
value: ScalarField::<u8>::new(value),
}
}
}
impl ProvidesSubObjects for AutoStartObject {
fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)> {
match sub {
0 => Some((SubInfo::new_u8(), &self.value)),
_ => None,
}
}

fn object_code(&self) -> ObjectCode {
ObjectCode::Var
}
}

#[test]
fn test_node_autostart_enabled() {
let object5000 = Box::leak(Box::new(AutoStartObject::new(1)));
let od_table = Box::leak(Box::new([ODEntry {
index: 0x5000,
data: object5000,
}]));

let tx_queue = Box::leak(Box::new(PriorityQueue::<4, CanMessage>::new()));
let sdo_buffer = Box::leak(Box::new([0u8; 100]));
let mbox = Box::leak(Box::new(NodeMbox::new(&[], &[], tx_queue, sdo_buffer)));
let state = Box::leak(Box::new(NodeState::new(&[], &[])));

let mut node = Node::new(
NodeId::new(1).unwrap(),
Callbacks::default(),
mbox,
state,
od_table,
);

node.process(0);
assert_eq!(NmtState::Operational, node.nmt_state());
}

#[test]
fn test_node_autostart_disabled() {
let object5000 = Box::leak(Box::new(AutoStartObject::new(0)));
let od_table = Box::leak(Box::new([ODEntry {
index: 0x5000,
data: object5000,
}]));

let tx_queue = Box::leak(Box::new(PriorityQueue::<4, CanMessage>::new()));
let sdo_buffer = Box::leak(Box::new([0u8; 100]));
let mbox = Box::leak(Box::new(NodeMbox::new(&[], &[], tx_queue, sdo_buffer)));
let state = Box::leak(Box::new(NodeState::new(&[], &[])));

let mut node = Node::new(
NodeId::new(1).unwrap(),
Callbacks::default(),
mbox,
state,
od_table,
);

node.process(0);
assert_eq!(NmtState::PreOperational, node.nmt_state());
}
}