mirror of
https://codeberg.org/icewind/pms700x.git
synced 2026-06-03 18:14:11 +02:00
wip
This commit is contained in:
parent
25dc443d9a
commit
9058d7e068
6 changed files with 353 additions and 2 deletions
|
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
||||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
embedded-hal = "0.2"
|
||||||
|
nb = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
linux-embedded-hal = "0.3"
|
||||||
|
|
|
||||||
13
examples/active_read.rs
Normal file
13
examples/active_read.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
use linux_embedded_hal::Serial;
|
||||||
|
use nb::block;
|
||||||
|
use pms700x::Pms700X;
|
||||||
|
use std::env::args;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let port = args().skip(1).next().expect("No serial provided");
|
||||||
|
let serial = Serial::open(&PathBuf::from(port)).unwrap();
|
||||||
|
|
||||||
|
let mut pms = Pms700X::new(serial).into_active().unwrap();
|
||||||
|
dbg!(block!(pms.read()).unwrap());
|
||||||
|
}
|
||||||
13
examples/passive_read.rs
Normal file
13
examples/passive_read.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
use linux_embedded_hal::Serial;
|
||||||
|
use nb::block;
|
||||||
|
use pms700x::Pms700X;
|
||||||
|
use std::env::args;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let port = args().skip(1).next().expect("No serial provided");
|
||||||
|
let serial = Serial::open(&PathBuf::from(port)).unwrap();
|
||||||
|
|
||||||
|
let mut pms = Pms700X::new(serial).into_passive().unwrap();
|
||||||
|
dbg!(block!(pms.read()).unwrap());
|
||||||
|
}
|
||||||
14
examples/sleep.rs
Normal file
14
examples/sleep.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use linux_embedded_hal::Serial;
|
||||||
|
use nb::block;
|
||||||
|
use pms700x::Pms700X;
|
||||||
|
use pms700x::Sleep;
|
||||||
|
use std::env::args;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let port = args().skip(1).next().expect("No serial provided");
|
||||||
|
let serial = Serial::open(&PathBuf::from(port)).unwrap();
|
||||||
|
|
||||||
|
let mut pms = Pms700X::new(serial);
|
||||||
|
block!(pms.set_sleeping(Sleep::Sleep)).unwrap();
|
||||||
|
}
|
||||||
14
examples/wake.rs
Normal file
14
examples/wake.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use linux_embedded_hal::Serial;
|
||||||
|
use nb::block;
|
||||||
|
use pms700x::Pms700X;
|
||||||
|
use pms700x::Sleep;
|
||||||
|
use std::env::args;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let port = args().skip(1).next().expect("No serial provided");
|
||||||
|
let serial = Serial::open(&PathBuf::from(port)).unwrap();
|
||||||
|
|
||||||
|
let mut pms = Pms700X::new(serial);
|
||||||
|
block!(pms.set_sleeping(Sleep::Wakeup)).unwrap();
|
||||||
|
}
|
||||||
294
src/lib.rs
294
src/lib.rs
|
|
@ -1,3 +1,297 @@
|
||||||
|
use embedded_hal::serial::{Read, Write};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
pub trait Sealed {}
|
||||||
|
|
||||||
|
impl Sealed for super::Active {}
|
||||||
|
|
||||||
|
impl Sealed for super::Passive {}
|
||||||
|
|
||||||
|
impl Sealed for super::UnInitialized {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Mode: private::Sealed {}
|
||||||
|
|
||||||
|
pub struct UnInitialized;
|
||||||
|
|
||||||
|
pub struct Active;
|
||||||
|
|
||||||
|
pub struct Passive;
|
||||||
|
|
||||||
|
impl Mode for Active {}
|
||||||
|
|
||||||
|
impl Mode for Passive {}
|
||||||
|
|
||||||
|
impl Mode for UnInitialized {}
|
||||||
|
|
||||||
|
const START_HEADER_1: u8 = 0x42;
|
||||||
|
const START_HEADER_2: u8 = 0x4d;
|
||||||
|
|
||||||
|
const MAX_RESPONSE_SIZE: usize = 32;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SensorData {
|
||||||
|
/// Number of >0.3µm particles per 0.1L
|
||||||
|
pm03_concentration: u16,
|
||||||
|
/// Number of >0.5µm particles per 0.1L
|
||||||
|
pm05_concentration: u16,
|
||||||
|
/// Number of >1.0µm particles per 0.1L
|
||||||
|
pm10_concentration: u16,
|
||||||
|
/// Number of >2.5µm particles per 0.1L
|
||||||
|
pm25_concentration: u16,
|
||||||
|
/// Number of >5.0µm particles per 0.1L
|
||||||
|
pm50_concentration: u16,
|
||||||
|
/// Number of >10.0µm particles per 0.1L
|
||||||
|
pm100_concentration: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SensorData {
|
||||||
|
pub fn from_raw(raw: &[u8; MAX_RESPONSE_SIZE]) -> Self {
|
||||||
|
SensorData {
|
||||||
|
pm03_concentration: u16::from_be_bytes([raw[16], raw[17]]),
|
||||||
|
pm05_concentration: u16::from_be_bytes([raw[18], raw[19]]),
|
||||||
|
pm10_concentration: u16::from_be_bytes([raw[20], raw[21]]),
|
||||||
|
pm25_concentration: u16::from_be_bytes([raw[22], raw[23]]),
|
||||||
|
pm50_concentration: u16::from_be_bytes([raw[24], raw[25]]),
|
||||||
|
pm100_concentration: u16::from_be_bytes([raw[26], raw[27]]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SensorReader {
|
||||||
|
byte_offset: u8,
|
||||||
|
length: u8,
|
||||||
|
data: [u8; MAX_RESPONSE_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pms700X<Error, Serial: Read<u8, Error = Error> + Write<u8, Error = Error>, Mode> {
|
||||||
|
serial: Serial,
|
||||||
|
mode: PhantomData<Mode>,
|
||||||
|
command_writer: CommandWriter,
|
||||||
|
reader: SensorReader,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Error, Serial: Read<u8, Error = Error> + Write<u8, Error = Error>>
|
||||||
|
Pms700X<Error, Serial, UnInitialized>
|
||||||
|
{
|
||||||
|
pub fn new(serial: Serial) -> Self {
|
||||||
|
Pms700X {
|
||||||
|
serial,
|
||||||
|
mode: PhantomData,
|
||||||
|
command_writer: CommandWriter::default(),
|
||||||
|
reader: SensorReader::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Error, Serial: Read<u8, Error = Error> + Write<u8, Error = Error>, SensorMode: Mode>
|
||||||
|
Pms700X<Error, Serial, SensorMode>
|
||||||
|
{
|
||||||
|
fn send_command(
|
||||||
|
&mut self,
|
||||||
|
command: Command,
|
||||||
|
data: u16,
|
||||||
|
expect_answer: bool,
|
||||||
|
) -> nb::Result<(), Error> {
|
||||||
|
if self.command_writer.command == Command::None {
|
||||||
|
self.command_writer = CommandWriter::new(command, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.command_writer.write(&mut self.serial)?;
|
||||||
|
if expect_answer {
|
||||||
|
self.reader.fill_data(&mut self.serial)?;
|
||||||
|
}
|
||||||
|
self.command_writer.command = Command::None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_mode<NewMode: Mode>(self) -> Pms700X<Error, Serial, NewMode> {
|
||||||
|
Pms700X {
|
||||||
|
serial: self.serial,
|
||||||
|
mode: PhantomData,
|
||||||
|
command_writer: self.command_writer,
|
||||||
|
reader: self.reader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_active(mut self) -> Result<Pms700X<Error, Serial, Active>, Error> {
|
||||||
|
nb::block!(self.send_command(Command::SetMode, 1, true))?;
|
||||||
|
Ok(self.into_mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_passive(mut self) -> Result<Pms700X<Error, Serial, Passive>, Error> {
|
||||||
|
nb::block!(self.send_command(Command::SetMode, 0, true))?;
|
||||||
|
Ok(self.into_mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sleeping(
|
||||||
|
&mut self,
|
||||||
|
sleeping: Sleep,
|
||||||
|
) -> nb::Result<(), <Serial as Write<u8>>::Error> {
|
||||||
|
self.send_command(Command::SetSleep, sleeping as u16, sleeping == Sleep::Sleep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum Sleep {
|
||||||
|
Sleep = 0,
|
||||||
|
Wakeup = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Error, Serial: Read<u8, Error = Error> + Write<u8, Error = Error>>
|
||||||
|
Pms700X<Error, Serial, Active>
|
||||||
|
{
|
||||||
|
pub fn read(&mut self) -> nb::Result<SensorData, Error> {
|
||||||
|
self.reader.fill_data(&mut self.serial)?;
|
||||||
|
if self.reader.length == 0x1c {
|
||||||
|
Ok(SensorData::from_raw(&self.reader.data))
|
||||||
|
} else {
|
||||||
|
Err(nb::Error::WouldBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Error, Serial: Read<u8, Error = Error> + Write<u8, Error = Error>>
|
||||||
|
Pms700X<Error, Serial, Passive>
|
||||||
|
{
|
||||||
|
pub fn read(&mut self) -> nb::Result<SensorData, Error> {
|
||||||
|
self.send_command(Command::ReadPassive, 0, true)?;
|
||||||
|
Ok(SensorData::from_raw(&self.reader.data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SensorReader {
|
||||||
|
fn fill_data<Serial: Read<u8>>(
|
||||||
|
&mut self,
|
||||||
|
serial: &mut Serial,
|
||||||
|
) -> nb::Result<(), Serial::Error> {
|
||||||
|
let byte = serial.read()?;
|
||||||
|
let offset = self.byte_offset;
|
||||||
|
self.byte_offset += 1;
|
||||||
|
println!("read {}: {:x}", offset, byte);
|
||||||
|
self.data[offset as usize] = byte;
|
||||||
|
match (offset, byte) {
|
||||||
|
(0, START_HEADER_1) => Err(nb::Error::WouldBlock),
|
||||||
|
(0, _) => {
|
||||||
|
// wait until we find the start header
|
||||||
|
self.byte_offset = 0;
|
||||||
|
Err(nb::Error::WouldBlock)
|
||||||
|
}
|
||||||
|
(1, START_HEADER_2) => Err(nb::Error::WouldBlock),
|
||||||
|
(2, 0) => Err(nb::Error::WouldBlock),
|
||||||
|
(2, _) => {
|
||||||
|
// we only allow length <= 32
|
||||||
|
self.byte_offset = 0;
|
||||||
|
Err(nb::Error::WouldBlock)
|
||||||
|
}
|
||||||
|
(3, length_low_byte) => {
|
||||||
|
self.length = length_low_byte;
|
||||||
|
if self.length > MAX_RESPONSE_SIZE as u8 {
|
||||||
|
self.byte_offset = 0;
|
||||||
|
}
|
||||||
|
Err(nb::Error::WouldBlock)
|
||||||
|
}
|
||||||
|
(offset, _) => {
|
||||||
|
if offset >= self.length + 3 {
|
||||||
|
self.byte_offset = 0;
|
||||||
|
if !self.validate_data() {
|
||||||
|
eprintln!("invalid checksum")
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(nb::Error::WouldBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_data(&self) -> bool {
|
||||||
|
let mut sum = 0;
|
||||||
|
for byte in self.data.iter().take(self.length as usize + 2) {
|
||||||
|
sum += *byte as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
let checksum = u16::from_be_bytes([
|
||||||
|
self.data[self.length as usize + 2],
|
||||||
|
self.data[self.length as usize + 3],
|
||||||
|
]);
|
||||||
|
checksum == sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum Command {
|
||||||
|
None = 0,
|
||||||
|
ReadPassive = 0xe2,
|
||||||
|
SetMode = 0xe1,
|
||||||
|
SetSleep = 0xe4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Command {
|
||||||
|
fn default() -> Self {
|
||||||
|
Command::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CommandWriter {
|
||||||
|
command: Command,
|
||||||
|
data_high: u8,
|
||||||
|
data_low: u8,
|
||||||
|
state: u8,
|
||||||
|
verify_high: u8,
|
||||||
|
verify_low: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandWriter {
|
||||||
|
fn new(command: Command, data: u16) -> Self {
|
||||||
|
let data_bytes: [u8; 2] = data.to_le_bytes();
|
||||||
|
let verify = START_HEADER_1 as u16
|
||||||
|
+ START_HEADER_2 as u16
|
||||||
|
+ command as u8 as u16
|
||||||
|
+ data_bytes[0] as u16
|
||||||
|
+ data_bytes[1] as u16;
|
||||||
|
let verify_bytes: [u8; 2] = verify.to_le_bytes();
|
||||||
|
CommandWriter {
|
||||||
|
command,
|
||||||
|
data_high: data_bytes[1],
|
||||||
|
data_low: data_bytes[0],
|
||||||
|
state: 0,
|
||||||
|
verify_high: verify_bytes[1],
|
||||||
|
verify_low: verify_bytes[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<Serial: Write<u8>>(&mut self, serial: &mut Serial) -> nb::Result<(), Serial::Error> {
|
||||||
|
if self.state < 7 {
|
||||||
|
let write_byte = match self.state {
|
||||||
|
0 => START_HEADER_1,
|
||||||
|
1 => START_HEADER_2,
|
||||||
|
2 => self.command as u8,
|
||||||
|
3 => self.data_high,
|
||||||
|
4 => self.data_low,
|
||||||
|
5 => self.verify_high,
|
||||||
|
6 => self.verify_low,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
println!("write {}: {:x}", self.state, write_byte);
|
||||||
|
serial.write(write_byte)?;
|
||||||
|
|
||||||
|
self.state += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.state == 7 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(nb::Error::WouldBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue