diff --git a/Cargo.toml b/Cargo.toml index f12e3d8..1f546ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" [dependencies] anyhow = "1.0.86" bincode = "1.3.3" +bme280 = "0.5.1" gpiod = "0.3.0" +linux-embedded-hal = "0.4.0" log = "0.4.22" ron = "0.8.1" serde = { version = "1.0.204", features = ["derive"] } diff --git a/conf.ron b/conf.ron index 5f938f8..2b0c390 100644 --- a/conf.ron +++ b/conf.ron @@ -18,10 +18,17 @@ Config( spi_mode: SPI_MODE_0, ), radio_config: RadioConfig( - bandwidth: bandwidth_20_8kHz, + frequency: 433_000_000, + bandwidth: bandwidth_31_25kHz, coding_rate: coding_4_8, spreading_factor: spreading_factor_4096, tx_power: 17, ), ), + bme_config: BME280Config( + i2c_bus_path: "/dev/i2c-2", + i2c_address: 118, + measurement_interval: 10, + enabled: false, + ), ) diff --git a/src/bme280.rs b/src/bme280.rs new file mode 100644 index 0000000..23f2534 --- /dev/null +++ b/src/bme280.rs @@ -0,0 +1,54 @@ +use anyhow::{Context, Result}; +use bme280::i2c::BME280; +use linux_embedded_hal::{Delay, I2cdev}; +use log::info; + +use crate::BME280Config; + +pub struct BME280Sensor { + bme280: BME280, + delay: Delay, +} + +impl BME280Sensor { + pub fn new(config: BME280Config) -> Result { + let i2c_bus = I2cdev::new(&config.i2c_bus_path) + .context("Failed to initialize I2C bus in BME280Sensor::new")?; + + let mut delay = Delay {}; + + let mut bme280 = BME280::new(i2c_bus, config.i2c_address); + bme280 + .init(&mut delay) + .map_err(|e| anyhow::anyhow!("BME280 initialization failed: {:?}", e))?; + + info!("BME280 initialized successfully"); + Ok(BME280Sensor { bme280, delay }) + } + + pub fn read_measurements(&mut self) -> Result<(f32, f32, f32)> { + let measurements = self + .bme280 + .measure(&mut self.delay) + .map_err(|e| anyhow::anyhow!("Failed to read BME280 sensor: {:?}", e))?; + + Ok(( + measurements.temperature, + measurements.pressure / 100.0, + measurements.humidity, + )) + } + + pub fn print(&mut self) -> Result<()> { + match self.read_measurements() { + Ok((temperature, pressure, humidity)) => { + info!("BME280 Sensor Measurements:"); + info!("Temperature: {:>6.1} °C", temperature); + info!("Pressure: {:>7.1} hPa", pressure); + info!("Humidity: {:>6.1} %", humidity); + Ok(()) + } + Err(e) => Err(e), + } + } +} diff --git a/src/config.rs b/src/config.rs index 49aab3f..f419580 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,52 +1,22 @@ use crate::defines::{Bandwidth, CodingRate, SpreadingFactor}; -use log::{error, info}; +use anyhow::{Context, Result}; +use log::info; use serde::{Deserialize, Serialize}; -use std::env; -use std::{fs, process}; +use std::fs; #[derive(Debug, Deserialize, Serialize)] pub struct Config { pub mqtt_config: MQTTConfig, pub lora_config: LoRaConfig, + pub bme_config: BME280Config } impl Config { - pub fn from_file() -> Config { - let config_file = match fs::read_to_string(Config::parse_args()) { - Ok(s) => s, - Err(e) => { - eprintln!("ERROR {:?}", e.to_string()); - error!("While reading config file: {e}."); - process::exit(-1) - } - }; - - match ron::from_str(config_file.as_str()) { - Ok(config) => { - info!("Succesfully read config file."); - config - } - Err(e) => { - eprintln!("ERROR {:?}", e.to_string()); - error!("While deserializing ron file to config: {e}."); - process::exit(-1); - } - } - } - - fn parse_args() -> String { - let args: Vec = env::args().collect(); - - match args.len() { - 1 => "./conf.ron".to_string(), - 2 => args[1].to_string(), - _ => { - eprintln!("Wrong number of arguments!"); - println!("Usage: ./rusty_beagle [config file]"); - error!("Wrong number of arguments."); - std::process::exit(-1); - } - } + pub fn from_file(config_path: String) -> Result { + let config_file = fs::read_to_string(config_path).context("Config::from_file")?; + let config = ron::from_str(config_file.as_str()).context("Config::from_file")?; + info!("Succesfully read config file."); + Ok(config) } } @@ -59,6 +29,14 @@ pub struct MQTTConfig { pub topic: String, } +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct BME280Config { + pub i2c_bus_path: String, + pub i2c_address: u8, + pub measurement_interval: u64, + pub enabled: bool, +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct SPIConfig { pub spidev_path: String, @@ -79,6 +57,7 @@ pub struct LoRaConfig { #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RadioConfig { + pub frequency: u64, pub bandwidth: Bandwidth, pub coding_rate: CodingRate, pub spreading_factor: SpreadingFactor, @@ -174,3 +153,18 @@ impl SpiFlags { pub const SPI_MODE_2: SpiFlags = SpiFlags::SPI_CPOL; pub const SPI_MODE_3: SpiFlags = SpiFlags::SPI_MODE_0; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn config_correct() { + assert!(Config::from_file("./tests/configs/conf.ron".to_string()).is_ok()); + } + + #[test] + fn config_incomplete() { + assert!(Config::from_file("./tests/configs/incomplete_conf.ron".to_string()).is_err()); + } +} diff --git a/src/conversions.rs b/src/conversions.rs index 8d513a8..e12ae75 100644 --- a/src/conversions.rs +++ b/src/conversions.rs @@ -1,16 +1,4 @@ -use anyhow::{anyhow, Context, Result}; - -pub fn vec_to_f32(vec: &[u8], start: usize) -> Result { - let slice = &vec[start..start + 4]; - let array: [u8; 4] = slice.try_into().context("vec_to_f32")?; - Ok(f32::from_le_bytes(array)) -} - -pub fn vec_to_f64(vec: &[u8], start: usize) -> Result { - let slice = &vec[start..start + 8]; - let array: [u8; 8] = slice.try_into().context("vec_to_f64")?; - Ok(f64::from_le_bytes(array)) -} +use anyhow::{Context, Result}; pub fn vec_to_u16(vec: &[u8], start: usize) -> Result { let slice = &vec[start..start + 2]; @@ -18,12 +6,6 @@ pub fn vec_to_u16(vec: &[u8], start: usize) -> Result { Ok(u16::from_le_bytes(array)) } -pub fn vec_to_u32(vec: &[u8], start: usize) -> Result { - let slice = &vec[start..start + 4]; - let array: [u8; 4] = slice.try_into().context("vec_to_u32")?; - Ok(u32::from_le_bytes(array)) -} - pub fn vec_to_u64(vec: &[u8], start: usize) -> Result { let slice = &vec[start..start + 8]; let array: [u8; 8] = slice.try_into().context("vec_to_u64")?; @@ -41,9 +23,3 @@ pub fn vec_to_i32(vec: &[u8], start: usize) -> Result { let array: [u8; 4] = slice.try_into().context("vec_to_i32")?; Ok(i32::from_le_bytes(array)) } - -pub fn vec_to_i16(vec: &[u8], start: usize) -> Result { - let slice = &vec[start..start + 2]; - let array: [u8; 2] = slice.try_into().context("vec_to_i16")?; - Ok(i16::from_le_bytes(array)) -} diff --git a/src/lora.rs b/src/lora.rs index bd4135e..2c61060 100644 --- a/src/lora.rs +++ b/src/lora.rs @@ -5,7 +5,7 @@ use crate::config::RadioConfig; use crate::defines::*; use crate::mqtt::BlockingQueue; use crate::packet::{Data, DataType, Packet, BME280}; -use crate::version_tag::{print_version_tag, print_rusty_beagle}; +use crate::version_tag::{print_rusty_beagle, print_version_tag}; use crate::{GPIOPin, GPIOPinNumber, LoRaConfig, Mode}; use anyhow::{anyhow, Context, Error, Result}; use gpiod::{Chip, Lines, Options, Output, Input, Edge, EdgeDetect}; @@ -54,7 +54,11 @@ impl LoRa { let dio0_pin = MockGPIO {}; let mode = _lora_config.mode.clone(); - Ok(LoRa { mock_registers, dio0_pin, mode }) + Ok(LoRa { + mock_registers, + dio0_pin, + mode, + }) } #[cfg(target_arch = "x86_64")] @@ -96,8 +100,10 @@ impl LoRa { .build(); spidev.configure(&spi_options)?; - let reset_pin = Self::config_output_pin(lora_config.reset_gpio).context("LoRa::from_config")?; - let dio0_pin = Self::config_input_pin(lora_config.dio0_gpio).context("LoRa::from_config")?; + let reset_pin = + Self::config_output_pin(lora_config.reset_gpio).context("LoRa::from_config")?; + let dio0_pin = + Self::config_input_pin(lora_config.dio0_gpio).context("LoRa::from_config")?; let mode = lora_config.mode.clone(); @@ -113,7 +119,6 @@ impl LoRa { #[cfg(target_arch = "arm")] fn config_output_pin(pin_number: GPIOPinNumber) -> Result> { - let pin = GPIOPin::from_gpio_pin_number(pin_number); let chip = match Chip::new(pin.chip) { @@ -133,7 +138,6 @@ impl LoRa { #[cfg(target_arch = "arm")] fn config_input_pin(pin_number: GPIOPinNumber) -> Result> { - let pin = GPIOPin::from_gpio_pin_number(pin_number); let chip = match Chip::new(pin.chip) { @@ -334,6 +338,7 @@ impl LoRa { Ok(()) } + pub async fn get_bandwidth(&mut self) -> Result { let mut value = 0x00; self.spi_read_register(LoRaRegister::MODEM_CONFIG_1, &mut value) @@ -405,7 +410,8 @@ impl LoRa { loop { let dio0_event = self.dio0_pin.read_event().context("LoRa::receive_packet")?; - if dio0_event.edge == Edge::Rising { // packet is received on rising edge of DIO0 + if dio0_event.edge == Edge::Rising { + // packet is received on rising edge of DIO0 let mut has_crc_error = false; self.has_crc_error(&mut has_crc_error) .context("LoRa::receive_packet")?; @@ -429,7 +435,8 @@ impl LoRa { self.spi_write_register(LoRaRegister::FIFO_ADDR_PTR, received_address) .context("LoRa::receive_packet")?; - self.read_fifo(&mut buffer).context("LoRa::receive_packet")?; + self.read_fifo(&mut buffer) + .context("LoRa::receive_packet")?; Ok(buffer) } @@ -450,7 +457,8 @@ impl LoRa { loop { let dio0_event = self.dio0_pin.read_event().context("LoRa::send_packet")?; - if dio0_event.edge == Edge::Rising { // rising edge of DIO0 indicates succesful packet send + if dio0_event.edge == Edge::Rising { + // rising edge of DIO0 indicates succesful packet send println!("Packet sent."); break; } @@ -535,10 +543,10 @@ impl LoRa { msg_id: 0x11, msg_count: 0x00, data_type: DataType::BME280, - data: Data::Bme280(BME280 { - temperature: 23, - humidity: 4, - pressure: 56 + data: Data::Bme280(BME280 { + temperature: 23, + humidity: 4, + pressure: 56, }), }; self.send_packet(packet.to_bytes()?).await.context("LoRa::start")?; @@ -571,15 +579,144 @@ impl LoRa { #[cfg(target_arch = "arm")] pub async fn config_dio(&mut self) -> Result<()> { let mut initial_value = 0x00; - self.spi_read_register(LoRaRegister::DIO_MAPPING_1, &mut initial_value).context("LoRa::config_dio")?; + self.spi_read_register(LoRaRegister::DIO_MAPPING_1, &mut initial_value) + .context("LoRa::config_dio")?; match self.mode { - Mode::RX => { - }, + Mode::RX => {} Mode::TX => { - self.spi_write_register(LoRaRegister::DIO_MAPPING_1, initial_value | (0b01 << 6)).context("LoRa::config_dio")?; // DIO0 TxDone - }, + self.spi_write_register(LoRaRegister::DIO_MAPPING_1, initial_value | (0b01 << 6)) + .context("LoRa::config_dio")?; // DIO0 TxDone + } } Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::*; + + macro_rules! handle_error { + ($func:expr) => { + match $func { + Err(e) => { + eprintln!("{:?}", e); + error!("{:?}", e); + std::process::exit(-1); + } + Ok(s) => s, + } + }; + } + + #[test] + fn spi_read_register_correct() { + let config = handle_error!(Config::from_file("./conf.ron".to_string())); + let mut lora = match LoRa::from_config(&config.lora_config) { + Ok(lora) => lora, + Err(e) => { + error!("When creating lora object: {e}"); + std::process::exit(-1); + } + }; + + let mut value: u8 = 0x00; + assert!(lora + .spi_read_register(LoRaRegister::PAYLOAD_LENGTH, &mut value) + .is_ok()); + } + + #[test] + fn spi_write_register_correct() { + let config = handle_error!(Config::from_file("./conf.ron".to_string())); + let mut lora = match LoRa::from_config(&config.lora_config) { + Ok(lora) => lora, + Err(e) => { + error!("When creating lora object: {e}"); + std::process::exit(-1); + } + }; + + let value: u8 = 0xFF; + assert!(lora + .spi_write_register(LoRaRegister::PAYLOAD_LENGTH, value) + .is_ok()); + } + + #[test] + fn standby_mode_correct() { + let config = handle_error!(Config::from_file("./conf.ron".to_string())); + let mut lora = match LoRa::from_config(&config.lora_config) { + Ok(lora) => lora, + Err(e) => { + error!("When creating lora object: {e}"); + std::process::exit(-1); + } + }; + + handle_error!(lora.standby_mode()); + + let mut mode: u8 = 0x00; + handle_error!(lora.spi_read_register(LoRaRegister::OP_MODE, &mut mode)); + assert_eq!((LoRaMode::LONG_RANGE as u8 | LoRaMode::STDBY as u8), mode); + } + + #[test] + fn sleep_mode_correct() { + let config = handle_error!(Config::from_file("./conf.ron".to_string())); + let mut lora = match LoRa::from_config(&config.lora_config) { + Ok(lora) => lora, + Err(e) => { + error!("When creating lora object: {e}"); + std::process::exit(-1); + } + }; + + handle_error!(lora.sleep_mode()); + + let mut mode: u8 = 0x00; + handle_error!(lora.spi_read_register(LoRaRegister::OP_MODE, &mut mode)); + assert_eq!((LoRaMode::LONG_RANGE as u8 | LoRaMode::SLEEP as u8), mode); + } + + #[test] + fn receive_mode_correct() { + let config = handle_error!(Config::from_file("./conf.ron".to_string())); + let mut lora = match LoRa::from_config(&config.lora_config) { + Ok(lora) => lora, + Err(e) => { + error!("When creating lora object: {e}"); + std::process::exit(-1); + } + }; + + handle_error!(lora.receive_mode()); + + let mut mode: u8 = 0x00; + handle_error!(lora.spi_read_register(LoRaRegister::OP_MODE, &mut mode)); + assert_eq!( + (LoRaMode::LONG_RANGE as u8 | LoRaMode::RX_CONTINUOUS as u8), + mode + ); + } + + #[test] + fn transmit_mode_correct() { + let config = handle_error!(Config::from_file("./conf.ron".to_string())); + let mut lora = match LoRa::from_config(&config.lora_config) { + Ok(lora) => lora, + Err(e) => { + error!("When creating lora object: {e}"); + std::process::exit(-1); + } + }; + + handle_error!(lora.transmit_mode()); + + let mut mode: u8 = 0x00; + handle_error!(lora.spi_read_register(LoRaRegister::OP_MODE, &mut mode)); + assert_eq!((LoRaMode::LONG_RANGE as u8 | LoRaMode::TX as u8), mode); + } +} diff --git a/src/main.rs b/src/main.rs index 58f1f4b..c7949f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod bme280; mod config; mod conversions; mod defines; @@ -12,6 +13,8 @@ extern crate log; pub use crate::config::*; pub use crate::defines::*; pub use crate::logging::start_logger; + +use bme280::BME280Sensor; use log::{error, info}; use lora::LoRa; use mqtt::BlockingQueue; @@ -19,6 +22,8 @@ use mqtt::Mqtt; use packet::DataType; use packet::Packet; use std::sync::Arc; +use std::env; + macro_rules! handle_error_exit { ($func:expr) => { @@ -46,13 +51,46 @@ macro_rules! handle_error_contiue { }; } + +fn parse_args() -> String { + let args: Vec = env::args().collect(); + + match args.len() { + 1 => "./conf.ron".to_string(), + 2 => args[1].to_string(), + _ => { + eprintln!("Wrong number of arguments!"); + println!("Usage: ./rusty_beagle [config file]"); + error!("Wrong number of arguments."); + std::process::exit(-1); + } + } +} + #[tokio::main] async fn main() { + start_logger(); - let config = Config::from_file(); + let config_path = parse_args(); + let config = handle_error!(Config::from_file(config_path)); let radio_config = config.lora_config.radio_config.clone(); let mqtt_config = config.mqtt_config.clone(); + let bme_config: BME280Config = config.bme_config.clone(); + + if bme_config.enabled { + tokio::spawn(async move { + let measurement_interval = bme_config.measurement_interval; + let mut bme280 = handle_error!(BME280Sensor::new(bme_config)); + + loop { + if let Err(e) = bme280.print() { + error!("Failed to print BME280 sensor measurements: {:?}", e); + } + tokio::sleep(Duration::from_secs(measurement_interval)).await; + } + }); + } let mut lora = match LoRa::from_config(&config.lora_config).await { Ok(lora) => { diff --git a/src/packet.rs b/src/packet.rs index b2f553f..68e709f 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,9 +1,8 @@ use core::fmt; - -use serde::{Deserialize, Serialize}; +use crate::conversions::*; use anyhow::{anyhow, Context, Result}; +use serde::{Deserialize, Serialize}; use std::{fmt::format, hash::{DefaultHasher, Hash, Hasher}, time::SystemTime}; -use crate::conversions::*; pub const DATA_SIZE: usize = 59; pub const META_DATA_SIZE: usize = 5; @@ -47,7 +46,7 @@ pub enum Data { Sms(String), } -pub trait GetData { +trait GetData { fn get_data(&self) -> Option<&T>; } @@ -135,40 +134,52 @@ impl Data { match data_type { DataType::BME280 => { if bytes.len() != 8 { - return Err(anyhow!("Incorrect length, was {}, should be 8", bytes.len())) - .context("Data::from_bytes"); + return Err(anyhow!( + "Incorrect length, was {}, should be 8", + bytes.len() + )) + .context("Data::from_bytes"); } Ok(Data::Bme280(BME280 { temperature: bytes[META_DATA_SIZE], humidity: bytes[META_DATA_SIZE + 1], pressure: bytes[META_DATA_SIZE + 2], })) - }, + } DataType::BMA400 => { if bytes.len() != 29 { - return Err(anyhow!("Incorrect length, was {}, should be 29", bytes.len())) - .context("Data::from_bytes"); + return Err(anyhow!( + "Incorrect length, was {}, should be 29", + bytes.len() + )) + .context("Data::from_bytes"); } Ok(Data::Bma400(BMA400 { x: vec_to_u64(bytes, META_DATA_SIZE).context("Data::from_bytes")?, y: vec_to_u64(bytes, META_DATA_SIZE + 8).context("Data::from_bytes")?, z: vec_to_u64(bytes, META_DATA_SIZE + 16).context("Data::from_bytes")?, })) - }, + } DataType::MQ2 => { if bytes.len() != 22 { - return Err(anyhow!("Incorrect length, was {}, should be 22", bytes.len())) - .context("Data::from_bytes"); + return Err(anyhow!( + "Incorrect length, was {}, should be 22", + bytes.len() + )) + .context("Data::from_bytes"); } Ok(Data::Mq2(MQ2 { gas_type: bytes[META_DATA_SIZE], value: vec_to_u128(bytes, META_DATA_SIZE + 1).context("Data::from_bytes")?, - })) - }, + })) + } DataType::Gps => { if bytes.len() != 16 { - return Err(anyhow!("Incorrect length, was {}, should be 16", bytes.len())) - .context("Data::from_bytes"); + return Err(anyhow!( + "Incorrect length, was {}, should be 16", + bytes.len() + )) + .context("Data::from_bytes"); } Ok(Data::Gps(Gps { status: bytes[META_DATA_SIZE], @@ -176,14 +187,20 @@ impl Data { latitude: vec_to_i32(bytes, META_DATA_SIZE + 3).context("Data::from_bytes")?, longitude: vec_to_i32(bytes, META_DATA_SIZE + 7).context("Data::from_bytes")?, })) - }, + } DataType::Sms => { if bytes.len() < 6 { - return Err(anyhow!("Incorrect length, was {}, should be at least 6", bytes.len())) - .context("Data::from_bytes"); + return Err(anyhow!( + "Incorrect length, was {}, should be at least 6", + bytes.len() + )) + .context("Data::from_bytes"); } - Ok(Data::Sms(String::from_utf8(bytes[META_DATA_SIZE..bytes.len()].to_vec()).context("Data::from_bytes")?)) - }, + Ok(Data::Sms( + String::from_utf8(bytes[META_DATA_SIZE..bytes.len()].to_vec()) + .context("Data::from_bytes")?, + )) + } } } } @@ -191,10 +208,22 @@ impl Data { impl fmt::Debug for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Data::Bme280(data) => write!(f, "{{ temperature: {}, humidity: {}, pressure: {} }}", data.temperature, data.humidity, data.pressure), + Data::Bme280(data) => write!( + f, + "{{ temperature: {}, humidity: {}, pressure: {} }}", + data.temperature, data.humidity, data.pressure + ), Data::Bma400(data) => write!(f, "{{ x: {}, y: {}, z: {} }}", data.x, data.y, data.z), - Data::Mq2(data) => write!(f, "{{ gas_type: {}, value: {} }}", data.gas_type, data.value), - Data::Gps(data) => write!(f, "{{ status: {}, altitude: {}, latitude: {}, longitude: {} }}", data.status, data.altitude, data.latitude, data.longitude), + Data::Mq2(data) => write!( + f, + "{{ gas_type: {}, value: {} }}", + data.gas_type, data.value + ), + Data::Gps(data) => write!( + f, + "{{ status: {}, altitude: {}, latitude: {}, longitude: {} }}", + data.status, data.altitude, data.latitude, data.longitude + ), Data::Sms(data) => write!(f, "\"{}\"", *data), } } @@ -212,20 +241,33 @@ pub struct Packet { impl Packet { pub fn new(bytes: &[u8]) -> Result { - if bytes.len() < 5 || bytes.len() > 64 { + if bytes.len() < META_DATA_SIZE || bytes.len() > PACKET_SIZE { return Err(anyhow!("Incorrect length, was {}", bytes.len())); } let version = bytes[PACKET_VERSION_IDX]; let id = bytes[PACKET_ID_IDX]; let msg_id = bytes[PACKET_MSG_ID_IDX]; let msg_count = bytes[PACKET_MSG_COUNT_IDX]; - let data_type = DataType::new(bytes[PACKET_DATA_TYPE_IDX]).context("Packet::new")?; + let data_type = DataType::new(bytes[PACKET_DATA_TYPE_IDX]).context("Packet::new")?; let data = Data::from_bytes(bytes).context("Packet::new")?; - Ok(Self { version, id, msg_id, msg_count, data_type, data }) + Ok(Self { + version, + id, + msg_id, + msg_count, + data_type, + data, + }) } pub fn to_bytes(&self) -> Result> { - let mut packet = vec![self.version, self.id, self.msg_id, self.msg_count, self.data_type as u8]; + let mut packet = vec![ + self.version, + self.id, + self.msg_id, + self.msg_count, + self.data_type as u8, + ]; let mut data = match &self.data { Data::Bme280(data) => bincode::serialize(data).context("Packet::to_bytes")?, Data::Bma400(data) => bincode::serialize(data).context("Packet::to_bytes")?, @@ -253,6 +295,7 @@ impl Packet { #[cfg(test)] mod tests { use super::*; + use std::hash::{DefaultHasher, Hasher}; fn calculate_hash(t: &T) -> u64 { let mut s = DefaultHasher::new(); @@ -300,7 +343,10 @@ mod tests { let deserialized_data = Packet::new(&bytes).unwrap(); - assert_eq!(calculate_hash(&deserialized_data), calculate_hash(&expected_packet)); + assert_eq!( + calculate_hash(&deserialized_data), + calculate_hash(&expected_packet) + ); } #[test] @@ -336,10 +382,11 @@ mod tests { }), }; - let expected_data: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x02, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let expected_data: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ]; let serialized_packet = packet.to_bytes().unwrap(); assert_eq!(serialized_packet, expected_data); @@ -347,10 +394,11 @@ mod tests { #[test] fn deserialize_bma400_correct() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x02, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ]; let expected_packet = Packet { version: 0x33, @@ -367,24 +415,28 @@ mod tests { let deserialized_data = Packet::new(&bytes).unwrap(); - assert_eq!(calculate_hash(&deserialized_data), calculate_hash(&expected_packet)); + assert_eq!( + calculate_hash(&deserialized_data), + calculate_hash(&expected_packet) + ); } #[test] fn deserialize_bma400_data_too_short() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x02, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; assert!(Packet::new(&bytes).is_err()); } #[test] fn deserialize_bma400_packet_too_long() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x02, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, + ]; assert!(Packet::new(&bytes).is_err()); } @@ -408,10 +460,10 @@ mod tests { }), }; - let expected_data: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x03, - 0x01, + let expected_data: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x03, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; + ]; let serialized_packet = packet.to_bytes().unwrap(); assert_eq!(serialized_packet, expected_data); @@ -419,10 +471,10 @@ mod tests { #[test] fn deserialize_mq2_correct() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x03, - 0x01, + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x03, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; + ]; let expected_packet = Packet { version: 0x33, @@ -438,24 +490,27 @@ mod tests { let deserialized_data = Packet::new(&bytes).unwrap(); - assert_eq!(calculate_hash(&deserialized_data), calculate_hash(&expected_packet)); + assert_eq!( + calculate_hash(&deserialized_data), + calculate_hash(&expected_packet) + ); } #[test] fn deserialize_mq2_data_too_short() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x03, - 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x03, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + ]; assert!(Packet::new(&bytes).is_err()); } #[test] fn deserialize_mq2_packet_too_long() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x03, - 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x03, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + ]; assert!(Packet::new(&bytes).is_err()); } @@ -475,17 +530,16 @@ mod tests { data_type: DataType::Gps, data: Data::Gps(Gps { status: u8::MAX, - altitude: u16::MAX, + altitude: u16::MAX, latitude: i32::MAX, longitude: i32::MAX, }), }; - let expected_data: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x04, - 0xFF, - 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x7F, - 0xFF, 0xFF, 0xFF, 0x7F]; + let expected_data: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, 0x7F, + ]; let serialized_packet = packet.to_bytes().unwrap(); assert_eq!(serialized_packet, expected_data); @@ -493,11 +547,10 @@ mod tests { #[test] fn deserialize_gps_correct() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x04, - 0xFF, - 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x7F, - 0xFF, 0xFF, 0xFF, 0x7F]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, 0x7F, + ]; let expected_packet = Packet { version: 0x33, @@ -507,7 +560,7 @@ mod tests { data_type: DataType::Gps, data: Data::Gps(Gps { status: u8::MAX, - altitude: u16::MAX, + altitude: u16::MAX, latitude: i32::MAX, longitude: i32::MAX, }), @@ -515,26 +568,27 @@ mod tests { let deserialized_data = Packet::new(&bytes).unwrap(); - assert_eq!(calculate_hash(&deserialized_data), calculate_hash(&expected_packet)); + assert_eq!( + calculate_hash(&deserialized_data), + calculate_hash(&expected_packet) + ); } #[test] fn deserialize_gps_data_too_short() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x04, - 0xFF, - 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x7F, - 0xFF, 0xFF, 0xFF]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, + ]; assert!(Packet::new(&bytes).is_err()); } #[test] fn deserialize_gps_packet_too_long() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x04, - 0xFF, - 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x7F, - 0xFF, 0xFF, 0xFF, 0x7F, 0xFF]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, 0x7F, 0xFF, + ]; assert!(Packet::new(&bytes).is_err()); } @@ -560,7 +614,7 @@ mod tests { assert_eq!(serialized_packet, expected_data); } - + #[test] fn deserialize_sms_correct() { let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x20, 0x41, 0x42]; @@ -576,7 +630,10 @@ mod tests { let deserialized_data = Packet::new(&bytes).unwrap(); - assert_eq!(calculate_hash(&deserialized_data), calculate_hash(&expected_packet)); + assert_eq!( + calculate_hash(&deserialized_data), + calculate_hash(&expected_packet) + ); } #[test] @@ -587,13 +644,13 @@ mod tests { #[test] fn deserialize_sms_packet_too_long() { - let bytes: Vec = vec![0x33, 0x22, 0x11, 0x00, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20]; + let bytes: Vec = vec![ + 0x33, 0x22, 0x11, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + ]; assert!(Packet::new(&bytes).is_err()); } diff --git a/tests/configs/conf.ron b/tests/configs/conf.ron new file mode 100644 index 0000000..dbae930 --- /dev/null +++ b/tests/configs/conf.ron @@ -0,0 +1,34 @@ +Config( + mqtt_config: MQTTConfig( + ip: "192.168.6.2", + port: "1234", + login: "admin", + password: "verysecurepassword", + topic: "lora/sensor" + ), + lora_config: LoRaConfig( + mode: RX, + reset_gpio: GPIO_66, + dio0_gpio: GPIO_69, + spi_config: SPIConfig( + spidev_path: "/dev/spidev0.0", + bits_per_word: 8, + max_speed_hz: 500000, + lsb_first: false, + spi_mode: SPI_MODE_0, + ), + radio_config: RadioConfig( + frequency: 433_000_000, + bandwidth: bandwidth_20_8kHz, + coding_rate: coding_4_8, + spreading_factor: spreading_factor_4096, + tx_power: 17, + ), + ), + bme_config: BME280Config( + i2c_bus_path: "/dev/i2c-2", + i2c_address: 118, + measurement_interval: 10, + enabled: true, + ), +) diff --git a/tests/configs/incomplete_conf.ron b/tests/configs/incomplete_conf.ron new file mode 100644 index 0000000..1f6acfe --- /dev/null +++ b/tests/configs/incomplete_conf.ron @@ -0,0 +1,33 @@ +Config( + mqtt_config: MQTTConfig( + ip: "192.168.6.2", + port: "1234", + login: "admin", + password: "verysecurepassword", + topic: "lora/sensor" + ), + lora_config: LoRaConfig( + mode: RX, + reset_gpio: GPIO_66, + dio0_gpio: GPIO_69, + spi_config: SPIConfig( + spidev_path: "/dev/spidev0.0", + bits_per_word: 8, + max_speed_hz: 500000, + lsb_first: false, + spi_mode: SPI_MODE_0, + ), + radio_config: RadioConfig( + frequency: 433_000_000, + bandwidth: bandwidth_20_8kHz, + coding_rate: coding_4_8, + spreading_factor: spreading_factor_4096, + ), + ), + bme_config: BME280Config( + i2c_bus_path: "/dev/i2c-2", + i2c_address: 118, + measurement_interval: 10, + enabled: true, + ), +)