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

use futures_util::future::select_all;

use tokio::select;
use tokio::time::{Duration,delay_for};
use tokio::sync::watch;

use apigpio::{Pin,Connection,GpioChange,Subscription};
use apigpio::GpioMode::*;
use apigpio::Level::*;

use crate::tasktrack;
use anyhow::Context;

pub trait Debounceable : Copy + Clone + Send + Sync {
  type Spec;
  fn pins(spec : &Self::Spec) -> &[Pin];
  fn interpret(pin_states : &[GpioChange]) -> Option<Self>;
  fn delay() -> Duration;
  fn description() -> String;
  fn equivalent(&self, other : &Self) -> bool;
}

type E = anyhow::Error;



async fn changed_internal<T : Debounceable>
  (pins : &mut Vec<Subscription>, vals : &mut Vec<GpioChange>)
   -> Result<Option<T>, E>
{
  Ok(loop {
    let mut futs = Vec::new();
    for p in pins.iter_mut() {
      let fut = p.recv();
      let fut = Box::pin(fut);
      futs.push(fut);
    }
    if let (Some(got), ix, _remain) = select_all(futs).await {
      vals[ix] = got;
      break <T as Debounceable>::interpret(&vals);
    }
  })
}

pub async fn new_debouncer
  <T : 'static + Debounceable>
  (pi : Connection, tt : &mut tasktrack::Tracker,
   spec : &T::Spec, initial : Option<T>)
   -> Result<watch::Receiver<T>,E>
{
  let specs = <T as Debounceable>::pins(spec);
  let mut pins = Vec::with_capacity(specs.len());
  for &p in specs {
    let mut pud = pi.set_pull_up_down(p,Some(H)).await;
    if pi.get_mode(p).await? != Input {
      pi.set_mode(p,Input).await.context("set mode").context(p)?;
    } else {
      // maybe we are poorly configured - eg, pigpiod not informed
      // that ID_* are OK
      if let e @ Err(apigpio::Error::Pi(
        apigpio::constants::PI_NOT_PERMITTED)
      ) = pud {
        println!("warning: pin {}: failed to set pull-up: {:?}", p, &e);
        pud = Ok(());
      }
    }
    pud.context("set pullup").context(p)?;

    let sub = pi.notify_subscribe(p,true,false).await?;
    pins.push(sub);
  }

  let mut vals : Vec<_> = pins.iter().map(|p|{ *p.borrow() }).collect();

  let initial = match initial {
    Some(supplied) => supplied,
    None => loop {
      let got = changed_internal(&mut pins, &mut vals).await?;
      if let Some(good) = got { break good }
    },
  };

  let (osender, oreceiver) = watch::channel(initial);
  let duration = T::delay();

  tt.spawn(format!("debounce tracker {:}", T::description()), async move {
    let mut sent = initial;
    'forever : loop {
      let stable : Option<T> = 'stabilise : loop {
        let mut seen = changed_internal(&mut pins, &mut vals).await?;
        '_reread : loop {
          select!{
            p = changed_internal(&mut pins, &mut vals) => { seen = p? },
            _ = delay_for(duration) => { break 'stabilise seen }
          }
        }
      };
      if let Some(good) = stable {
        if !good.equivalent(&sent) {
          if osender.broadcast(good).is_err() { break 'forever }
          sent = good;
        }
      }
    }
    Ok(())
  });

  Ok(oreceiver)
}