Skip to content

Commit

Permalink
add temps (#131)
Browse files Browse the repository at this point in the history
* start to add bambu status

* FINISH

* start to think about temp sensor standards here

* start to implement the temp trait

* add in temp trait for moonraker
  • Loading branch information
paultag authored Oct 17, 2024
1 parent 910b523 commit 75ae3d8
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 56 deletions.
111 changes: 57 additions & 54 deletions src/bin/machine-api/cmd_serve.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{Cli, Config};
use anyhow::Result;
use machine_api::{moonraker, server, AnyMachine};
use machine_api::{server, AnyMachine, TemperatureSensors};
use prometheus_client::{
metrics::gauge::Gauge,
registry::{Registry, Unit},
Expand All @@ -17,54 +17,54 @@ use tokio::sync::RwLock;
///
/// For now we can just do this for moonraker (and maybe one or two others)
/// before we refine the API.
async fn spawn_metrics_moonraker(registry: &mut Registry, key: &str, machine: &moonraker::Client) {
async fn spawn_metrics<TemperatureSensorT>(
registry: &mut Registry,
key: &str,
machine: TemperatureSensorT,
) -> Result<(), TemperatureSensorT::Error>
where
TemperatureSensorT: TemperatureSensors,
TemperatureSensorT: Send,
TemperatureSensorT: 'static,
TemperatureSensorT::Error: Send,
TemperatureSensorT::Error: 'static,
{
let registry = registry.sub_registry_with_label(("id".into(), key.to_owned().into()));

let machine = machine.clone();

let extruder_temperature = Gauge::<f64, AtomicU64>::default();
registry.register_with_unit(
"extruder_temperature",
"Last temp of the extruder",
Unit::Celsius,
extruder_temperature.clone(),
);

let extruder_temperature_target = Gauge::<f64, AtomicU64>::default();
registry.register_with_unit(
"extruder_temperature_target",
"Target temp of the extruder",
Unit::Celsius,
extruder_temperature_target.clone(),
);

let bed_temperature = Gauge::<f64, AtomicU64>::default();
registry.register_with_unit(
"bed_temperature",
"Last temp of the bed",
Unit::Celsius,
bed_temperature.clone(),
);

let bed_temperature_target = Gauge::<f64, AtomicU64>::default();
registry.register_with_unit(
"bed_temperature_target",
"Target temp of the bed",
Unit::Celsius,
bed_temperature_target.clone(),
);
let mut sensors = HashMap::new();

for (sensor_id, sensor_type) in machine.sensors().await? {
let sensor_id_target = format!("{}_target", sensor_id);

sensors.insert(sensor_id.to_owned(), Gauge::<f64, AtomicU64>::default());
sensors.insert(sensor_id_target.clone(), Gauge::<f64, AtomicU64>::default());

registry.register_with_unit(
&sensor_id,
format!("machine-api sensor {} for {}'s {:?}", sensor_id, key, sensor_type),
Unit::Celsius,
sensors.get(&sensor_id).unwrap().clone(),
);

registry.register_with_unit(
&sensor_id_target,
format!(
"machine-api sensor target {} for {}'s {:?}",
sensor_id, key, sensor_type
),
Unit::Celsius,
sensors.get(&sensor_id_target).unwrap().clone(),
);
}

let key = key.to_owned();
tokio::spawn(async move {
let key = key;
let machine = machine;
let extruder_temperature = extruder_temperature;
let extruder_temperature_target = extruder_temperature_target;
let bed_temperature = bed_temperature;
let bed_temperature_target = bed_temperature_target;
let mut machine = machine;
let mut sensors = sensors;

loop {
let Ok(readings) = machine.get_client().temperatures().await else {
let Ok(readings) = machine.poll_sensors().await else {
tracing::warn!("failed to collect temperatures from {}", key);

/* This mega-sucks. I really really *REALLY* hate this. I
Expand All @@ -81,28 +81,31 @@ async fn spawn_metrics_moonraker(registry: &mut Registry, key: &str, machine: &m
* I have no idea what the real fix is, but this ain't it. This
* just stops graphs from lying when the box goes offline. */

extruder_temperature.set(0.0);
extruder_temperature_target.set(0.0);
bed_temperature.set(0.0);
bed_temperature_target.set(0.0);
for (_, gauge) in sensors.iter_mut() {
gauge.set(0.0);
}

continue;
};
tracing::trace!("metrics collected from {}", key);

// TODO: collect last N values and avg?

extruder_temperature.set(*readings.extruder.temperatures.last().unwrap_or(&0.0));
extruder_temperature_target.set(*readings.extruder.targets.last().unwrap_or(&0.0));

if let Some(heater_bed) = readings.heater_bed {
bed_temperature.set(*heater_bed.temperatures.last().unwrap_or(&0.0));
bed_temperature_target.set(*heater_bed.targets.last().unwrap_or(&0.0));
for (sensor_id, sensor_reading) in readings.iter() {
let sensor_id_target = format!("{}_target", sensor_id);
if let Some(gauge) = sensors.get(sensor_id) {
gauge.set(sensor_reading.temperature_celsius);
}
if let Some(gauge) = sensors.get(&sensor_id_target) {
if let Some(target_temperature_celsius) = sensor_reading.target_temperature_celsius {
gauge.set(target_temperature_celsius);
}
}
}

tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
});

Ok(())
}

pub async fn main(_cli: &Cli, cfg: &Config, bind: &str) -> Result<()> {
Expand All @@ -120,7 +123,7 @@ pub async fn main(_cli: &Cli, cfg: &Config, bind: &str) -> Result<()> {

match &any_machine {
AnyMachine::Moonraker(moonraker) => {
spawn_metrics_moonraker(&mut registry, key, moonraker).await;
spawn_metrics(&mut registry, key, moonraker.get_temperature_sensors()).await?;
}
_ => { /* Nothing to do here! */ }
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ pub use slicer::AnySlicer;
pub use sync::SharedMachine;
pub use traits::{
Control, GcodeControl, GcodeSlicer, GcodeTemporaryFile, MachineInfo, MachineMakeModel, MachineState, MachineType,
SuspendControl, ThreeMfControl, ThreeMfSlicer, ThreeMfTemporaryFile,
SuspendControl, TemperatureSensor, TemperatureSensorReading, TemperatureSensors, ThreeMfControl, ThreeMfSlicer,
ThreeMfTemporaryFile,
};

/// A specific file containing a design to be manufactured.
Expand Down
2 changes: 2 additions & 0 deletions src/moonraker/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! This module contains support for printing to moonraker 3D printers.

mod control;
mod temperature;
mod variants;

use anyhow::Result;
pub use control::MachineInfo;
use moonraker::Client as MoonrakerClient;
use serde::{Deserialize, Serialize};
pub use temperature::TemperatureSensors;
pub use variants::MoonrakerVariant;

use crate::{slicer, MachineMakeModel, Volume};
Expand Down
55 changes: 55 additions & 0 deletions src/moonraker/temperature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use super::Client;
use crate::{TemperatureSensor, TemperatureSensorReading, TemperatureSensors as TemperatureSensorsTrait};
use anyhow::Result;
use std::collections::HashMap;

impl Client {
/// Return a handle to read the temperature information from the
/// Moonraker printer.
pub fn get_temperature_sensors(&self) -> TemperatureSensors {
TemperatureSensors {
client: self.client.clone(),
}
}
}

/// Struct to read Temperature values from the 3d printer.
#[derive(Clone)]
pub struct TemperatureSensors {
client: moonraker::Client,
}

impl TemperatureSensorsTrait for TemperatureSensors {
type Error = anyhow::Error;

async fn sensors(&self) -> Result<HashMap<String, TemperatureSensor>> {
Ok(HashMap::from([
("extruder".to_owned(), TemperatureSensor::Extruder),
("bed".to_owned(), TemperatureSensor::Bed),
]))
}

async fn poll_sensors(&mut self) -> Result<HashMap<String, TemperatureSensorReading>> {
let readings = self.client.temperatures().await?;

let mut sensor_readings = HashMap::from([(
"extruder".to_owned(),
TemperatureSensorReading {
temperature_celsius: *readings.extruder.temperatures.last().unwrap_or(&0.0),
target_temperature_celsius: Some(*readings.extruder.targets.last().unwrap_or(&0.0)),
},
)]);

if let Some(heater_bed) = readings.heater_bed {
sensor_readings.insert(
"bed".to_owned(),
TemperatureSensorReading {
temperature_celsius: *heater_bed.temperatures.last().unwrap_or(&0.0),
target_temperature_celsius: Some(*heater_bed.targets.last().unwrap_or(&0.0)),
},
);
}

Ok(sensor_readings)
}
}
43 changes: 42 additions & 1 deletion src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::future::Future;
use std::{collections::HashMap, future::Future};

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -126,6 +126,47 @@ pub trait Control {
fn state(&self) -> impl Future<Output = Result<MachineState, Self::Error>>;
}

/// [TemperatureSensor] indicates the specific part of the machine that the
/// sensor is attached to.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TemperatureSensor {
/// This sensor measures the temperature of the extruder of a
/// FDM printer.
Extruder,

/// This sensor measures the temperature of the print bed.
Bed,

/// This sensor measures the temperature of a 3d print chamber.
Chamber,
}

/// Temperature read from a sensor *ALWAYS IN CELSIUS*!
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct TemperatureSensorReading {
/// The specific temperature value observed on or near the machine.
pub temperature_celsius: f64,

/// If set, the desired temperature that the machine will attempt to
/// stabalize to.
pub target_temperature_celsius: Option<f64>,
}

/// The [TemperatureSensors] trait is implemented on Machines that are capable
/// of reporting thermal state to the caller.
pub trait TemperatureSensors {
/// Error type returned by this trait.
type Error;

/// List all attached Sensors. This must not change during runtime.
fn sensors(&self) -> impl Future<Output = Result<HashMap<String, TemperatureSensor>, Self::Error>> + Send;

/// Poll all sensors returned by [TemperatureSensors::sensors].
fn poll_sensors(
&mut self,
) -> impl Future<Output = Result<HashMap<String, TemperatureSensorReading>, Self::Error>> + Send;
}

/// [ControlGcode] is used by Machines that accept gcode, control commands
/// that are produced from a slicer from a design file.
pub trait GcodeControl
Expand Down

0 comments on commit 75ae3d8

Please sign in to comment.