├── .github ├── pull_request_template.md └── workflows │ └── build-and-test.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── driver.rs ├── frame.rs ├── ldf.rs ├── lib.rs └── master.rs /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Check the following: 2 | 3 | - [ ] Breaking changes marked in commit message 4 | - [ ] Changelog updated 5 | - [ ] Tests added 6 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Check and build rust library 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | check-and-build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Stable with rustfmt and clippy 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: 1.57 21 | components: rustfmt, clippy 22 | - run: cargo fmt -- --check 23 | - run: cargo build 24 | - run: cargo test 25 | - run: cargo clippy -- -D warnings 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This project follows [semantic versioning](https://semver.org/). 4 | 5 | ## [Unreleased] 6 | * breaking: `Pid::new` returns `Result` instead of asserting in case of an error. 7 | ([#37](https://github.com/Sensirion/lin-bus-rs/pull/37)) 8 | 9 | ## [0.4.0] (2021-12-16) 10 | 11 | * changed: `PID::new`, `PID::from_id`, `PCI::new_sf` and `PCI::get_type` are 12 | now `const fn`s. ([#33](https://github.com/Sensirion/lin-bus-rs/pull/33)) 13 | * changed: Use Rust 2021 edition 14 | ([#34](https://github.com/Sensirion/lin-bus-rs/pull/34)) 15 | * breaking: Minimal required Rust version changed to 1.57.0 16 | 17 | ## [0.3.2] (2021-10-28) 18 | 19 | * added: `PID::new` to construct PID's with a known value 20 | ([#31](https://github.com/Sensirion/lin-bus-rs/pull/31)) 21 | 22 | ## [0.3.1] (2019-08-09) 23 | 24 | * added: `NodeAttributes` struct and helper functions to generate diagnostic 25 | frames from it. ([#23](https://github.com/Sensirion/lin-bus-rs/pull/23)) 26 | * added: `SerialNumber` and `ProductId` definitions and decode support 27 | ([#24](https://github.com/Sensirion/lin-bus-rs/pull/24)) 28 | * fixed: Bug in `PCI::get_length` where the length would be returned wrong 29 | ([#26](https://github.com/Sensirion/lin-bus-rs/pull/26)) 30 | 31 | ## [0.3.0] (2019-07-15) 32 | 33 | * changed: Declare some functions as `const fn` 34 | ([#19](https://github.com/Sensirion/lin-bus-rs/pull/19)) 35 | * changed: Moved `PID` and `Frame` into separat module 36 | ([#20](https://github.com/Sensirion/lin-bus-rs/pull/20)) 37 | * added: Support for transport layer and diagnostic frames 38 | ([#20](https://github.com/Sensirion/lin-bus-rs/pull/20)) 39 | 40 | ## [0.2.1] (2019-05-06) 41 | 42 | * fixed: Decoding of frame which uses last bit 43 | ([#15](https://github.com/Sensirion/lin-bus-rs/pull/15)) 44 | 45 | ## [0.2.0] (2019-04-18) 46 | 47 | * changed: Use Rust 2018 edition syntax 48 | ([#13](https://github.com/Sensirion/lin-bus-rs/pull/13)) 49 | * changed: Use classic checksum on special frames. Adds 50 | `PID::uses_classic_checksum` and `PID::get_id`. 51 | 52 | ## [0.1.1] (2018-07-04) 53 | 54 | * changed: Derive Copy, Clone, PartialEq and Eq for Error enum 55 | ([#11](https://github.com/Sensirion/lin-bus-rs/pull/11)) 56 | 57 | ## 0.1.0 (2018-06-25) 58 | 59 | * First crates.io release 60 | 61 | [Unreleased]: https://github.com/Sensirion/lin-bus-rs/compare/v0.4.0...HEAD 62 | [0.4.0]: https://github.com/Sensirion/lin-bus-rs/compare/v0.3.2...v0.4.0 63 | [0.3.2]: https://github.com/Sensirion/lin-bus-rs/compare/v0.3.1...v0.3.2 64 | [0.3.1]: https://github.com/Sensirion/lin-bus-rs/compare/v0.3.0...v0.3.1 65 | [0.3.0]: https://github.com/Sensirion/lin-bus-rs/compare/v0.2.1...v0.3.0 66 | [0.2.1]: https://github.com/Sensirion/lin-bus-rs/compare/v0.2.0...v0.2.1 67 | [0.2.0]: https://github.com/Sensirion/lin-bus-rs/compare/v0.1.1...v0.2.0 68 | [0.1.1]: https://github.com/Sensirion/lin-bus-rs/compare/v0.1.0...v0.1.1 69 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lin-bus" 3 | version = "0.4.0" 4 | authors = [ 5 | "Raphael Nestler ", 6 | "Andreas Brauchli ", 7 | "Niclas Lind ", 8 | ] 9 | description = "LIN bus driver traits and protocol implementation" 10 | readme = "README.md" 11 | keywords = ["LIN", "local", "interconnect", "network"] 12 | categories = ["no-std", "hardware-support"] 13 | license = "BSD-3-Clause" 14 | repository = "https://github.com/Sensirion/lin-bus-rs" 15 | edition = "2021" 16 | 17 | [dependencies] 18 | bitfield = "^0.13" 19 | 20 | [dependencies.num-traits] 21 | version = "^0.2" 22 | default-features = false 23 | 24 | [dependencies.byteorder] 25 | version = "^1.0" 26 | default-features = false 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Sensirion AG 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Docs.rs](https://docs.rs/lin-bus/badge.svg)](https://docs.rs/lin-bus/) 2 | # lin-bus-rs 3 | 4 | LIN bus driver traits and protocol implementation of the [2.2A specification]. 5 | 6 | Currently only the frame level for reading and writing frames from a master is 7 | supported. 8 | 9 | 10 | [2.2A specification]: https://www.cs-group.de/wp-content/uploads/2016/11/LIN_Specification_Package_2.2A.pdf 11 | -------------------------------------------------------------------------------- /src/driver.rs: -------------------------------------------------------------------------------- 1 | //! Trait for a hardware driver to implement 2 | pub use crate::Error; 3 | use crate::PID; 4 | 5 | pub trait Master { 6 | type Error: Into + From; 7 | fn send_wakeup(&mut self) -> Result<(), Self::Error>; 8 | fn send_header(&mut self, pid: PID) -> Result<(), Self::Error>; 9 | fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>; 10 | fn write(&mut self, data: &[u8]) -> Result<(), Self::Error>; 11 | } 12 | -------------------------------------------------------------------------------- /src/frame.rs: -------------------------------------------------------------------------------- 1 | //! LIN bus frame definitions 2 | 3 | use crate::ldf::NodeAttributes; 4 | use bitfield::BitRange; 5 | use byteorder::{ByteOrder, LittleEndian}; 6 | use core::mem::size_of; 7 | use num_traits::{PrimInt, Unsigned}; 8 | 9 | /// Protected ID which is a 6 bit ID with two parity bits 10 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 11 | #[repr(transparent)] 12 | pub struct PID(u8); 13 | 14 | impl PID { 15 | /// Creates a new PID object with given PID 16 | pub const fn new(pid: u8) -> Result { 17 | let correct_pid = PID::from_id(pid & 0b0011_1111); 18 | if correct_pid.0 == pid { 19 | Ok(correct_pid) 20 | } else { 21 | Err("Invalid parity bits") 22 | } 23 | } 24 | 25 | /// Calculate the PID from an ID. 26 | /// P0 = ID0 ⊕ ID1 ⊕ ID2 ⊕ ID4 27 | /// P1 = ¬(ID1 ⊕ ID3 ⊕ ID4 ⊕ ID5) 28 | pub const fn from_id(id: u8) -> PID { 29 | assert!(id < 64, "ID must be less than 64"); 30 | // count parity bits and check if they are even odd 31 | let p0 = (id & 0b1_0111).count_ones() as u8 & 0b1; 32 | let p1 = ((id & 0b11_1010).count_ones() as u8 + 1) & 0b1; 33 | PID(id | (p0 << 6u8) | (p1 << 7u8)) 34 | } 35 | 36 | /// Return the contained PID 37 | pub const fn get(self) -> u8 { 38 | self.0 39 | } 40 | 41 | /// Return the contained ID 42 | pub const fn get_id(self) -> u8 { 43 | self.0 & 0b0011_1111 44 | } 45 | 46 | /// Return if the associated frame uses the classic checksum (diagnostic IDs 60 and 61 or 47 | /// special use IDs 62, 63) 48 | pub const fn uses_classic_checksum(self) -> bool { 49 | self.get_id() >= 60 50 | } 51 | } 52 | 53 | /// Calculate the LIN V2.1 "enhanced" checksum. It is defined as "The inverted eight bit sum with 54 | /// carry. Eight bit sum with carry is equivalent to sum all values and subtract 255 every time the 55 | /// sum is greater or equal to 256" 56 | pub fn checksum(pid: PID, data: &[u8]) -> u8 { 57 | let sum = data.iter().fold(u16::from(pid.0), |sum, v| { 58 | let sum = sum + u16::from(*v); 59 | if sum >= 256 { 60 | sum - 255 61 | } else { 62 | sum 63 | } 64 | }); 65 | !(sum as u8) 66 | } 67 | 68 | /// Calculate the LIN V1.3 "classic" checksum. It is defined as "Checksum calculation over the data 69 | /// bytes only" 70 | pub fn classic_checksum(data: &[u8]) -> u8 { 71 | checksum(PID(0u8), data) 72 | } 73 | 74 | #[derive(Debug, Eq, PartialEq)] 75 | pub struct Frame { 76 | pub(crate) pid: PID, 77 | pub(crate) buffer: [u8; 9], 78 | pub(crate) data_length: usize, 79 | } 80 | 81 | impl Frame { 82 | /// Creates a LIN frame from the PID and data. Calculates and adds checksum accordingly 83 | pub fn from_data(pid: PID, data: &[u8]) -> Frame { 84 | assert!(data.len() <= 8, "Maximum data is 8 bytes"); 85 | let mut buffer = [0u8; 9]; 86 | buffer[0..data.len()].clone_from_slice(data); 87 | buffer[data.len()] = { 88 | if pid.uses_classic_checksum() { 89 | classic_checksum(&buffer[0..data.len()]) 90 | } else { 91 | checksum(pid, &buffer[0..data.len()]) 92 | } 93 | }; 94 | Frame { 95 | pid, 96 | buffer, 97 | data_length: data.len(), 98 | } 99 | } 100 | 101 | /// Access the data from the frame 102 | pub fn get_data(&self) -> &[u8] { 103 | &self.buffer[0..self.data_length] 104 | } 105 | 106 | /// Decode frame data 107 | pub fn decode(&self, offset: usize, length: usize) -> T 108 | where 109 | T: PrimInt + Unsigned, 110 | u64: BitRange, 111 | { 112 | assert!( 113 | (offset + length) <= self.data_length * 8, 114 | "Not enough data available" 115 | ); 116 | assert!(length <= size_of::() * 8, "Output type not big enough"); 117 | 118 | let num = LittleEndian::read_u64(&self.buffer[0..8]); 119 | num.bit_range(offset + length - 1, offset) 120 | } 121 | 122 | /// Get the checksum from the frame 123 | pub fn get_checksum(&self) -> u8 { 124 | self.buffer[self.data_length] 125 | } 126 | 127 | /// Get the PID from the frame 128 | pub fn get_pid(&self) -> PID { 129 | self.pid 130 | } 131 | 132 | /// Get the serialized bytes to write to the driver 133 | pub fn get_data_with_checksum(&self) -> &[u8] { 134 | &self.buffer[0..=self.data_length] 135 | } 136 | } 137 | 138 | /// Implements the transport layer of LIN. The units that are transported in a transport layer 139 | /// frame are called PDU (Packet Data Unit) 140 | pub mod transport { 141 | use super::{Frame, PID}; 142 | 143 | /// NAD is the address of the slave node being addressed in a request, i.e. only slave nodes 144 | /// have an address. NAD is also used to indicate the source of a response. 145 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 146 | #[repr(transparent)] 147 | pub struct NAD(pub u8); 148 | 149 | /// The PCI (Protocol Control Information) contains the transport layer flow control 150 | /// information. 151 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 152 | #[repr(transparent)] 153 | pub struct PCI(u8); 154 | 155 | /// Type of the `PCI` byte 156 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 157 | pub enum PCIType { 158 | /// Single Frame 159 | SF = 0, 160 | /// First Frame. Start of a multi frame message. 161 | FF = 1, 162 | /// Consecutive Frame. 163 | CF = 2, 164 | /// Invalid PCIType 165 | Invalid, 166 | } 167 | 168 | impl PCI { 169 | /// Create a `PCI` with type `PCIType::SF` and the given length 170 | pub const fn new_sf(length: u8) -> PCI { 171 | assert!(length <= 6, "Maximum length for single frame is 6"); 172 | PCI(length) 173 | } 174 | 175 | /// Get the `PCIType` of the PCI 176 | pub const fn get_type(self) -> PCIType { 177 | match self.0 >> 4 { 178 | 0 => PCIType::SF, 179 | 1 => PCIType::FF, 180 | 2 => PCIType::CF, 181 | _ => PCIType::Invalid, 182 | } 183 | } 184 | 185 | /// Get the length field of the `PCI` 186 | pub const fn get_length(self) -> u8 { 187 | self.0 & 0x0F 188 | } 189 | } 190 | 191 | /// The Service Identifier (SID) specifies the request that shall be performed by the slave 192 | /// node addressed. 193 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 194 | #[repr(transparent)] 195 | pub struct SID(pub u8); 196 | 197 | /// The Response Service Identifier (RSID) specifies the contents of the response. 198 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 199 | #[repr(transparent)] 200 | pub struct RSID(pub u8); 201 | 202 | /// Create a single frame (CF) PDU 203 | pub fn create_single_frame(pid: PID, nad: NAD, sid: SID, data: &[u8]) -> Frame { 204 | assert!( 205 | !data.is_empty() && data.len() <= 5, 206 | "A single frame must contain between 0 and 5 bytes" 207 | ); 208 | // If a PDU is not completely filled the unused bytes shall be filled with 0xFF. 209 | let mut frame_data = [0xFFu8; 8]; 210 | frame_data[0] = nad.0; 211 | frame_data[1] = PCI::new_sf(data.len() as u8 + 1).0; 212 | frame_data[2] = sid.0; 213 | frame_data[3..data.len() + 3].clone_from_slice(data); 214 | Frame::from_data(pid, &frame_data) 215 | } 216 | } 217 | 218 | /// Implements the LIN diagnostics methods. 219 | pub mod diagnostic { 220 | use super::transport::{create_single_frame, NAD, SID}; 221 | use super::{ByteOrder, Frame, LittleEndian, PID}; 222 | 223 | pub const MASTER_REQUEST_FRAME_ID: u8 = 0x3C; 224 | pub const SLAVE_RESPONSE_FRAME_ID: u8 = 0x3D; 225 | 226 | pub const MASTER_REQUEST_FRAME_PID: PID = PID::from_id(0x3C); 227 | pub const SLAVE_RESPONSE_FRAME_PID: PID = PID::from_id(0x3D); 228 | 229 | pub const READ_BY_IDENTIFIER_SID: SID = SID(0xB2); 230 | 231 | #[repr(u8)] 232 | /// Identifiers used for the Read by identifer 233 | pub enum Identifier { 234 | /// See also `ProductId` 235 | LINProductIdentification, 236 | /// See also `SerialNumber` 237 | SerialNumber, 238 | /// User defined range 32..=63 239 | UserDefined(u8), 240 | /// Reserved range 2..=31 and 64..=255 241 | Reserved(u8), 242 | } 243 | 244 | impl From for Identifier { 245 | fn from(byte: u8) -> Identifier { 246 | match byte { 247 | 0 => Identifier::LINProductIdentification, 248 | 1 => Identifier::SerialNumber, 249 | b @ 32..=63 => Identifier::UserDefined(b), 250 | b => Identifier::Reserved(b), 251 | } 252 | } 253 | } 254 | 255 | impl From for u8 { 256 | fn from(identifier: Identifier) -> u8 { 257 | match identifier { 258 | Identifier::LINProductIdentification => 0, 259 | Identifier::SerialNumber => 1, 260 | Identifier::UserDefined(b) => b, 261 | Identifier::Reserved(b) => b, 262 | } 263 | } 264 | } 265 | 266 | /// Holds the LIN slave node product identification 267 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 268 | pub struct ProductId { 269 | pub supplier_id: u16, 270 | pub function_id: u16, 271 | pub variant: u8, 272 | } 273 | 274 | impl From<&[u8]> for ProductId { 275 | fn from(data: &[u8]) -> ProductId { 276 | assert!(data.len() >= 5, "We require at least 4 data bytes"); 277 | ProductId { 278 | supplier_id: LittleEndian::read_u16(&data[0..2]), 279 | function_id: LittleEndian::read_u16(&data[2..4]), 280 | variant: data[4], 281 | } 282 | } 283 | } 284 | 285 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 286 | #[repr(transparent)] 287 | pub struct SerialNumber(pub u32); 288 | 289 | impl From<&[u8]> for SerialNumber { 290 | fn from(data: &[u8]) -> SerialNumber { 291 | assert!(data.len() >= 4, "We require at least 4 data bytes"); 292 | SerialNumber(LittleEndian::read_u32(data)) 293 | } 294 | } 295 | 296 | /// Create a read by identifier `Frame` from `NodeAttributes` 297 | pub fn create_read_by_identifier_frame_from_node_attributes( 298 | node_attributes: super::NodeAttributes, 299 | identifier: Identifier, 300 | ) -> Frame { 301 | create_read_by_identifier_frame( 302 | node_attributes.initial_nad, 303 | identifier, 304 | node_attributes.product_id.supplier_id, 305 | node_attributes.product_id.function_id, 306 | ) 307 | } 308 | 309 | /// Create a read by identifier frame 310 | pub fn create_read_by_identifier_frame( 311 | nad: NAD, 312 | identifier: Identifier, 313 | supplier_id: u16, 314 | function_id: u16, 315 | ) -> Frame { 316 | create_single_frame( 317 | MASTER_REQUEST_FRAME_PID, 318 | nad, 319 | READ_BY_IDENTIFIER_SID, 320 | &[ 321 | identifier.into(), 322 | (supplier_id & 0xFF) as u8, 323 | (supplier_id >> 8) as u8, 324 | (function_id & 0xFF) as u8, 325 | (function_id >> 8) as u8, 326 | ], 327 | ) 328 | } 329 | 330 | pub fn create_read_lin_product_identification_frame( 331 | node_attributes: super::NodeAttributes, 332 | ) -> Frame { 333 | create_read_by_identifier_frame_from_node_attributes( 334 | node_attributes, 335 | Identifier::LINProductIdentification, 336 | ) 337 | } 338 | 339 | pub fn create_read_serial_number_frame(node_attributes: super::NodeAttributes) -> Frame { 340 | create_read_by_identifier_frame_from_node_attributes( 341 | node_attributes, 342 | Identifier::SerialNumber, 343 | ) 344 | } 345 | } 346 | 347 | #[cfg(test)] 348 | mod tests { 349 | use super::diagnostic::*; 350 | use super::transport::*; 351 | use super::*; 352 | 353 | struct CheckSumTestData<'a> { 354 | pid: PID, 355 | data: &'a [u8], 356 | checksum: u8, 357 | } 358 | 359 | #[test] 360 | fn test_enhanced_checksum() { 361 | let test_data = [ 362 | CheckSumTestData { 363 | pid: PID(0xDD), 364 | data: &[0x01], 365 | checksum: 0x21, 366 | }, 367 | CheckSumTestData { 368 | pid: PID(0x4A), 369 | data: &[0x55, 0x93, 0xE5], 370 | checksum: 0xE6, 371 | }, 372 | CheckSumTestData { 373 | pid: PID(0xBF), 374 | data: &[0x40, 0xFF], 375 | checksum: 0x00, 376 | }, 377 | ]; 378 | for d in &test_data { 379 | assert_eq!(d.checksum, checksum(d.pid, d.data)); 380 | } 381 | } 382 | 383 | #[test] 384 | fn test_classic_checksum() { 385 | let test_data = [ 386 | CheckSumTestData { 387 | pid: PID::from_id(0x3C), 388 | data: &[0x01], 389 | checksum: 0xFE, 390 | }, 391 | CheckSumTestData { 392 | pid: PID::from_id(0x3D), 393 | data: &[0x01], 394 | checksum: 0xFE, 395 | }, 396 | CheckSumTestData { 397 | pid: PID::from_id(0x3d), 398 | data: &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 399 | checksum: 0xDB, 400 | }, 401 | ]; 402 | for d in &test_data { 403 | assert_eq!(d.checksum, classic_checksum(d.data)); 404 | } 405 | } 406 | 407 | #[test] 408 | fn test_pid_new() { 409 | let test_data = [ 410 | (0x64, PID::new(0x64)), 411 | (0xCA, PID::new(0xCA)), 412 | (0x80, PID::new(0x80)), 413 | (0xC1, PID::new(0xC1)), 414 | (0x47, PID::new(0x47)), 415 | (0x61, PID::new(0x61)), 416 | ]; 417 | 418 | for d in &test_data { 419 | assert_eq!(d.0, d.1.unwrap().get()); 420 | } 421 | } 422 | 423 | #[test] 424 | fn test_invalid_pid_new() { 425 | assert_eq!(Err("Invalid parity bits"), PID::new(0x07)); 426 | } 427 | 428 | #[test] 429 | fn test_pid_from_id() { 430 | let test_data = [ 431 | (0, PID(0x80)), 432 | (1, PID(0xC1)), 433 | (2, PID(0x42)), 434 | (25, PID(0x99)), 435 | (27, PID(0x5B)), 436 | (29, PID(0xDD)), 437 | ]; 438 | 439 | for d in &test_data { 440 | let pid = PID::from_id(d.0); 441 | assert_eq!(pid, d.1); 442 | assert_eq!(pid.get_id(), d.0); 443 | } 444 | } 445 | 446 | #[test] 447 | fn test_id_uses_classic_checksum() { 448 | let test_ids: &[u8] = &[0, 1, 59, 60, 63]; 449 | 450 | for i in test_ids { 451 | assert_eq!(PID::from_id(*i).uses_classic_checksum(), *i >= 60); 452 | } 453 | } 454 | 455 | #[test] 456 | #[should_panic] 457 | fn test_pid_from_id_panic() { 458 | PID::from_id(64); 459 | } 460 | 461 | #[test] 462 | fn test_pci() { 463 | let pci = PCI::new_sf(5); 464 | assert_eq!(pci.get_type(), PCIType::SF); 465 | assert_eq!(pci.get_length(), 5); 466 | } 467 | 468 | #[test] 469 | fn test_transport_frame() { 470 | struct TestData { 471 | pid: PID, 472 | nad: transport::NAD, 473 | sid: transport::SID, 474 | data: &'static [u8], 475 | frame_data: [u8; 8], 476 | } 477 | let test_data = [ 478 | TestData { 479 | pid: diagnostic::MASTER_REQUEST_FRAME_PID, 480 | nad: transport::NAD(0x10), 481 | sid: transport::SID(0xB2), 482 | data: &[0x01, 0xB3, 0x00, 0x01, 0x10], 483 | frame_data: [0x10, 0x06, 0xB2, 0x01, 0xB3, 0x00, 0x01, 0x10], 484 | }, 485 | TestData { 486 | pid: diagnostic::SLAVE_RESPONSE_FRAME_PID, 487 | nad: transport::NAD(0x10), 488 | sid: transport::SID(0xB2), 489 | data: &[0x01], 490 | frame_data: [0x10, 0x02, 0xB2, 0x01, 0xFF, 0xFF, 0xFF, 0xFF], 491 | }, 492 | ]; 493 | 494 | for d in &test_data { 495 | let frame = transport::create_single_frame(d.pid, d.nad, d.sid, d.data); 496 | assert_eq!(frame.get_pid(), d.pid); 497 | assert_eq!(frame.get_data(), d.frame_data); 498 | assert_eq!(frame.data_length, 8); 499 | } 500 | } 501 | 502 | #[test] 503 | #[should_panic] 504 | fn test_transport_frame_without_data() { 505 | transport::create_single_frame( 506 | PID::from_id(0x1), 507 | transport::NAD(0x2), 508 | transport::SID(0x03), 509 | &[], 510 | ); 511 | } 512 | 513 | #[test] 514 | #[should_panic] 515 | fn test_transport_frame_with_too_much_data() { 516 | transport::create_single_frame( 517 | PID::from_id(0x1), 518 | transport::NAD(0x2), 519 | transport::SID(0x03), 520 | &[1, 2, 3, 4, 5, 6], 521 | ); 522 | } 523 | 524 | #[test] 525 | fn test_create_read_by_identifier_frame() { 526 | const LIN_ID_SERIAL_REQ_PAYLOAD: &[u8] = &[0x10, 0x06, 0xB2, 0x01, 0xB3, 0x00, 0x01, 0x10]; 527 | 528 | let frame = diagnostic::create_read_by_identifier_frame( 529 | transport::NAD(0x10), 530 | diagnostic::Identifier::SerialNumber, 531 | 0x00B3, 532 | 0x1001, 533 | ); 534 | 535 | assert_eq!(frame.get_pid(), diagnostic::MASTER_REQUEST_FRAME_PID); 536 | assert_eq!(frame.get_data(), LIN_ID_SERIAL_REQ_PAYLOAD); 537 | assert_eq!(frame.data_length, 8); 538 | } 539 | 540 | #[test] 541 | fn test_create_read_by_identifier_frame_from_node_attributes() { 542 | const LIN_ID_SERIAL_REQ_PAYLOAD: &[u8] = &[0x10, 0x06, 0xB2, 0x01, 0xB3, 0x00, 0x01, 0x10]; 543 | let node_attributes = NodeAttributes::with_default_timing( 544 | transport::NAD(0x10), 545 | transport::NAD(0x10), 546 | diagnostic::ProductId { 547 | supplier_id: 0x00B3, 548 | function_id: 0x1001, 549 | variant: 0x00, 550 | }, 551 | ); 552 | 553 | let frame = diagnostic::create_read_by_identifier_frame_from_node_attributes( 554 | node_attributes, 555 | diagnostic::Identifier::SerialNumber, 556 | ); 557 | assert_eq!(frame.get_pid(), diagnostic::MASTER_REQUEST_FRAME_PID); 558 | assert_eq!(frame.get_data(), LIN_ID_SERIAL_REQ_PAYLOAD); 559 | assert_eq!(frame.data_length, 8); 560 | } 561 | 562 | #[test] 563 | fn test_decode_product_id() { 564 | let product_id = ProductId { 565 | supplier_id: 0x00B3, 566 | function_id: 0x1001, 567 | variant: 0x01, 568 | }; 569 | let data = [0xB3, 0x00, 0x01, 0x10, 0x01]; 570 | 571 | assert_eq!(product_id, ProductId::from(&data[..])); 572 | } 573 | 574 | #[test] 575 | fn test_decode_serial_number() { 576 | let serial_number = SerialNumber(190200009); 577 | let data = [0xC9, 0x38, 0x56, 0x0B]; 578 | assert_eq!(serial_number, SerialNumber::from(&data[..])); 579 | } 580 | } 581 | -------------------------------------------------------------------------------- /src/ldf.rs: -------------------------------------------------------------------------------- 1 | //! Contains structs representing data of the LDF file 2 | 3 | use crate::frame::diagnostic::ProductId; 4 | use crate::frame::transport::NAD; 5 | 6 | #[derive(Copy, Clone, Debug, PartialEq)] 7 | #[repr(transparent)] 8 | pub struct P2Min(pub f32); 9 | 10 | impl Default for P2Min { 11 | fn default() -> P2Min { 12 | P2Min(50.0) 13 | } 14 | } 15 | 16 | #[derive(Copy, Clone, Debug, PartialEq)] 17 | #[repr(transparent)] 18 | pub struct STMin(pub f32); 19 | 20 | impl Default for STMin { 21 | fn default() -> STMin { 22 | STMin(0.0) 23 | } 24 | } 25 | 26 | #[derive(Copy, Clone, Debug, PartialEq)] 27 | #[repr(transparent)] 28 | pub struct NAsTimeout(pub f32); 29 | 30 | impl Default for NAsTimeout { 31 | fn default() -> NAsTimeout { 32 | NAsTimeout(1000.0) 33 | } 34 | } 35 | 36 | #[derive(Copy, Clone, Debug, PartialEq)] 37 | #[repr(transparent)] 38 | pub struct NCrTimeout(pub f32); 39 | 40 | impl Default for NCrTimeout { 41 | fn default() -> NCrTimeout { 42 | NCrTimeout(1000.0) 43 | } 44 | } 45 | 46 | /// Holds the most important node attributes 47 | #[derive(Copy, Clone, Debug, PartialEq)] 48 | pub struct NodeAttributes { 49 | pub configured_nad: NAD, 50 | pub initial_nad: NAD, 51 | pub product_id: ProductId, 52 | pub p2_min: P2Min, 53 | pub st_min: STMin, 54 | pub n_as_timeout: NAsTimeout, 55 | pub n_cr_timeout: NCrTimeout, 56 | } 57 | 58 | impl NodeAttributes { 59 | pub fn with_default_timing( 60 | configured_nad: NAD, 61 | initial_nad: NAD, 62 | product_id: ProductId, 63 | ) -> NodeAttributes { 64 | NodeAttributes { 65 | configured_nad, 66 | initial_nad, 67 | product_id, 68 | p2_min: P2Min::default(), 69 | st_min: STMin::default(), 70 | n_as_timeout: NAsTimeout::default(), 71 | n_cr_timeout: NCrTimeout::default(), 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub mod driver; 4 | pub mod frame; 5 | pub mod ldf; 6 | pub mod master; 7 | 8 | pub use crate::frame::{checksum, classic_checksum, Frame, PID}; 9 | pub use crate::master::Master; 10 | 11 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 12 | pub enum Error { 13 | Timeout, 14 | PhysicalBus, 15 | Checksum, 16 | } 17 | -------------------------------------------------------------------------------- /src/master.rs: -------------------------------------------------------------------------------- 1 | //! LIN bus master implementation 2 | use crate::driver; 3 | use crate::frame::Frame; 4 | use crate::PID; 5 | use crate::{checksum, classic_checksum}; 6 | 7 | pub trait Master { 8 | type Error; 9 | fn send_wakeup(&mut self) -> Result<(), Self::Error>; 10 | fn write_frame(&mut self, frame: &Frame) -> Result<(), Self::Error>; 11 | fn read_frame(&mut self, pid: PID, data_lengh: usize) -> Result; 12 | } 13 | 14 | impl Master for Driver 15 | where 16 | Driver: driver::Master, 17 | { 18 | type Error = Driver::Error; 19 | 20 | fn send_wakeup(&mut self) -> Result<(), Driver::Error> { 21 | Driver::send_wakeup(self) 22 | } 23 | 24 | fn write_frame(&mut self, frame: &Frame) -> Result<(), Driver::Error> { 25 | self.send_header(frame.get_pid())?; 26 | self.write(frame.get_data_with_checksum()) 27 | } 28 | 29 | fn read_frame(&mut self, pid: PID, data_length: usize) -> Result { 30 | assert!(data_length <= 8, "Maximum data length is 8 bytes"); 31 | self.send_header(pid)?; 32 | let mut frame = Frame { 33 | pid, 34 | data_length, 35 | buffer: [0u8; 9], 36 | }; 37 | self.read(&mut frame.buffer[0..=data_length])?; 38 | 39 | let checksum = { 40 | if pid.uses_classic_checksum() { 41 | classic_checksum(&frame.buffer[0..data_length]) 42 | } else { 43 | checksum(pid, &frame.buffer[0..data_length]) 44 | } 45 | }; 46 | if checksum != frame.buffer[data_length] { 47 | Err(Driver::Error::from(driver::Error::Checksum)) 48 | } else { 49 | Ok(frame) 50 | } 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | struct FrameTestData<'a> { 59 | pid: PID, 60 | data: &'a [u8], 61 | frame: Frame, 62 | } 63 | #[test] 64 | fn test_frame_from_data() { 65 | let test_data = [FrameTestData { 66 | pid: PID::new(0xDD).unwrap(), 67 | data: &[0x01], 68 | frame: Frame { 69 | pid: PID::new(0xDD).unwrap(), 70 | buffer: [0x01, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 71 | data_length: 1, 72 | }, 73 | }]; 74 | for d in &test_data { 75 | let frame = Frame::from_data(d.pid, d.data); 76 | assert_eq!(frame, d.frame); 77 | assert_eq!(frame.get_data(), d.data); 78 | assert_eq!(frame.get_pid(), d.pid); 79 | assert_eq!(frame.get_data_with_checksum().len(), d.data.len() + 1); 80 | } 81 | } 82 | 83 | #[test] 84 | fn test_data_decode() { 85 | let test_data = [ 86 | ( 87 | Frame::from_data(PID::new(80).unwrap(), &[254, 251, 239, 255]), 88 | [1022, 1022, 2046], 89 | ), 90 | ( 91 | Frame::from_data(PID::new(80).unwrap(), &[3, 12, 240, 182]), 92 | [3, 3, 879], 93 | ), 94 | ( 95 | Frame::from_data(PID::new(80).unwrap(), &[3, 12, 0, 183]), 96 | [3, 3, 880], 97 | ), 98 | ( 99 | Frame::from_data(PID::new(80).unwrap(), &[2, 12, 240, 182]), 100 | [2, 3, 879], 101 | ), 102 | ( 103 | Frame::from_data(PID::new(80).unwrap(), &[2, 8, 0, 183]), 104 | [2, 2, 880], 105 | ), 106 | ]; 107 | 108 | for d in &test_data { 109 | assert_eq!(d.0.decode::(0, 10), d.1[0]); 110 | assert_eq!(d.0.decode::(10, 10), d.1[1]); 111 | assert_eq!(d.0.decode::(20, 11), d.1[2]); 112 | } 113 | } 114 | 115 | #[test] 116 | fn test_data_decode_all_bits() { 117 | let frame = Frame::from_data(PID::new(80).unwrap(), &[0x55, 0xDD]); 118 | assert_eq!(frame.decode::(0, 16), 0xdd55); 119 | } 120 | } 121 | --------------------------------------------------------------------------------