use std::collections::HashMap;
use std::error::Error;
use std::net::SocketAddr;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use tokio::net::{tcp::OwnedReadHalf, TcpListener, TcpStream};
use tokio::sync::{mpsc, Mutex};
use tokio::task::spawn;
#[cfg(feature = "from-str")]
use itertools::Itertools;
mod reader;
mod writer;
pub use reader::{BulbError, Notification, Response};
use reader::{NotifyChan, Reader};
use writer::Writer;
#[derive(Debug)]
struct Message(u64, String);
pub struct Bulb {
notify_chan: NotifyChan,
writer: writer::Writer,
}
impl Bulb {
pub fn attach(stream: ::std::net::TcpStream) -> Result<Self, Box<dyn Error>> {
let stream = TcpStream::from_std(stream)?;
Ok(Self::attach_tokio(stream))
}
pub fn attach_tokio(stream: TcpStream) -> Self {
let (reader, writer, reader_half, notify_chan) = Self::build_rw(stream);
spawn(reader.start(reader_half));
Self {
notify_chan,
writer,
}
}
pub async fn connect(addr: &str, mut port: u16) -> Result<Self, Box<dyn Error>> {
if port == 0 {
port = 55443
}
let stream = TcpStream::connect(format!("{}:{}", addr, port)).await?;
let (reader, writer, reader_half, notify_chan) = Self::build_rw(stream);
spawn(reader.start(reader_half));
Ok(Self {
notify_chan,
writer,
})
}
fn build_rw(stream: TcpStream) -> (Reader, Writer, OwnedReadHalf, NotifyChan) {
let (reader_half, writer_half) = stream.into_split();
let resp_chan = HashMap::new();
let resp_chan = Arc::new(Mutex::new(resp_chan));
let notify_chan = Arc::new(Mutex::new(None));
let reader = Reader::new(resp_chan.clone(), notify_chan.clone());
let writer = Writer::new(writer_half, resp_chan);
(reader, writer, reader_half, notify_chan)
}
pub fn no_response(mut self) -> Self {
self.writer.set_get_response(false);
self
}
pub fn get_response(mut self) -> Self {
self.writer.set_get_response(true);
self
}
pub async fn set_notify(&mut self, chan: mpsc::Sender<Notification>) {
self.notify_chan.lock().await.replace(chan);
}
pub async fn get_notify(&mut self) -> mpsc::Receiver<Notification> {
let (sender, receiver) = mpsc::channel(10);
self.set_notify(sender).await;
receiver
}
pub async fn start_music(&mut self, host: &str, port: u16) -> Result<Self, Box<dyn Error>> {
let addr = format!("127.0.0.1:{}", port).parse::<SocketAddr>()?;
let mut listener = TcpListener::bind(&addr).await?;
self.set_music(MusicAction::On, host, port).await?;
let (socket, _) = listener.accept().await?;
Ok(Self::attach_tokio(socket).no_response())
}
}
#[cfg(feature = "from-str")]
#[derive(Debug)]
pub struct ParseError(String);
#[cfg(feature = "from-str")]
impl ToString for ParseError {
fn to_string(&self) -> String {
self.0.to_string()
}
}
#[cfg(feature = "from-str")]
impl From<::std::num::ParseIntError> for ParseError {
fn from(e: ::std::num::ParseIntError) -> Self {
ParseError(e.to_string())
}
}
trait Stringify {
fn stringify(&self) -> String;
}
impl Stringify for str {
fn stringify(&self) -> String {
format!("\"{}\"", self)
}
}
macro_rules! stringify_nums {
($($type:ty),*) => {
$(
impl Stringify for $type {
fn stringify(&self) -> String {
self.to_string()
}
}
)*
};
}
stringify_nums!(u8, u16, u32, u64, i8);
macro_rules! enum_str {
($name:ident: $($variant:ident -> $val:literal),* $(,)?) => {
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum $name {
$($variant),*
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
$($name::$variant => write!(f, stringify!($val)),)+
}
}
}
impl Stringify for $name {
fn stringify(&self) -> String {
self.to_string()
}
}
#[cfg(feature="from-str")]
impl ::std::str::FromStr for $name {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self,Self::Err> {
match s {
$(stringify!($variant) |
_ if s.eq_ignore_ascii_case(stringify!($variant)) => Ok($name::$variant),)+
_ => Err(ParseError({
let v = vec![
$(stringify!($variant),)+
];
format!("Could not parse {} \n Valid values:{}", s,
v.iter().fold(String::new(), |a, i| {
a + &format!(" {}", i)[..]
}))
}))
}
}
}
#[cfg(feature="from-str")]
impl $name {
#[allow(dead_code)]
pub fn variants() -> Vec<&'static str> {
vec![
$(stringify!($variant),)+
]
}
}
};
}
enum_str!(Property:
Power -> "power",
Bright -> "bright",
CT -> "ct",
RGB -> "rgb",
Hue -> "hue",
Sat -> "sat",
ColorMode -> "color_mode",
Flowing -> "flowing",
DelayOff -> "delayoff",
FlowParams -> "flow_params",
MusicOn -> "music_on",
Name -> "name",
BgPower -> "bg_power",
BgFlowing -> "bg_flowing",
BgFlowParams -> "bg_flow_params",
BgCT -> "bg_ct",
BgColorMode -> "bg_lmode",
BgBright -> "bg_bright",
BgRGB -> "bg_rgb",
BgHue -> "bg_hue",
BgSat -> "bg_sat",
NightLightBright -> "nl_br",
ActiveMode -> "active_mode",
);
enum_str!(Power:
On -> "on",
Off -> "off",
);
enum_str!(Effect:
Sudden -> "sudden",
Smooth -> "smooth",
);
enum_str!(Prop:
Bright -> "bright",
CT -> "ct",
Color -> "color",
);
enum_str!(Class:
Color -> "color",
HSV -> "hsv",
CT -> "ct",
CF -> "cf",
AutoDelayOff -> "auto_delay_off",
);
enum_str!(Mode:
Normal -> 0,
CT -> 1,
RGB -> 2,
HSV -> 3,
CF -> 4,
NightLight -> 5,
);
enum_str!(CronType:
Off -> 0,
);
enum_str!(CfAction:
Recover -> 0,
Stay -> 1,
Off -> 2,
);
enum_str!(AdjustAction:
Increase -> "increase",
Decrease -> "decrease",
Circle -> "circle",
);
enum_str!(MusicAction:
Off -> 0,
On -> 1,
);
enum_str!(FlowMode:
Color -> 1,
CT -> 2,
Sleep -> 7,
);
#[derive(Debug, Serialize, Deserialize)]
pub struct FlowTuple {
pub duration: u64,
pub mode: FlowMode,
pub value: u32,
pub brightness: i8,
}
impl FlowTuple {
pub fn new(duration: u64, mode: FlowMode, value: u32, brightness: i8) -> Self {
Self {
duration,
mode,
value,
brightness,
}
}
pub fn rgb(duration: u64, rgb: u32, brightness: i8) -> Self {
Self {
duration,
mode: FlowMode::Color,
value: rgb,
brightness,
}
}
pub fn ct(duration: u64, ct: u32, brightness: i8) -> Self {
Self {
duration,
mode: FlowMode::CT,
value: ct,
brightness,
}
}
pub fn sleep(duration: u64) -> Self {
Self {
duration,
mode: FlowMode::Sleep,
value: 0,
brightness: -1,
}
}
}
impl ToString for FlowTuple {
fn to_string(&self) -> String {
format!(
"{},{},{},{}",
self.duration,
self.mode.to_string(),
self.value,
self.brightness
)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FlowExpresion(pub Vec<FlowTuple>);
impl Stringify for FlowExpresion {
fn stringify(&self) -> String {
let mut s = '"'.to_string();
for tuple in self.0.iter() {
s.push_str(&tuple.to_string());
s.push(',');
}
s.pop();
s.push('"');
s
}
}
#[cfg(feature = "from-str")]
impl ::std::str::FromStr for FlowExpresion {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut v = Vec::new();
for (duration, mode, value, brightness) in s.split(',').tuples() {
let duration = duration.parse::<u64>()?;
let value = value.parse::<u32>()?;
let mode = match FlowMode::from_str(mode) {
Ok(m) => Ok(m),
Err(_) => match mode {
"1" => Ok(FlowMode::Color),
"2" => Ok(FlowMode::CT),
"7" => Ok(FlowMode::Sleep),
_ => Err(ParseError(format!(
"Could not parse FlowMode: {}\nvalid values: 1 (Color), 2(CT), 7(Sleep)",
mode.to_string()
))),
},
}?;
let brightness = brightness.parse::<i8>()?;
v.push(FlowTuple {
duration,
mode,
value,
brightness,
});
}
Ok(FlowExpresion(v))
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Properties(pub Vec<Property>);
impl Stringify for Properties {
fn stringify(&self) -> String {
self.0
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join(",")
}
}
macro_rules! params {
($($v:tt),+) => {
vec!( $( $v.stringify() ),+ ).join(", ")
};
() => {""};
}
macro_rules! gen_func {
($name:ident - $( $p:ident : $t:ty ),* ) => {
pub async fn $name(&mut self, $($p : $t),*) -> Result<Option<Response>, BulbError> {
self.writer.send(
&stringify!($name), ¶ms!($($p),*)
).await
}
};
($fn_default:ident / $fn_bg:ident - $( $p:ident : $t:ty ),* ) => {
gen_func!($fn_default - $($p : $t),*);
gen_func!($fn_bg - $($p : $t),*);
};
($name:ident) => { gen_func!($name - ); };
($fn_default:ident / $fn_bg:ident) => { gen_func!($fn_default / $fn_bg - ); };
}
#[rustfmt::skip]
impl Bulb {
gen_func!(get_prop - properties: &Properties);
gen_func!(set_power / bg_set_power - power: Power, effect: Effect, duration: u64, mode: Mode);
gen_func!(toggle / bg_toggle);
gen_func!(dev_toggle);
gen_func!(set_ct_abx / bg_set_ct_abx - ct_value: u16, effect: Effect, duration: u64);
gen_func!(set_rgb / bg_set_rgb - rgb_value: u32, effect: Effect, duration: u64);
gen_func!(set_hsv / bg_set_hsv - hue: u16, sat: u8, effect: Effect, duration: u64);
gen_func!(set_bright / bg_set_bright - brightness: u8, effect: Effect, duration: u64);
gen_func!(set_scene / bg_set_scene - class: Class, val1: u64, val2: u64, val3: u64);
gen_func!(start_cf / bg_start_cf - count: u8, action: CfAction, flow_expression: FlowExpresion);
gen_func!(stop_cf / bg_stop_cf);
gen_func!(set_adjust / bg_set_adjust - action: AdjustAction, prop: Prop);
gen_func!(adjust_bright / bg_adjust_bright - percentage: i8, duration: u64);
gen_func!(adjust_ct / bg_adjust_ct - percentage: i8, duration: u64);
gen_func!(adjust_color / bg_adjust_color - percentage: i8, duration: u64);
gen_func!(set_default / bg_set_default);
gen_func!(set_name - name: &str);
gen_func!(set_music - action: MusicAction, host: &str, port: u16);
gen_func!(cron_add - cron_type: CronType, value: u64);
gen_func!(cron_del - cron_type: CronType);
pub async fn cron_get(&mut self, _cron_type: CronType) -> Result<Option<Response>, BulbError> {
self.get_prop(&Properties(vec![Property::DelayOff])).await
}
}