rsiot/components/cmp_raspberrypi_gpio/
fn_process.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::time::Duration;

use rppal::gpio::{Gpio, InputPin, Level, OutputPin};
use tokio::{task::JoinSet, time::sleep};

use crate::{
    executor::CmpInOut,
    message::{Message, MsgDataBound, ServiceBound},
};

use super::{Config, Error, PullMode};

const INPUT_READ_DELAY: Duration = Duration::from_millis(100);

pub async fn fn_process<TMsg, TService>(
    config: Config<TMsg>,
    in_out: CmpInOut<TMsg, TService>,
) -> super::Result<()>
where
    TMsg: MsgDataBound + 'static,
    TService: ServiceBound + 'static,
{
    let gpio = Gpio::new()?;

    let mut task_set: JoinSet<super::Result<()>> = JoinSet::new();

    for input_config in config.inputs {
        let pin = gpio.get(input_config.pin_number)?;
        let pin = match input_config.pull_mode {
            PullMode::Floating => pin.into_input(),
            PullMode::Up => pin.into_input_pullup(),
            PullMode::Down => pin.into_input_pulldown(),
        };
        task_set.spawn(input_pin(pin, input_config.fn_output, in_out.clone()));
    }

    for output_config in config.outputs {
        let pin = gpio.get(output_config.pin_number)?.into_output();
        task_set.spawn(output_pin(
            pin,
            output_config.fn_input,
            in_out.clone(),
            output_config.is_low_triggered,
        ));
    }

    while let Some(res) = task_set.join_next().await {
        res??
    }
    Ok(())
}

/// Функция чтения одного входа
///
/// В данной реализации просто периодически считывает состояние. Если в библиотеке `rppal` появится
/// возможность ожидать переключения в точке await - нужно переделать
async fn input_pin<TMsg, TService>(
    pin: InputPin,
    fn_output: fn(bool) -> Message<TMsg>,
    in_out: CmpInOut<TMsg, TService>,
) -> super::Result<()>
where
    TMsg: MsgDataBound,
    TService: ServiceBound,
{
    let mut prev_level: Option<bool> = None;
    loop {
        let level_read = pin.read();
        let level = match level_read {
            Level::High => true,
            Level::Low => false,
        };
        match prev_level {
            // Функция исполняется в первый раз
            None => {
                prev_level = Some(level);
            }
            // Функция исполняется не в первый раз
            Some(prev_level_value) => {
                // Значение со входа не изменилось
                if prev_level_value == level {
                    sleep(INPUT_READ_DELAY).await;
                    continue;
                // Значение изменилось
                } else {
                    prev_level = Some(level)
                }
            }
        }
        let msg = (fn_output)(level);
        in_out.send_output(msg).await.map_err(Error::CmpOutput)?;
        sleep(INPUT_READ_DELAY).await;
    }
}

/// Функция записи одного выхода
async fn output_pin<TMsg, TService>(
    mut pin: OutputPin,
    fn_input: fn(Message<TMsg>) -> Option<bool>,
    mut in_out: CmpInOut<TMsg, TService>,
    is_low_triggered: bool,
) -> super::Result<()>
where
    TMsg: MsgDataBound,
    TService: ServiceBound,
{
    // Значение по-умолчанию
    if is_low_triggered {
        pin.set_high();
    } else {
        pin.set_low();
    }

    while let Ok(msg) = in_out.recv_input().await {
        let Some(control) = (fn_input)(msg) else {
            continue;
        };
        if is_low_triggered ^ control {
            pin.set_high();
        } else {
            pin.set_low();
        }
    }
    Ok(())
}