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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
/*!
Library, and CLI, for [Luxafor](https://luxafor.com/products/) lights via either USB or webhooks.

The main entry point for clients is the trait [Device](trait.Device.html) that has implementations
for USB connected devices such as the [flag](https://luxafor.com/flag-usb-busylight-availability-indicator/)
as well as webhooks for both the flag and [bluetooth](https://luxafor.com/bluetooth-busy-light-availability-indicator/)
lights.

Each connection has its own discovery or connection methods but will provide a `Device` implementation
for the manipulation of the light state.

# API Examples

The following example shows a function that sets the light to a solid red color. It demonstrates
the use of a USB connected device.

```rust,ignore
use luxafor::usb_hid::USBDeviceDiscovery;
use luxafor::{Device, SolidColor, TargetedDevice};
use luxafor::error::Result;

fn set_do_not_disturb() -> Result<()> {
    let discovery = USBDeviceDiscovery::new()?;
    let device = discovery.device()?;
    println!("USB device: '{}'", device.id());
    device.set_specific_led(SpecificLED::AllFront);
    device.set_solid_color(SolidColor::Red, false)
}
```

The following shows the same function but using the webhook connection. Note that the webhook API
is more limited in the features it exposes; it does not support `set_specific_led` for a start.

```rust,ignore
use luxafor::webhook::new_device_for;
use luxafor::{Device, SolidColor};
use luxafor::error::Result;

fn set_do_not_disturb(device_id: &str) -> Result<()> {
    let device = new_device_for(device_id)?;
    println!("Webhook device: '{}'", device.id());
    device.set_solid_color(SolidColor::Red, false)
}
```

# CLI Examples

The following shows the command line tool setting the color to red.

```bash
❯ lux -d 2a0f2c73b72 solid red
```

The following shows the command line tool setting the color to a blinking green. This example uses the environment
variable `LUX_DEVICE` to save repeating the device identifier on each call.

```bash
❯ export LUX_DEVICE=2a0f2c73b72
❯ lux blink green
```

The following shows the command line tool turning the light off.

```bash
❯ lux -vvv -d 2a0f2c73b72 off
 INFO  luxafor > Setting the color of device '2a0f2c73b72e' to 000000
 INFO  luxafor > call successful
```

The following shows the how to set USB connected lights.

```bash
❯ lux -d usb solid red
```

# Features

* **command-line**; provides the command line tool `lux`, it is not on by default for library clients.
* **usb**; provides access to USB connected devices.
* **webhook** (default); provides access to USB, or Bluetooth, devices via webhooks.

*/

#![warn(
    // ---------- Stylistic
    future_incompatible,
    nonstandard_style,
    rust_2018_idioms,
    trivial_casts,
    trivial_numeric_casts,
    // ---------- Public
    missing_debug_implementations,
    missing_docs,
    unreachable_pub,
    // ---------- Unsafe
    unsafe_code,
    // ---------- Unused
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications,
    unused_results,
)]

#[macro_use]
extern crate error_chain;

#[allow(unused_imports)]
#[macro_use]
extern crate log;

use std::fmt::{Display, Formatter};
use std::str::FromStr;

// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------

///
/// A color that the light can be set to.
///
#[derive(Clone, Debug)]
pub enum SolidColor {
    /// A preset color
    Red,
    /// A preset color
    Green,
    /// A preset color
    Yellow,
    /// A preset color
    Blue,
    /// A preset color
    White,
    /// A preset color
    Cyan,
    /// A preset color
    Magenta,
    /// A custom color using standard RGB values
    Custom {
        /// The _red_ channel
        red: u8,
        /// The _green_ channel
        green: u8,
        /// The _blue_ channel
        blue: u8,
    },
}

///
/// Waves produce a pattern that starts at the bottom of the light, fills the light and then
/// fades out at the top.
///
#[derive(Clone, Debug)]
pub enum Wave {
    /// A short transition, completed before the next wave starts.
    Short,
    /// A long transition, completed before the next wave starts.
    Long,
    /// A short transition, which _does not_ complete before the next wave starts.
    OverlappingShort,
    /// A long transition, which _does not_ complete before the next wave starts.
    OverlappingLong,
}

///
/// A pattern the light can be set to show.
///
#[derive(Clone, Debug)]
pub enum Pattern {
    /// A preset pattern that cycles between red and blue.
    Police,
    /// A preset pattern that cycles between green,. yellow, and red.
    TrafficLights,
    /// Preset random patterns
    Random(u8),
    /// A preset pattern
    #[cfg(target_os = "windows")]
    Rainbow,
    /// A preset pattern
    #[cfg(target_os = "windows")]
    Sea,
    /// A preset pattern
    #[cfg(target_os = "windows")]
    WhiteWave,
    /// A preset pattern
    #[cfg(target_os = "windows")]
    Synthetic,
}

