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
126
127
128
129
130
131
132
133
134
135
136
137
138
139

use std::time::SystemTime;

#[macro_use]
extern crate tracing;
use tracing::{span, Level};

extern crate structopt;
use structopt::StructOpt;

extern crate serde;
use serde::{Serialize, Deserialize};

extern crate toml;

extern crate humantime;
use humantime::Duration;

extern crate streamdeck;
use streamdeck::{StreamDeck, Error as DeckError};

pub mod automata;
pub use automata::*;

/// Automation configuration options
#[derive(StructOpt)]
pub struct Options {
    #[structopt(long, default_value="100ms", env="BLOCK_PERIOD")]
    /// Period for blocking polling of input device
    block_period: Duration,

    #[structopt(long, default_value="5m", env="POLL_PERIOD")]
    /// Period for running automata update functions
    poll_period: Duration,
}

#[derive(Debug)]
pub enum Error {
    Io(std::io::Error),
    Toml(toml::de::Error),
    Deck(streamdeck::Error),
    InvalidState,
    TooManyAutomata,
}

/// Automation object contains a collection of automata
#[derive(Debug, Serialize, Deserialize)]
pub struct Automation {
    automata: Vec<Automata>,
}

impl Automation {
    /// Load an automation from a file
    pub fn load(file: &str) -> Result<Self, Error> {
        let d = std::fs::read_to_string(file).map_err(Error::Io)?;
        let a = toml::from_str(&d).map_err(Error::Toml)?;

        Ok(a)
    }

    /// Execute an automation
    pub fn run(&mut self, deck: &mut StreamDeck, opts: Options) -> Result<(), Error> {
        debug!("Executing automation");

        if self.automata.len() > deck.kind().keys() as usize {
            error!("Specified number of automata exceed available keys");
            return Err(Error::TooManyAutomata)
        }

        // Initialise automata
        for i in 0..self.automata.len() {
            let span = span!(Level::INFO, "automata", index = i);
            let _guard = span.enter();

            let a = &mut self.automata[i];

            // Init object
            a.on_init(i as u8, deck)?;

            // Render object
            a.render(i as u8, deck)?;
        }

        let mut last_update = SystemTime::now();

        // Run loop
        loop {
            // Poll for button presses
            let buttons = match deck.read_buttons(Some(*opts.block_period)) {
                Ok(b) => Some(b),
                Err(DeckError::NoData) => None,
                Err(e) => return Err(Error::Deck(e)),
            };

            // Handle button presses
            if let Some(b) = &buttons {
                for i in 0..self.automata.len() {
                    let span = span!(Level::INFO, "automata", index = i);
                    let _guard = span.enter();

                    let a = &mut self.automata[i];

                    if b[i] != 0 {
                        a.on_press(i as u8, deck)?;
                    }
                }
            }

             // Handle periodic updates
             let now = SystemTime::now();
             if now.duration_since(last_update).unwrap() > *opts.poll_period {

                for i in 0..self.automata.len() {
                    let span = span!(Level::INFO, "automata", index = i);
                    let _guard = span.enter();
    
                    let a = &mut self.automata[i];

                    a.on_poll(i as u8, deck)?;

                }
                
                last_update = now;
             }
        }
    }
}


#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn load_automation() {
        let a = Automation::load("example.toml")
            .expect("error loading automation");
    }
}