capsules_extra/
chirp_i2c_moisture.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Chirp I2C Soil moisture sensor using the I2C bus.
6//!
7//! <https://www.tindie.com/products/2330/>
8//! <https://github.com/Miceuz/i2c-moisture-sensor/blob/master/README.md>
9//!
10
11use core::cell::Cell;
12use kernel::hil::i2c::{self, I2CClient, I2CDevice};
13use kernel::hil::sensors::{MoistureClient, MoistureDriver};
14use kernel::utilities::cells::{OptionalCell, TakeCell};
15use kernel::ErrorCode;
16
17pub const BUFFER_SIZE: usize = 2;
18
19const GET_CAPACITANCE: u8 = 0x00;
20#[allow(dead_code)]
21const SET_ADDRESS: u8 = 0x01;
22#[allow(dead_code)]
23const GET_ADDRESS: u8 = 0x02;
24#[allow(dead_code)]
25const MEASURE_LIGHT: u8 = 0x03;
26#[allow(dead_code)]
27const GET_LIGHT: u8 = 0x04;
28#[allow(dead_code)]
29const GET_TEMPERATURE: u8 = 0x05;
30#[allow(dead_code)]
31const RESET: u8 = 0x06;
32#[allow(dead_code)]
33const GET_VERSION: u8 = 0x07;
34#[allow(dead_code)]
35const SLEEP: u8 = 0x08;
36#[allow(dead_code)]
37const GET_BUSY: u8 = 0x09;
38
39#[derive(Clone, Copy, PartialEq)]
40enum DeviceState {
41    Normal,
42    StartMoisture,
43    FinalMoisture,
44}
45
46#[allow(dead_code)]
47#[derive(Clone, Copy, PartialEq)]
48enum Operation {
49    None,
50    Moisture,
51}
52
53pub struct ChirpI2cMoisture<'a, I: I2CDevice> {
54    buffer: TakeCell<'static, [u8]>,
55    i2c: &'a I,
56    moisture_client: OptionalCell<&'a dyn MoistureClient>,
57    state: Cell<DeviceState>,
58    op: Cell<Operation>,
59}
60
61impl<'a, I: I2CDevice> ChirpI2cMoisture<'a, I> {
62    pub fn new(i2c: &'a I, buffer: &'static mut [u8]) -> Self {
63        ChirpI2cMoisture {
64            buffer: TakeCell::new(buffer),
65            i2c,
66            moisture_client: OptionalCell::empty(),
67            state: Cell::new(DeviceState::Normal),
68            op: Cell::new(Operation::None),
69        }
70    }
71}
72
73impl<'a, I: I2CDevice> MoistureDriver<'a> for ChirpI2cMoisture<'a, I> {
74    fn set_client(&self, client: &'a dyn MoistureClient) {
75        self.moisture_client.set(client);
76    }
77
78    fn read_moisture(&self) -> Result<(), ErrorCode> {
79        if self.state.get() != DeviceState::Normal {
80            return Err(ErrorCode::BUSY);
81        }
82
83        if self.op.get() != Operation::None {
84            return Err(ErrorCode::BUSY);
85        }
86
87        self.buffer.take().map_or(Err(ErrorCode::BUSY), |buffer| {
88            buffer[0] = GET_CAPACITANCE;
89
90            self.op.set(Operation::Moisture);
91            self.state.set(DeviceState::StartMoisture);
92            if let Err((e, buf)) = self.i2c.write_read(buffer, 1, 2) {
93                self.buffer.replace(buf);
94                return Err(e.into());
95            }
96
97            Ok(())
98        })
99    }
100}
101
102impl<I: I2CDevice> I2CClient for ChirpI2cMoisture<'_, I> {
103    fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) {
104        if let Err(i2c_err) = status {
105            self.buffer.replace(buffer);
106
107            match self.op.get() {
108                Operation::None => (),
109                Operation::Moisture => {
110                    self.op.set(Operation::None);
111
112                    self.moisture_client
113                        .map(|client| client.callback(Err(i2c_err.into())));
114                }
115            }
116
117            return;
118        }
119
120        match self.state.get() {
121            DeviceState::StartMoisture => match self.op.get() {
122                Operation::None => (),
123                Operation::Moisture => {
124                    self.state.set(DeviceState::FinalMoisture);
125                    buffer[0] = GET_CAPACITANCE;
126                    if let Err((e, buf)) = self.i2c.write_read(buffer, 1, 2) {
127                        self.buffer.replace(buf);
128                        self.op.set(Operation::None);
129
130                        self.moisture_client
131                            .map(|client| client.callback(Err(e.into())));
132                    }
133                }
134            },
135            DeviceState::FinalMoisture => {
136                match self.op.get() {
137                    Operation::None => (),
138                    Operation::Moisture => {
139                        let capacitance = (((buffer[0] as u32) << 8) | (buffer[1] as u32)) as f32;
140
141                        // 240 is the rough capacitance in air
142                        // 400 is the rough capacitance in wet soil
143                        // Use those to calculate the moisture percentage, which is rougly linear
144                        // https://github.com/Miceuz/i2c-moisture-sensor/blob/master/README.md#how-to-interpret-the-readings
145                        // Note that this gives moisture in hundredths of a percent
146                        let moisture_content = ((capacitance - 240.0) / (400.0 - 240.0)) * 10000.0;
147
148                        self.state.set(DeviceState::Normal);
149                        self.buffer.replace(buffer);
150                        self.op.set(Operation::None);
151
152                        self.moisture_client
153                            .map(|client| client.callback(Ok(moisture_content as usize)));
154                    }
155                }
156            }
157            DeviceState::Normal => {}
158        }
159    }
160}