///
/// A trait implemented by different access methods to control a light.
///
pub trait Device {
    ///
    /// Return the identifier for the device.
    ///
    fn id(&self) -> String;

    ///
    /// Turn the light off.
    ///
    fn turn_off(&self) -> error::Result<()>;

    ///
    /// Set the light to a continuous solid color.
    ///
    fn set_solid_color(&self, color: SolidColor) -> error::Result<()>;

    ///
    /// Set the light to fade from its current color to a new one.
    ///
    fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> error::Result<()>;

    ///
    /// Strobe the light, this will dim and brighten the same color.
    ///
    fn set_color_strobe(
        &self,
        color: SolidColor,
        strobe_speed: u8,
        repeat_count: u8,
    ) -> error::Result<()>;

    ///
    /// Set the light to repeat one of a pre-defined set of wave patterns.
    ///
    fn set_color_wave(
        &self,
        color: SolidColor,
        wave_pattern: Wave,
        wave_speed: u8,
        repeat_count: u8,
    ) -> error::Result<()>;

    ///
    /// Set the light to repeat one of a pre-defined set of patterns.
    ///
    fn set_pattern(&self, pattern: Pattern, repeat_count: u8) -> error::Result<()>;
}

///
/// Denotes which LED in the light should be the target of any device operations.
///
#[derive(Clone, Debug)]
pub enum SpecificLED {
    /// All supported LEDs
    All,
    /// Only the LEDs on the front (tab) of the light
    AllFront,
    /// Only the LEDs on the back of the light
    AllBack,
    /// Only one specific LED (value: 1..6)
    Number(u8),
}

///
/// Extension trait to allow targeting specific LEDs on the device.
///
pub trait TargetedDevice: Device {
    /// Set the LED to be used for future operations.
    fn set_specific_led(&mut self, led: SpecificLED) -> error::Result<()>;
}

// ------------------------------------------------------------------------------------------------
// Implementations
// ------------------------------------------------------------------------------------------------

impl Display for SolidColor {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        fn to_hex(v: &u8) -> String {
            format!("{:#04x}", v)[2..].to_string()
        }
        write!(
            f,
            "{}",
            match self {
                SolidColor::Red => "red".to_string(),
                SolidColor::Green => "green".to_string(),
                SolidColor::Yellow => "yellow".to_string(),
                SolidColor::Blue => "blue".to_string(),
                SolidColor::White => "white".to_string(),
                SolidColor::Cyan => "cyan".to_string(),
                SolidColor::Magenta => "magenta".to_string(),
                SolidColor::Custom { red, green, blue } =>
                    format!("{}{}{}", to_hex(red), to_hex(green), to_hex(blue)),
            }
        )
    }
}

impl FromStr for SolidColor {
    type Err = error::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.to_lowercase();
        match s.as_str() {
            "red" => Ok(SolidColor::Red),
            "green" => Ok(SolidColor::Green),
            "yellow" => Ok(SolidColor::Yellow),
            "blue" => Ok(SolidColor::Blue),
            "white" => Ok(SolidColor::White),
            "cyan" => Ok(SolidColor::Cyan),
            "magenta" => Ok(SolidColor::Magenta),
            _ => {
                if s.len() == 6 && s.chars().all(|c| c.is_ascii_hexdigit()) {
                    Ok(SolidColor::Custom {
                        red: u8::from_str_radix(&s[0..1], 16)?,
                        green: u8::from_str_radix(&s[2..3], 16)?,
                        blue: u8::from_str_radix(&s[4..5], 16)?,
                    })
                } else {
                    Err(error::ErrorKind::InvalidColor.into())
                }
            }
        }
    }
}

// ------------------------------------------------------------------------------------------------

impl Display for Wave {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Wave::Short => "short",
                Wave::Long => "long",
                Wave::OverlappingShort => "overlapping short",
                Wave::OverlappingLong => "overlapping long",
            }
        )
    }
}

impl FromStr for Wave {
    type Err = error::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.to_lowercase();
        match s.as_str() {
            "short" => Ok(Wave::Short),
            "long" => Ok(Wave::Long),
            "overlapping short" => Ok(Wave::OverlappingShort),
            "overlapping long" => Ok(Wave::OverlappingLong),
            _ => Err(error::ErrorKind::InvalidPattern.into()),
        }
    }
}

// ------------------------------------------------------------------------------------------------

