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>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[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)]
|
||||
mod tests {
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue