feat: Add basic support for bambu printer
This commit is contained in:
@@ -25,3 +25,4 @@ thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
wakey = { workspace = true }
|
||||
bambulab = { version = "0.4.30", default-features = false }
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
use std::convert::Infallible;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use automation_lib::action_callback::ActionCallback;
|
||||
use automation_lib::device::Device;
|
||||
use automation_macro::{Device, LuaDeviceConfig};
|
||||
use bambulab::client::Client;
|
||||
use bambulab::{Command, Message};
|
||||
use google_home::errors::{self};
|
||||
use google_home::traits::OnOff;
|
||||
use lua_typed::Typed;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::{DebugWrap, LuaDeviceCreate};
|
||||
|
||||
#[derive(Debug, Clone, LuaDeviceConfig, Typed, Default)]
|
||||
#[typed(as = "BambuCallbacks")]
|
||||
pub struct Callbacks {
|
||||
#[device_config(from_lua, default)]
|
||||
#[typed(default)]
|
||||
pub state: ActionCallback<Bambu>,
|
||||
#[device_config(from_lua, default)]
|
||||
#[typed(default)]
|
||||
pub connected: ActionCallback<Bambu>,
|
||||
}
|
||||
crate::register_type!(Callbacks);
|
||||
|
||||
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||
#[typed(as = "BambuConfig")]
|
||||
pub struct Config {
|
||||
pub host: String,
|
||||
pub device_id: String,
|
||||
pub access_code: String,
|
||||
#[device_config(from_lua, default)]
|
||||
pub callbacks: Callbacks,
|
||||
}
|
||||
crate::register_type!(Config);
|
||||
|
||||
#[derive(Debug, Clone, Device)]
|
||||
#[device(traits(OnOff))]
|
||||
pub struct Bambu {
|
||||
config: Config,
|
||||
|
||||
client: DebugWrap<Client>,
|
||||
|
||||
state: Arc<AtomicBool>,
|
||||
}
|
||||
crate::register_device!(Bambu);
|
||||
|
||||
#[async_trait]
|
||||
impl LuaDeviceCreate for Bambu {
|
||||
type Config = Config;
|
||||
type Error = Infallible;
|
||||
|
||||
async fn create(config: Self::Config) -> Result<Self, Infallible> {
|
||||
trace!(id = config.device_id, "Setting up bambu");
|
||||
|
||||
let (tx, mut rx) = tokio::sync::broadcast::channel(25);
|
||||
let client = Client::new(&config.host, &config.access_code, &config.device_id, tx);
|
||||
|
||||
let state = Arc::new(AtomicBool::new(false));
|
||||
let bambu = Self {
|
||||
config,
|
||||
client: DebugWrap(client.clone()),
|
||||
state: state.clone(),
|
||||
};
|
||||
|
||||
tokio::spawn({
|
||||
let mut bambu = bambu.clone();
|
||||
async move {
|
||||
// The printer might be offline so periodically try to reconnecct
|
||||
loop {
|
||||
bambu.client.run().await.ok();
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn({
|
||||
let bambu = bambu.clone();
|
||||
async move {
|
||||
loop {
|
||||
let message = rx.recv().await.unwrap();
|
||||
|
||||
match message {
|
||||
Message::Print(data) => 'print: {
|
||||
// Extract the state of the chamber light
|
||||
let Some(light_report) = data.print.lights_report else {
|
||||
break 'print;
|
||||
};
|
||||
|
||||
let on = light_report
|
||||
.iter()
|
||||
.find(|report| report.node == "chamber_light")
|
||||
.map(|report| report.mode == "on")
|
||||
.unwrap_or(false);
|
||||
|
||||
state.store(on, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
bambu.config.callbacks.state.call(bambu.clone()).await;
|
||||
}
|
||||
Message::Connected => {
|
||||
debug!(id = bambu.config.device_id, "Connected");
|
||||
client.publish(Command::PushAll).await.unwrap();
|
||||
|
||||
bambu.config.callbacks.connected.call(bambu.clone()).await;
|
||||
}
|
||||
// Ignore everything else
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(bambu)
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for Bambu {
|
||||
fn get_id(&self) -> String {
|
||||
self.config.device_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnOff for Bambu {
|
||||
async fn on(&self) -> Result<bool, errors::ErrorCode> {
|
||||
Ok(self.state.load(std::sync::atomic::Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn set_on(&self, on: bool) -> Result<(), errors::ErrorCode> {
|
||||
// NOTE: This will error in case the printer is offline, but we don't really care in that
|
||||
// case so we just ignore the error
|
||||
self.client.publish(Command::SetChamberLight(on)).await.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#![feature(debug_closure_helpers)]
|
||||
#![feature(iter_intersperse)]
|
||||
mod air_filter;
|
||||
mod bambu;
|
||||
mod contact_sensor;
|
||||
mod hue_bridge;
|
||||
mod hue_group;
|
||||
@@ -13,6 +15,9 @@ mod wake_on_lan;
|
||||
mod washer;
|
||||
mod zigbee;
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use automation_lib::Module;
|
||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||
use tracing::{debug, warn};
|
||||
@@ -20,6 +25,31 @@ use tracing::{debug, warn};
|
||||
type DeviceNameFn = fn() -> String;
|
||||
type RegisterDeviceFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::AnyUserData>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DebugWrap<T: Clone>(T);
|
||||
|
||||
impl<T: Clone> DerefMut for DebugWrap<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Deref for DebugWrap<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> fmt::Debug for DebugWrap<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("DebugWrap")
|
||||
.field_with(|f| f.write_str(stringify!(T)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegisteredDevice {
|
||||
name_fn: DeviceNameFn,
|
||||
register_fn: RegisterDeviceFn,
|
||||
|
||||
Reference in New Issue
Block a user