impl Display for Pattern {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Pattern::Police => "police".to_string(),
                Pattern::TrafficLights => "traffic lights".to_string(),
                Pattern::Random(n) => format!("random {}", n),
                #[cfg(target_os = "windows")]
                Pattern::Rainbow => "rainbow".to_string(),
                #[cfg(target_os = "windows")]
                Pattern::Sea => "sea".to_string(),
                #[cfg(target_os = "windows")]
                Pattern::WhiteWave => "white wave".to_string(),
                #[cfg(target_os = "windows")]
                Pattern::Synthetic => "synthetic".to_string(),
            }
        )
    }
}

impl FromStr for Pattern {
    type Err = error::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.to_lowercase();
        match s.as_str() {
            "police" => Ok(Pattern::Police),
            "traffic lights" => Ok(Pattern::TrafficLights),
            "random 1" => Ok(Pattern::Random(1)),
            "random 2" => Ok(Pattern::Random(2)),
            "random 3" => Ok(Pattern::Random(3)),
            "random 4" => Ok(Pattern::Random(4)),
            "random 5" => Ok(Pattern::Random(5)),
            #[cfg(target_os = "windows")]
            "rainbow" => Ok(Pattern::Rainbow),
            #[cfg(target_os = "windows")]
            "sea" => Ok(Pattern::Sea),
            #[cfg(target_os = "windows")]
            "white wave" => Ok(Pattern::WhiteWave),
            #[cfg(target_os = "windows")]
            "synthetic" => Ok(Pattern::Synthetic),
            _ => Err(error::ErrorKind::InvalidPattern.into()),
        }
    }
}

// ------------------------------------------------------------------------------------------------

impl Display for SpecificLED {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                SpecificLED::All => "all".to_string(),
                SpecificLED::AllFront => "front".to_string(),
                SpecificLED::AllBack => "back".to_string(),
                SpecificLED::Number(n) => n.to_string(),
            }
        )
    }
}

impl FromStr for SpecificLED {
    type Err = error::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.to_lowercase();
        match s.as_str() {
            "all" => Ok(SpecificLED::All),
            "front" => Ok(SpecificLED::AllFront),
            "back" => Ok(SpecificLED::AllBack),
            "1" => Ok(SpecificLED::Number(1)),
            "2" => Ok(SpecificLED::Number(2)),
            "3" => Ok(SpecificLED::Number(3)),
            "4" => Ok(SpecificLED::Number(4)),
            "5" => Ok(SpecificLED::Number(5)),
            "6" => Ok(SpecificLED::Number(6)),
            _ => Err(error::ErrorKind::InvalidLED.into()),
        }
    }
}

// ------------------------------------------------------------------------------------------------
// Modules
// ------------------------------------------------------------------------------------------------

///
/// Error handling types.
///
#[allow(missing_docs)]
pub mod error {
    error_chain! {
        errors {
            #[doc("The color value supplied was not recognized")]
            InvalidColor {
                description("The color value supplied was not recognized")
                display("The color value supplied was not recognized")
            }
            #[doc("The pattern value supplied was not recognized")]
            InvalidPattern {
                description("The pattern value supplied was not recognized")
                display("The pattern value supplied was not recognized")
            }
            #[doc("The LED number is either invalid or not supported by the connected device")]
            InvalidLED {
                description("The LED number is either invalid or not supported by the connected device")
                display("The LED number is either invalid or not supported by the connected device")
            }
            #[doc("The provided device ID was incorrectly formatted")]
            InvalidDeviceID {
                description("The provided device ID was incorrectly formatted")
                display("The provided device ID was incorrectly formatted")
            }
            #[doc("No device was discovered, or the ID did not resolve to a device")]
            DeviceNotFound {
                description("No device was discovered, or the ID did not resolve to a device")
                display("No device was discovered, or the ID did not resolve to a device")
            }
            #[doc("The server indicated an invalid request")]
            InvalidRequest {
                description("The server indicated an invalid request")
                display("The server indicated an invalid request")
            }
            #[doc("An unexpected HTTP error was returned")]
            UnexpectedError(sc: u16) {
                description("An unexpected HTTP error was returned")
                display("An unexpected HTTP error was returned: {}", sc)
            }
            #[doc("The command is not supported by the current device, or connection to the device")]
            UnsupportedCommand {
                description("The command is not supported by the current device, or connection to the device")
                display("The command is not supported by the current device, or connection to the device")
            }
        }
        foreign_links {
            CustomFmt(::std::num::ParseIntError);
            Request(::reqwest::Error);
            Fmt(::std::fmt::Error);
        }
    }
}

#[cfg(feature = "usb")]
pub mod usb_hid;

#[cfg(feature = "webhook")]
pub mod webhook;