Skip to content

Commit

Permalink
Merge pull request #18 from mjhouse/add_documentation
Browse files Browse the repository at this point in the history
Add documentation
  • Loading branch information
mjhouse authored Mar 3, 2024
2 parents 4523b16 + 39d019b commit 452b762
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 86 deletions.
55 changes: 45 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,58 @@
# MIL STD 1553B

Rust implementation of 1553 message parsing
![tests passing](https://github.com/mjhouse/mil_std_1553b/actions/workflows/testing.yaml/badge.svg) [![docs passing](https://github.com/mjhouse/mil_std_1553b/actions/workflows/documentation.yaml/badge.svg)](https://mjhouse.github.io/mil_std_1553b/)

This library implements a complete set of Rust structs for parsing or constructing messages that comply with
the MIL STD 1553B communication protocal.

## Features

The following features make this library useable in constrained embedded systems for government, commercial,
or military projects that can't have virally licensed dependencies.

* Does not use the standard library (`no_std`).
* Does not allocate dynamic memory.
* Has no dependencies.
* MIT licensed.

## Usage

### Creating a message

```rust
use mil_std_1553b::*;

let mut message = Message::new();

let mut command = CommandWord::new(0b0001100001100010);
assert_eq!(command.count(),2);

message.add_command(command);
assert_eq!(message.data_count(),0);
assert_eq!(message.data_expected(),2);

// add two data words
message.add_data(DataWord::new(0b0110100001101001)).unwrap();
message.add_data(DataWord::new(0b0110100001101001)).unwrap();

assert!(message.is_full());
assert_eq!(message.word_count(),3);
```

## Words

A "word" in the 1553B standard is made up of twenty bits, total. Three sync bits,
16 bits of data (in one of three different formats), and a trailing parity
bit. This means that there are two ways of referencing a particular bit- either with
a bit index offset from the beginning of the *word data* or as a "bit time" offset
from the begining of the word, including the sync bits.
A "word" in the 1553B standard is made up of twenty bits, total. Three sync bits, 16 bits of data (in one of
three different formats), and a trailing parity bit. This means that there are two ways of referencing a particular
bit- either with a bit index offset from the beginning of the *word data* or as a "bit time" offset from the beginning
of the word, including the sync bits.

| Index | Sync1 | Sync2 | Sync3 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Parity |
|--------|--- |--- |--- |----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|--- |
| Time | - | - | - | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | - |
| Offset | - | - | - | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | - |
| Offset | - | - | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | - |

The bit-time reference is used in the standard, but because we're only dealing with
the 16-bit data from each word in this project we'll be using a zero-indexed reference
in the actual code.
The bit-time reference is used in the standard, but because we're only dealing with the 16-bit data from each word in this
project we'll be using a zero-indexed reference in the actual code.

## References

Expand Down
21 changes: 21 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
/// A result type which uses the [Error] enum as the error type.
pub type Result<T> = core::result::Result<T, Error>;

/// Calculate a parity bit given a u16 word value
///
/// MIL STD 1553B uses an odd parity bit (1 if the
/// bit count of the data is even, 0 if not)[^1].
///
/// [^1]: [MIL-STD-1553 Tutorial](http://www.horntech.cn/techDocuments/MIL-STD-1553Tutorial.pdf)
#[inline]
#[must_use = "Returned value is not used"]
pub(crate) fn parity<T: Into<u16>>(v: T) -> u8 {
match v.into().count_ones() % 2 {
0 => 1,
_ => 0,
}
}

/// An error deriving from the software itself, rather than a terminal.
///
/// These errors occur during parsing or other calculations when those
Expand Down Expand Up @@ -99,11 +114,13 @@ pub enum TerminalError {

impl TerminalError {
/// Check if the enum is the 'None' variant
#[must_use = "Returned value is not used"]
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}

/// Check if the enum is the 'Error' variant
#[must_use = "Returned value is not used"]
pub const fn is_error(&self) -> bool {
matches!(self, Self::Error)
}
Expand Down Expand Up @@ -151,11 +168,13 @@ pub enum SubsystemError {

impl SubsystemError {
/// Check if the enum is 'None' variant
#[must_use = "Returned value is not used"]
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}

/// Check if the enum is 'Error' variant
#[must_use = "Returned value is not used"]
pub const fn is_error(&self) -> bool {
matches!(self, Self::Error)
}
Expand Down Expand Up @@ -203,11 +222,13 @@ pub enum MessageError {

impl MessageError {
/// Check if the enum is 'None' variant
#[must_use = "Returned value is not used"]
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}

/// Check if enum is 'Error' variant
#[must_use = "Returned value is not used"]
pub const fn is_error(&self) -> bool {
matches!(self, Self::Error)
}
Expand Down
22 changes: 22 additions & 0 deletions src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ impl ModeCode {
///
/// If the TR bit is set, but this function returns true,
/// then the message is illegal.
#[must_use = "Returned value is not used"]
pub const fn is_receive(&self) -> bool {
matches!(
self,
Expand All @@ -151,6 +152,7 @@ impl ModeCode {
/// For clarity, the enum variants are explicitly
/// listed here rather than converted to a u8 and
/// masked to get the bool value.
#[must_use = "Returned value is not used"]
pub const fn has_data(&self) -> bool {
matches!(
self,
Expand All @@ -169,6 +171,7 @@ impl ModeCode {
/// (RTs) while for other codes, this would be nonsensical.
/// Even if a mode code *can* be sent to all RTs, it may
/// have disasterous consequences if done while in flight.
#[must_use = "Returned value is not used"]
pub const fn is_broadcast(&self) -> bool {
matches!(
self,
Expand All @@ -186,6 +189,7 @@ impl ModeCode {
}

/// Check if the mode code is unrecognized
#[must_use = "Returned value is not used"]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::UnknownModeCode(_))
}
Expand Down Expand Up @@ -261,11 +265,13 @@ pub enum TransmitReceive {

impl TransmitReceive {
/// Check if this enum is the transmit variant
#[must_use = "Returned value is not used"]
pub const fn is_transmit(&self) -> bool {
matches!(self, Self::Transmit)
}

/// Check if this enum is the receive variant
#[must_use = "Returned value is not used"]
pub const fn is_receive(&self) -> bool {
matches!(self, Self::Receive)
}
Expand Down Expand Up @@ -321,6 +327,7 @@ impl Address {
}

/// Get the actual u8 value of the address
#[must_use = "Returned value is not used"]
pub const fn value(&self) -> u8 {
match self {
Self::Value(k) => *k,
Expand All @@ -330,16 +337,19 @@ impl Address {
}

/// Check if this enum contains an address
#[must_use = "Returned value is not used"]
pub const fn is_value(&self) -> bool {
matches!(self, Self::Value(_))
}

/// Check if this enum contains an unknown address
#[must_use = "Returned value is not used"]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown(_))
}

/// Check if this address is a reserved broadcast value
#[must_use = "Returned value is not used"]
pub const fn is_broadcast(&self) -> bool {
matches!(self, Self::Broadcast(_))
}
Expand Down Expand Up @@ -455,11 +465,13 @@ pub enum Instrumentation {

impl Instrumentation {
/// Check if this enum is the Status variant
#[must_use = "Returned value is not used"]
pub const fn is_status(&self) -> bool {
matches!(self, Self::Status)
}

/// Check if this enum is the Command variant
#[must_use = "Returned value is not used"]
pub const fn is_command(&self) -> bool {
matches!(self, Self::Command)
}
Expand Down Expand Up @@ -507,11 +519,13 @@ pub enum ServiceRequest {

impl ServiceRequest {
/// Check if enum is the NoService variant
#[must_use = "Returned value is not used"]
pub const fn is_noservice(&self) -> bool {
matches!(self, Self::NoService)
}

/// Check if the enum is the Service variant
#[must_use = "Returned value is not used"]
pub const fn is_service(&self) -> bool {
matches!(self, Self::Service)
}
Expand Down Expand Up @@ -558,11 +572,13 @@ pub enum Reserved {

impl Reserved {
/// Check if this enum is the None variant
#[must_use = "Returned value is not used"]
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}

/// Check if this enum is the Value variant
#[must_use = "Returned value is not used"]
pub const fn is_value(&self) -> bool {
matches!(self, Self::Value(_))
}
Expand Down Expand Up @@ -609,11 +625,13 @@ pub enum BroadcastCommand {

impl BroadcastCommand {
/// Check if enum is the NotReceived variant
#[must_use = "Returned value is not used"]
pub const fn is_notreceived(&self) -> bool {
matches!(self, Self::NotReceived)
}

/// Check if the enum is the Received variant
#[must_use = "Returned value is not used"]
pub const fn is_received(&self) -> bool {
matches!(self, Self::Received)
}
Expand Down Expand Up @@ -660,11 +678,13 @@ pub enum TerminalBusy {

impl TerminalBusy {
/// Check if enum is the NotBusy variant
#[must_use = "Returned value is not used"]
pub const fn is_notbusy(&self) -> bool {
matches!(self, Self::NotBusy)
}

/// Check if the enum is the Busy variant
#[must_use = "Returned value is not used"]
pub const fn is_busy(&self) -> bool {
matches!(self, Self::Busy)
}
Expand Down Expand Up @@ -712,11 +732,13 @@ pub enum BusControlAccept {

impl BusControlAccept {
/// Check if the enum is the NotAccepted variant
#[must_use = "Returned value is not used"]
pub const fn is_notaccepted(&self) -> bool {
matches!(self, Self::NotAccepted)
}

/// Check if the enum is the Accepted variant
#[must_use = "Returned value is not used"]
pub const fn is_accepted(&self) -> bool {
matches!(self, Self::Accepted)
}
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ pub mod fields;
pub mod flags;
pub mod message;
pub mod word;

pub use message::Message;
pub use word::{CommandWord,StatusWord,DataWord};
14 changes: 10 additions & 4 deletions src/message/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ impl Message {
}

/// Check if the message is full
#[must_use = "Returned value is not used"]
pub fn is_full(&self) -> bool {
self.count == MAX_WORDS
self.data_count() == self.data_expected()
}

/// Check if the message is empty
#[must_use = "Returned value is not used"]
pub fn is_empty(&self) -> bool {
self.count == 0
}
Expand Down Expand Up @@ -79,25 +81,29 @@ impl Message {

/// Get the expected number of data words
pub fn data_expected(&self) -> usize {
self.first().map(Word::data).unwrap_or(0)
self.first().map(Word::data_count).unwrap_or(0)
}

/// Check if message has data words
#[must_use = "Returned value is not used"]
pub fn has_data(&self) -> bool {
self.data_count() > 0
}

/// Check if message can contain more data words
#[must_use = "Returned value is not used"]
pub fn has_space(&self) -> bool {
self.data_count() < self.data_expected()
}

/// Check if message starts with a command word
#[must_use = "Returned value is not used"]
pub fn has_command(&self) -> bool {
self.first().map(Word::is_command).unwrap_or(false)
}

/// Check if message starts with a status word
#[must_use = "Returned value is not used"]
pub fn has_status(&self) -> bool {
self.first().map(Word::is_status).unwrap_or(false)
}
Expand All @@ -114,7 +120,7 @@ impl Message {

/// Add a data word, returning the size of the message on success
pub fn add_data(&mut self, word: DataWord) -> Result<usize> {
if self.is_full() {
if self.is_full() && self.has_command() {
Err(Error::MessageIsFull)
} else if self.is_empty() {
Err(Error::FirstWordIsData)
Expand Down Expand Up @@ -164,7 +170,7 @@ mod tests {
fn test_create_message() {
let message = Message::new();

assert_eq!(message.is_full(), false);
assert_eq!(message.is_full(), true);
assert_eq!(message.is_empty(), true);
assert_eq!(message.first(), None);
assert_eq!(message.last(), None);
Expand Down
Loading

0 comments on commit 452b762

Please sign in to comment.