feat(rust): add shared Signal client and workspace configuration

This commit is contained in:
Richie committed 2026-10-11 20:11:21 -04:00
1 parent da1976f7ce
commit df045d4c63
9 files changed
+508 -23

No files matched your search

+10 -11
View File
@@ -6,16 +6,15 @@ description = "Fetch privacy-masked weather for a van and publish it to Home Ass
license = "MIT"
[dependencies]
anyhow = "1.0"
chrono = "0.4"
clap = { version = "4.5", features = ["derive", "env"] }
env_logger = "0.11"
log = "0.4"
anyhow.workspace = true
chrono.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
env_logger.workspace = true
log.workspace = true
num-traits = "0.2"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[lints.clippy]
all = "deny"
pedantic = "deny"
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
[lints]
workspace = true
+19 -3
View File
@@ -1,10 +1,26 @@
{ rustPlatform }:
{ lib, rustPlatform }:
rustPlatform.buildRustPackage {
pname = "van-weather";
version = "0.1.0";
src = ./.;
src = lib.fileset.toSource {
root = ../..;
fileset = lib.fileset.unions [
../../Cargo.toml
../../Cargo.lock
../../clippy.toml
../.
];
};
cargoLock.lockFile = ./Cargo.lock;
cargoLock.lockFile = ../../Cargo.lock;
cargoBuildFlags = [
"-p"
"van-weather"
];
cargoTestFlags = [
"-p"
"van-weather"
];
meta = {
description = "Privacy-masked van weather publisher for Home Assistant";
+34 -9
View File
@@ -4,6 +4,7 @@ use anyhow::{Context, Result, bail};
use chrono::{DateTime, Utc};
use clap::Parser;
use log::{error, info};
use num_traits::ToPrimitive;
use reqwest::{
StatusCode, Url,
blocking::{Client, ClientBuilder},
@@ -25,7 +26,7 @@ const fn decimal_factor(decimals: u32) -> f64 {
let mut remaining = decimals;
while remaining > 0 {
factor *= 10.0;
remaining -= 1;
remaining = remaining.saturating_sub(1);
}
factor
}
@@ -468,18 +469,25 @@ fn weather_updates(weather: &Weather) -> Vec<(String, Value)> {
}
fn sensor(entity_id: &str, state: Option<Value>, attributes: Value) -> Option<(String, Value)> {
state.map(|state| {
let mut payload = serde_json::Map::new();
payload.insert("state".to_owned(), state);
payload.insert("attributes".to_owned(), attributes);
(entity_id.to_owned(), Value::Object(payload))
})
let state = state?;
let mut payload = serde_json::Map::new();
payload.insert("state".to_owned(), state);
payload.insert("attributes".to_owned(), attributes);
Some((entity_id.to_owned(), Value::Object(payload)))
}
#[allow(clippy::cast_possible_truncation)]
fn percent(value: Option<f64>) -> i64 {
// Preserve Python's int(probability * 100) behavior for Home Assistant.
(value.unwrap_or_default() * 100.0) as i64
let value = value.unwrap_or_default() * 100.0;
value.to_i64().unwrap_or_else(|| {
if value.is_nan() {
0
} else if value.is_sign_negative() {
i64::MIN
} else {
i64::MAX
}
})
}
#[cfg(test)]
@@ -562,4 +570,21 @@ mod tests {
assert!((mask_coordinate(37.7749) - 37.8).abs() < f64::EPSILON);
assert!((mask_coordinate(-122.4194) - (-122.4)).abs() < f64::EPSILON);
}
#[test]
fn percentages_truncate_and_preserve_saturation_for_extreme_values() {
for (value, expected) in [
(None, 0),
(Some(0.129), 12),
(Some(-0.129), -12),
(Some(1.0), 100),
(Some(f64::NAN), 0),
(Some(f64::MAX), i64::MAX),
(Some(f64::MIN), i64::MIN),
(Some(f64::INFINITY), i64::MAX),
(Some(f64::NEG_INFINITY), i64::MIN),
] {
assert_eq!(percent(value), expected);
}
}
}