pub const LSS_MASTER_COB_ID: u16 = 0x7E5;
pub const LSS_SLAVE_COB_ID: u16 = 0x7E4;
pub type LssFrame = [u8; 8];
pub const UNCONFIGURED_NODE_ID: u8 = 0xFF;
const CS_SWITCH_GLOBAL: u8 = 0x04;
const CS_CONFIGURE_NODE_ID: u8 = 0x11;
const CS_STORE_CONFIGURATION: u8 = 0x17;
const CS_SWITCH_SELECTIVE_VENDOR: u8 = 0x40;
const CS_SWITCH_SELECTIVE_PRODUCT: u8 = 0x41;
const CS_SWITCH_SELECTIVE_REVISION: u8 = 0x42;
const CS_SWITCH_SELECTIVE_SERIAL: u8 = 0x43;
const CS_SWITCH_SELECTIVE_RESPONSE: u8 = 0x44;
const CS_INQUIRE_VENDOR: u8 = 0x5A;
const CS_INQUIRE_PRODUCT: u8 = 0x5B;
const CS_INQUIRE_REVISION: u8 = 0x5C;
const CS_INQUIRE_SERIAL: u8 = 0x5D;
const CS_INQUIRE_NODE_ID: u8 = 0x5E;
const MODE_WAITING: u8 = 0;
const MODE_CONFIGURATION: u8 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LssAddress {
pub vendor_id: u32,
pub product_code: u32,
pub revision_number: u32,
pub serial_number: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LssState {
Waiting,
Configuration,
}
fn frame(cs: u8) -> LssFrame {
let mut f = [0u8; 8];
f[0] = cs;
f
}
fn frame_u32(cs: u8, value: u32) -> LssFrame {
let mut f = frame(cs);
f[1..5].copy_from_slice(&value.to_le_bytes());
f
}
fn u32_at(f: &LssFrame) -> u32 {
u32::from_le_bytes([f[1], f[2], f[3], f[4]])
}
pub fn encode_switch_global(configuration: bool) -> LssFrame {
let mut f = frame(CS_SWITCH_GLOBAL);
f[1] = if configuration {
MODE_CONFIGURATION
} else {
MODE_WAITING
};
f
}
pub fn encode_switch_selective(address: &LssAddress) -> [LssFrame; 4] {
[
frame_u32(CS_SWITCH_SELECTIVE_VENDOR, address.vendor_id),
frame_u32(CS_SWITCH_SELECTIVE_PRODUCT, address.product_code),
frame_u32(CS_SWITCH_SELECTIVE_REVISION, address.revision_number),
frame_u32(CS_SWITCH_SELECTIVE_SERIAL, address.serial_number),
]
}
pub fn encode_configure_node_id(node_id: u8) -> LssFrame {
let mut f = frame(CS_CONFIGURE_NODE_ID);
f[1] = node_id;
f
}
pub fn encode_store() -> LssFrame {
frame(CS_STORE_CONFIGURATION)
}
pub fn encode_inquire_node_id() -> LssFrame {
frame(CS_INQUIRE_NODE_ID)
}
pub fn is_switch_selective_response(response: &LssFrame) -> bool {
response[0] == CS_SWITCH_SELECTIVE_RESPONSE
}
pub fn decode_configure_node_id_response(response: &LssFrame) -> Option<Result<(), u8>> {
if response[0] != CS_CONFIGURE_NODE_ID {
return None;
}
Some(if response[1] == 0 {
Ok(())
} else {
Err(response[1])
})
}
pub fn decode_inquire_node_id_response(response: &LssFrame) -> Option<u8> {
(response[0] == CS_INQUIRE_NODE_ID).then_some(response[1])
}
#[derive(Debug)]
pub struct LssSlave {
address: LssAddress,
node_id: u8,
pending_node_id: u8,
state: LssState,
selective: u8,
}
impl LssSlave {
pub const fn new(address: LssAddress, node_id: u8) -> Self {
Self {
address,
node_id,
pending_node_id: node_id,
state: LssState::Waiting,
selective: 0,
}
}
pub const fn node_id(&self) -> u8 {
self.node_id
}
pub const fn pending_node_id(&self) -> u8 {
self.pending_node_id
}
pub const fn state(&self) -> LssState {
self.state
}
pub const fn address(&self) -> LssAddress {
self.address
}
pub fn adopt_pending(&mut self) {
self.node_id = self.pending_node_id;
}
pub fn handle(&mut self, req: &LssFrame) -> Option<LssFrame> {
let configuring = self.state == LssState::Configuration;
match req[0] {
CS_SWITCH_GLOBAL => {
self.selective = 0;
self.state = if req[1] == MODE_CONFIGURATION {
LssState::Configuration
} else {
LssState::Waiting
};
None
}
CS_SWITCH_SELECTIVE_VENDOR => {
self.selective = u8::from(u32_at(req) == self.address.vendor_id);
None
}
CS_SWITCH_SELECTIVE_PRODUCT => {
self.selective = if self.selective == 1 && u32_at(req) == self.address.product_code
{
2
} else {
0
};
None
}
CS_SWITCH_SELECTIVE_REVISION => {
self.selective =
if self.selective == 2 && u32_at(req) == self.address.revision_number {
3
} else {
0
};
None
}
CS_SWITCH_SELECTIVE_SERIAL => {
if self.selective == 3 && u32_at(req) == self.address.serial_number {
self.selective = 0;
self.state = LssState::Configuration;
Some(frame(CS_SWITCH_SELECTIVE_RESPONSE))
} else {
self.selective = 0;
None
}
}
CS_CONFIGURE_NODE_ID if configuring => {
let id = req[1];
let error = if (1..=127).contains(&id) || id == UNCONFIGURED_NODE_ID {
self.pending_node_id = id;
0
} else {
1 };
let mut f = frame(CS_CONFIGURE_NODE_ID);
f[1] = error;
Some(f)
}
CS_STORE_CONFIGURATION if configuring => Some(frame(CS_STORE_CONFIGURATION)),
CS_INQUIRE_NODE_ID if configuring => {
let mut f = frame(CS_INQUIRE_NODE_ID);
f[1] = self.node_id;
Some(f)
}
CS_INQUIRE_VENDOR if configuring => {
Some(frame_u32(CS_INQUIRE_VENDOR, self.address.vendor_id))
}
CS_INQUIRE_PRODUCT if configuring => {
Some(frame_u32(CS_INQUIRE_PRODUCT, self.address.product_code))
}
CS_INQUIRE_REVISION if configuring => {
Some(frame_u32(CS_INQUIRE_REVISION, self.address.revision_number))
}
CS_INQUIRE_SERIAL if configuring => {
Some(frame_u32(CS_INQUIRE_SERIAL, self.address.serial_number))
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn address() -> LssAddress {
LssAddress {
vendor_id: 0x0000_001F,
product_code: 0x0000_002A,
revision_number: 0x0000_0001,
serial_number: 0x0000_1234,
}
}
fn slave() -> LssSlave {
LssSlave::new(address(), UNCONFIGURED_NODE_ID)
}
#[test]
fn switch_global_frames() {
assert_eq!(encode_switch_global(true), [0x04, 0x01, 0, 0, 0, 0, 0, 0]);
assert_eq!(encode_switch_global(false), [0x04, 0x00, 0, 0, 0, 0, 0, 0]);
}
#[test]
fn configure_node_id_frame() {
assert_eq!(
encode_configure_node_id(0x7F),
[0x11, 0x7F, 0, 0, 0, 0, 0, 0]
);
}
#[test]
fn switch_selective_sequence() {
let f = encode_switch_selective(&address());
assert_eq!(f[0], [0x40, 0x1F, 0x00, 0x00, 0x00, 0, 0, 0]); assert_eq!(f[3], [0x43, 0x34, 0x12, 0x00, 0x00, 0, 0, 0]); }
#[test]
fn switch_global_changes_state() {
let mut s = slave();
assert_eq!(s.state(), LssState::Waiting);
assert!(s.handle(&encode_switch_global(true)).is_none());
assert_eq!(s.state(), LssState::Configuration);
s.handle(&encode_switch_global(false));
assert_eq!(s.state(), LssState::Waiting);
}
#[test]
fn selective_switch_matches_address() {
let mut s = slave();
let seq = encode_switch_selective(&address());
assert!(s.handle(&seq[0]).is_none());
assert!(s.handle(&seq[1]).is_none());
assert!(s.handle(&seq[2]).is_none());
let resp = s.handle(&seq[3]).expect("selective response");
assert!(is_switch_selective_response(&resp));
assert_eq!(s.state(), LssState::Configuration);
}
#[test]
fn selective_switch_rejects_wrong_address() {
let mut s = slave();
let mut seq = encode_switch_selective(&address());
seq[3][1] = 0xFF; for f in &seq {
assert!(s.handle(f).is_none());
}
assert_eq!(s.state(), LssState::Waiting);
}
#[test]
fn configure_node_id_only_in_configuration() {
let mut s = slave();
assert!(s.handle(&encode_configure_node_id(0x20)).is_none());
s.handle(&encode_switch_global(true));
let resp = s.handle(&encode_configure_node_id(0x20)).unwrap();
assert_eq!(decode_configure_node_id_response(&resp), Some(Ok(())));
assert_eq!(s.pending_node_id(), 0x20);
assert_eq!(s.node_id(), UNCONFIGURED_NODE_ID); s.adopt_pending();
assert_eq!(s.node_id(), 0x20);
}
#[test]
fn configure_node_id_rejects_out_of_range() {
let mut s = slave();
s.handle(&encode_switch_global(true));
let resp = s.handle(&encode_configure_node_id(200)).unwrap();
assert_eq!(decode_configure_node_id_response(&resp), Some(Err(1)));
}
#[test]
fn inquiries_report_identity_in_configuration() {
let mut s = slave();
s.handle(&encode_switch_global(true));
let node = s.handle(&encode_inquire_node_id()).unwrap();
assert_eq!(
decode_inquire_node_id_response(&node),
Some(UNCONFIGURED_NODE_ID)
);
let vendor = s.handle(&frame(CS_INQUIRE_VENDOR)).unwrap();
assert_eq!(vendor, [0x5A, 0x1F, 0x00, 0x00, 0x00, 0, 0, 0]);
}
#[test]
fn store_acknowledges() {
let mut s = slave();
s.handle(&encode_switch_global(true));
assert_eq!(
s.handle(&encode_store()).unwrap(),
[0x17, 0, 0, 0, 0, 0, 0, 0]
);
}
}