M Cargo.lock => Cargo.lock +49 -51
@@ 130,7 130,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn",
+ "syn 1.0.109",
"which",
]
@@ 179,7 179,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 443,7 443,7 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 460,7 460,7 @@ checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 483,7 483,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 494,7 494,7 @@ checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
dependencies = [
"darling_core",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 517,7 517,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 695,7 695,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 782,6 782,12 @@ dependencies = [
]
[[package]]
+name = "fast-srgb8"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
+
+[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 803,15 809,6 @@ dependencies = [
]
[[package]]
-name = "find-crate"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2"
-dependencies = [
- "toml",
-]
-
-[[package]]
name = "flume"
version = "0.10.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 895,7 892,7 @@ checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 1146,7 1143,7 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "huelia-conductor"
-version = "0.2.0"
+version = "0.3.0"
dependencies = [
"hostname",
"http",
@@ 1164,7 1161,7 @@ dependencies = [
[[package]]
name = "huelia-performer"
-version = "0.2.0"
+version = "0.3.0"
dependencies = [
"anyhow",
"embedded-hal 1.0.0-alpha.9",
@@ 1545,7 1542,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 1580,12 1577,12 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "palette"
-version = "0.6.1"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f9cd68f7112581033f157e56c77ac4a5538ec5836a2e39284e65bd7d7275e49"
+checksum = "e1641aee47803391405d0a1250e837d2336fdddd18b27f3ddb8c1d80ce8d7f43"
dependencies = [
"approx",
- "num-traits",
+ "fast-srgb8",
"palette_derive",
"phf",
"serde",
@@ 1593,14 1590,13 @@ dependencies = [
[[package]]
name = "palette_derive"
-version = "0.6.1"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05eedf46a8e7c27f74af0c9cfcdb004ceca158cb1b918c6f68f8d7a549b3e427"
+checksum = "3c02bfa6b3ba8af5434fa0531bf5701f750d983d4260acd6867faca51cdc4484"
dependencies = [
- "find-crate",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.18",
]
[[package]]
@@ 1645,7 1641,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 1674,7 1670,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 1720,7 1716,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
"version_check",
]
@@ 1737,9 1733,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.52"
+version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
+checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
dependencies = [
"unicode-ident",
]
@@ 1752,9 1748,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
-version = "1.0.26"
+version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
dependencies = [
"proc-macro2",
]
@@ 2093,7 2089,7 @@ checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 2252,7 2248,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 2265,7 2261,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 2280,6 2276,17 @@ dependencies = [
]
[[package]]
+name = "syn"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
name = "tempfile"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 2318,7 2325,7 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 2411,7 2418,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ 2463,15 2470,6 @@ dependencies = [
]
[[package]]
-name = "toml"
-version = "0.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
-dependencies = [
- "serde",
-]
-
-[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 2737,7 2735,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
"wasm-bindgen-shared",
]
@@ 2759,7 2757,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
M Cargo.toml => Cargo.toml +2 -2
@@ 4,13 4,13 @@ default-members = ["conductor"]
[workspace.package]
authors = ["JoJo <jo@jo.zone>"]
-version = "0.2.0"
+version = "0.3.0"
edition = "2021"
[workspace.dependencies]
log = { version = "0.4", features = ["max_level_debug", "release_max_level_info"] }
microcrisp = { git = "https://git.sr.ht/~jojo/microcrisp" }
-palette = "0.6.1"
+palette = "0.7.2"
simple_logger = "4.0"
[patch.crates-io]
M conductor/src/main.rs => conductor/src/main.rs +21 -3
@@ 86,6 86,14 @@ async fn main() {
n_leds.to_string())
.await
.unwrap(),
+ Cmd::SetCurrentLimit { performer, limit_ma } => mqtt_client
+ .publish(
+ format!("huelia/performer/{performer}/set-current-limit"),
+ QoS::ExactlyOnce,
+ false,
+ limit_ma.to_string())
+ .await
+ .unwrap(),
Cmd::Identify(performer) => mqtt_client
.publish(
format!("huelia/performer/{performer}/color"),
@@ 167,6 175,13 @@ async fn main() {
.and(warp::body::json())
.then(move |performer, n_leds| send_cmd1(Cmd::SetNumLeds { performer, n_leds }));
let send_cmd1 = send_cmd.clone();
+ let performer_set_current_limit = warp::post()
+ .and(warp::path("performer"))
+ .and(warp::path::param())
+ .and(warp::path("current-limit"))
+ .and(warp::body::json())
+ .then(move |performer, limit_ma| send_cmd1(Cmd::SetCurrentLimit { performer, limit_ma }));
+ let send_cmd1 = send_cmd.clone();
let performer_identify = warp::post()
.and(warp::path("performer"))
.and(warp::path::param())
@@ 185,6 200,7 @@ async fn main() {
.or(eval)
.or(performer_set_prefix)
.or(performer_set_n_leds)
+ .or(performer_set_current_limit)
.or(performer_identify)
.or(get_performers);
warp::serve(routes).run(([0, 0, 0, 0], 8558))
@@ 205,7 221,7 @@ async fn main() {
}
};
match_expr!(expr, {
- ('online, prefix, i(n_leds), str(ver)) => {
+ ('online, prefix, i(n_leds), str(ver), i(current_limit_ma)) => {
let prefix = match_expr!(prefix, {
('some, str(prefix)) => Some(prefix.to_owned()),
'none => None,
@@ 214,8 230,8 @@ async fn main() {
continue
}
});
- log::info!("performer {} (v{ver}) came online", performer);
- performers.lock().unwrap().insert(performer.to_owned(), Performer { name: performer.to_owned(), prefix, n_leds: *n_leds as usize, version: ver.to_owned() });
+ log::info!("performer {} came online. ver = {ver}, limit = {current_limit_ma} mA", performer);
+ performers.lock().unwrap().insert(performer.to_owned(), Performer { name: performer.to_owned(), prefix, n_leds: *n_leds as usize, version: ver.to_owned(), current_limit_ma: *current_limit_ma as u16 });
},
'offline => {
log::info!("performer {} went offline", performer);
@@ 240,6 256,7 @@ struct Performer {
prefix: Option<String>,
n_leds: usize,
version: String,
+ current_limit_ma: u16,
}
enum Refresh {
@@ 265,6 282,7 @@ enum Cmd {
Cycle(Cycle),
SetNamePrefix { performer: String, prefix: String },
SetNumLeds { performer: String, n_leds: u16 },
+ SetCurrentLimit { performer: String, limit_ma: u16 },
Identify(String),
Eval(f32, Lisp),
}
M conductor/static/main.css => conductor/static/main.css +12 -0
@@ 79,3 79,15 @@ input[type=range] {
margin: 20px;
border-radius: 10px;
}
+#performers > div > section > form {
+ display: flex;
+}
+#performers > div > section > form > label {
+ margin-right: 0.5em;
+}
+#performers > div > section > form > input {
+ text-align: right;
+}
+#performers > div > section > form > .grow {
+ flex-grow: 1;
+}
M conductor/static/main.js => conductor/static/main.js +8 -2
@@ 77,15 77,21 @@ function fetchPerformers() {
let set_prefix = document.createElement("form");
set_prefix.action = `/performer/${performer.name}/prefix`;
set_prefix.addEventListener("submit", postFormSingletonJson);
- set_prefix.innerHTML = `<label for="prefix">prefix</label> <input type="text" name="prefix" value="${performer.prefix}"> <input type="submit" value="set">`;
+ set_prefix.innerHTML = `<label for="prefix">prefix</label> <input type="text" name="prefix" value="${performer.prefix}" class="grow"> <input type="submit" value="set">`;
section.appendChild(set_prefix);
let set_n_leds = document.createElement("form");
set_n_leds.action = `/performer/${performer.name}/n-leds`;
set_n_leds.addEventListener("submit", postFormSingletonJson);
- set_n_leds.innerHTML = `<label for="n-leds">n leds</label> <input type="number" name="n_leds" min="0" max="65535" value="${performer.n_leds}"> <input type="submit" value="set">`;
+ set_n_leds.innerHTML = `<label for="n-leds">n leds</label> <input type="number" name="n_leds" min="0" max="65535" value="${performer.n_leds}" class="grow"> <input type="submit" value="set">`;
section.appendChild(set_n_leds);
+ let set_current_limit = document.createElement("form");
+ set_current_limit.action = `/performer/${performer.name}/current-limit`;
+ set_current_limit.addEventListener("submit", postFormSingletonJson);
+ set_current_limit.innerHTML = `<label for="current-limit">mA limit</label> <input type="number" name="current_limit" min="0" max="5000" value="${performer.current_limit_ma}" class="grow"> <input type="submit" value="set">`;
+ section.appendChild(set_current_limit);
+
let identify = document.createElement("button");
identify.addEventListener("click", (event) => {
fetch(new Request(`/performer/${performer.name}/identify`, {
M performer/Makefile => performer/Makefile +1 -1
@@ 29,7 29,7 @@ force-publish: Cargo.toml release.bin
wait
release.bin: elf-release partitions.csv
- espflash save-image --chip esp32c3 release.bin target/riscv32imc-esp-espidf/release/huelia-performer
+ espflash save-image --chip esp32c3 release.bin ../target/riscv32imc-esp-espidf/release/huelia-performer
debug.bin: elf-debug partitions.csv
espflash save-image --chip esp32c3 debug.bin ../target/riscv32imc-esp-espidf/debug/huelia-performer
M performer/bin/crate-version-lisp.py => performer/bin/crate-version-lisp.py +1 -1
@@ 2,6 2,6 @@
import sys, tomllib
-semver = [int(x) for x in tomllib.load(open("Cargo.toml", "rb"))["package"]["version"].split(".")]
+semver = [int(x) for x in tomllib.load(open("../Cargo.toml", "rb"))["workspace"]["package"]["version"].split(".")]
[major, minor, patch] = semver
print(f"(version {major} {minor} {patch})")
M performer/src/main.rs => performer/src/main.rs +48 -8
@@ 13,7 13,7 @@ use esp_idf_svc::{
// If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use esp_idf_sys::ESP_ERR_NVS_INVALID_LENGTH;
use microcrisp::{match_expr, try_match_expr, Lisp};
-use palette::{rgb::channels, FromColor, Srgb};
+use palette::{rgb::channels, Srgb};
use simple_logger::SimpleLogger;
use smart_leds::SmartLedsWrite;
use std::{
@@ 105,6 105,21 @@ fn main_() -> Result<()> {
}
};
log::info!("n leds: {n_leds}");
+ let current_limit_ma = {
+ let mut buf = [0u8; 2];
+ let default_limit_ma = 250u16;
+ match storage.get_raw("current-limit", &mut buf) {
+ Ok(Some(_)) => u16::from_le_bytes(buf),
+ Ok(None) => default_limit_ma,
+ Err(e) if e.code() == ESP_ERR_NVS_INVALID_LENGTH => {
+ log::error!("invalid length error when reading \"current-limit\" from nvs. The field length might've changed between updates. Setting it to the default value.");
+ storage.set_raw("current-limit", &default_limit_ma.to_le_bytes())?;
+ restart()
+ }
+ Err(e) => Err(e)?,
+ }
+ };
+ log::info!("current limit mA: {current_limit_ma}");
// == HTTP client ==
{
@@ 121,6 136,9 @@ fn main_() -> Result<()> {
let color_chan_full1 = color_chan_full.clone();
let color_chan_full2 = color_chan_full.clone();
+ let ma_per_channel = 20.0f32;
+ let limit_ma_per_led = current_limit_ma as f32 / n_leds as f32;
+
log::info!("starting display thread");
std::thread::Builder::new()
.stack_size(4096)
@@ 132,14 150,16 @@ fn main_() -> Result<()> {
let was_full = color_chan_full1.swap(false, Relaxed);
match color_rx.try_recv() {
Ok(color) => {
- let mut color = palette::Hsl::from_color(color.into_format::<f32>());
- let a_per_led = 0.06;
- if n_leds as f32 * a_per_led * color.lightness > 2.0 {
- color.lightness = 2.0 / (n_leds as f32 * a_per_led);
+ let mut color: Srgb<f32> = color.into_format();
+ let ma_per_led = ma_per_channel * (color.red + color.green + color.blue);
+ if ma_per_led > limit_ma_per_led {
+ color *= limit_ma_per_led / ma_per_led;
}
- let color = Srgb::from_color(color).into_format();
led_strip
- .write(std::iter::repeat(smart_leds::RGB::from(color.into_components())).take(n_leds))
+ .write(
+ std::iter::repeat(smart_leds::RGB::from(color.into_format().into_components()))
+ .take(n_leds),
+ )
.ok();
if was_full {
too_long_delay = delay;
@@ 177,6 197,7 @@ fn main_() -> Result<()> {
log::info!("setting up mqtt");
let storage1 = Arc::new(Mutex::new(storage));
let storage2 = storage1.clone();
+ let storage3 = storage1.clone();
let name_prefix1 = name_prefix.clone();
let color_tx1 = color_tx.clone();
let subscriptions = [
@@ 224,6 245,25 @@ fn main_() -> Result<()> {
}),
),
(
+ format!("huelia/performer/{name}/set-current-limit"),
+ mqtt::QoS::ExactlyOnce,
+ batch_lisp(move |_topic, expr| match expr {
+ Lisp::Int(n) if n < 0 || n > 5000 => Err(anyhow!("current limit (mA) must be 0 < X <= 5000, was {n}")),
+ Lisp::Int(n) if n == current_limit_ma as i64 => {
+ log::info!("already configured for current limit of {n} mA");
+ Ok(())
+ }
+ Lisp::Int(n) => {
+ match storage3.lock().unwrap().set_raw("current-limit", &(n as u16).to_le_bytes()) {
+ Ok(_) => log::info!("set current limit to {n} mA"),
+ Err(e) => log::error!("error storing current limit, {e}"),
+ }
+ restart()
+ }
+ _ => Err(anyhow!("expected integral expression, got {expr}")),
+ }),
+ ),
+ (
format!("huelia/performer/{name}/color"),
mqtt::QoS::AtMostOnce,
batch(move |topic, data| color_handler(topic, data, &color_tx1, &color_chan_full2)),
@@ 237,7 277,7 @@ fn main_() -> Result<()> {
mqtt::connect(
&name,
&format!("mqtt://{}", env!("HUELIA_MQTT_HOST")),
- (name_prefix.as_deref(), n_leds),
+ (name_prefix.as_deref(), n_leds, current_limit_ma),
subscriptions.into_iter(),
)
}
M performer/src/mqtt.rs => performer/src/mqtt.rs +3 -2
@@ 34,7 34,7 @@ pub enum Handler {
pub fn connect<I>(
client_name: &str,
server: &str,
- (prefix, n_leds): (Option<&str>, usize),
+ (prefix, n_leds, current_limit_ma): (Option<&str>, usize, u16),
subscriptions: I,
) -> Result<()>
where
@@ 138,7 138,8 @@ where
&topic_status,
QoS::ExactlyOnce,
true,
- format!("(online {prefix_lisp} {n_leds} {:?})", crate::VERSION_STR).as_bytes(),
+ format!("(online {prefix_lisp} {n_leds} {:?} {current_limit_ma})", crate::VERSION_STR)
+ .as_bytes(),
)
.unwrap();
if let Some((topic, qos)) = topics.next() {