Update
This commit is contained in:
parent
76b43a0386
commit
220a446fb6
21 changed files with 677 additions and 111 deletions
167
Cargo.lock
generated
167
Cargo.lock
generated
|
@ -548,6 +548,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
|
@ -666,7 +675,7 @@ version = "0.99.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"convert_case 0.4.0",
|
||||
"proc-macro2 1.0.89",
|
||||
"quote 1.0.37",
|
||||
"rustc_version",
|
||||
|
@ -884,6 +893,17 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -1204,10 +1224,10 @@ dependencies = [
|
|||
"http 1.1.0",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
@ -1537,9 +1557,9 @@ dependencies = [
|
|||
"chrono",
|
||||
"chrono-tz",
|
||||
"cron",
|
||||
"nutype",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"validated_newtype",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1553,16 +1573,42 @@ dependencies = [
|
|||
"clap",
|
||||
"cron",
|
||||
"env_logger",
|
||||
"envconfig",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"jsonwebtoken",
|
||||
"kdash_protocol",
|
||||
"reqwest",
|
||||
"rumqttc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kinded"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce4bdbb2f423660b19f0e9f7115182214732d8dd5f840cd0a3aee3e22562f34c"
|
||||
dependencies = [
|
||||
"kinded_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kinded_macros"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a13b4ddc5dcb32f45dac3d6f606da2a52fdb9964a18427e63cd5ef6c0d13288d"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"proc-macro2 1.0.89",
|
||||
"quote 1.0.37",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.3.2"
|
||||
|
@ -1744,6 +1790,29 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nutype"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8789358e2d6cdffb0cb170c7802ee7548beb8067ed643f3122fa36c335f3c64"
|
||||
dependencies = [
|
||||
"nutype_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nutype_macros"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93a3e222ba1f06a03552910fe89a232a1661dcf8ad4c837531fb199828d0916b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"kinded",
|
||||
"proc-macro2 1.0.89",
|
||||
"quote 1.0.37",
|
||||
"syn 2.0.87",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.5"
|
||||
|
@ -1980,7 +2049,7 @@ dependencies = [
|
|||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls 0.23.16",
|
||||
"socket2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
@ -1997,7 +2066,7 @@ dependencies = [
|
|||
"rand",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls 0.23.16",
|
||||
"slab",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
|
@ -2138,7 +2207,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
|
@ -2148,7 +2217,7 @@ dependencies = [
|
|||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
|
@ -2175,6 +2244,24 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rumqttc"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1568e15fab2d546f940ed3a21f48bbbd1c494c90c99c4481339364a497f94a9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"flume",
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls-native-certs",
|
||||
"rustls-pemfile",
|
||||
"rustls-webpki",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-rustls 0.25.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
|
@ -2209,6 +2296,20 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.16"
|
||||
|
@ -2223,6 +2324,19 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
|
@ -2453,6 +2567,9 @@ name = "spin"
|
|||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
|
@ -2672,13 +2789,24 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
|
||||
dependencies = [
|
||||
"rustls 0.22.4",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
|
@ -2740,6 +2868,12 @@ version = "1.0.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
|
@ -2764,6 +2898,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf16_iter"
|
||||
version = "1.0.5"
|
||||
|
@ -2791,15 +2931,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validated_newtype"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "160f1ca3dc12c9e48f60b5b7a56092e372bda4ee2805f6c84ae337dd582fe12f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
|
|
@ -25,6 +25,8 @@ reqwest = { version = "0.12.9", default-features = false, features = [
|
|||
"json",
|
||||
"rustls-tls",
|
||||
"stream",
|
||||
"http2",
|
||||
"charset",
|
||||
] }
|
||||
tinybmp = "0.6.0"
|
||||
tokio = { version = "1.41.0", features = ["full"] }
|
||||
|
|
12
kdash_client/extensions/kdash/config.xml
Normal file
12
kdash_client/extensions/kdash/config.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension>
|
||||
<information>
|
||||
<name>kdash</name>
|
||||
<version>0.1.0</version>
|
||||
<author>dergrimm</author>
|
||||
<id>kdash</id>
|
||||
</information>
|
||||
<menus>
|
||||
<menu type="json" dynamic="false">menu.json</menu>
|
||||
</menus>
|
||||
</extension>
|
|
@ -1,8 +1,9 @@
|
|||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
DAEMON_PATH="/mnt/us/kdash"
|
||||
DAEMON_PATH="/mnt/us/extensions/kdash"
|
||||
|
||||
DAEMON_ENV_FILE="${DAEMON_PATH}/kdash.env"
|
||||
DAEMON_ENABLED_FILE="${DAEMON_PATH}/ENABLED"
|
||||
|
||||
DAEMON="./kdash_client"
|
||||
DAEMONOPTS=""
|
||||
|
@ -13,20 +14,30 @@ PIDFILE="${DAEMON_PATH}/${DAEMON}.pid"
|
|||
# SCRIPTNAME="/etc/init.d/${NAME}"
|
||||
|
||||
case "$1" in
|
||||
enable)
|
||||
touch "$DAEMON_ENABLED_FILE"
|
||||
;;
|
||||
disable)
|
||||
rm -f "$DAEMON_ENABLED_FILE"
|
||||
;;
|
||||
start)
|
||||
printf "%-50s" "Starting $NAME..."
|
||||
cd "$DAEMON_PATH" || exit
|
||||
. "$DAEMON_ENV_FILE"
|
||||
PID=$(
|
||||
RUST_BACKTRACE=full RUST_LOG=debug $DAEMON "$DAEMONOPTS" >/dev/null 2>&1 &
|
||||
echo $!
|
||||
)
|
||||
#echo "Saving PID" $PID " to " $PIDFILE
|
||||
if [ -z "$PID" ]; then
|
||||
printf "%s\n" "Fail"
|
||||
if [ -e "$DAEMON_ENABLED_FILE" ]; then
|
||||
printf "%-50s" "Starting $NAME..."
|
||||
cd "$DAEMON_PATH" || exit
|
||||
. "$DAEMON_ENV_FILE"
|
||||
PID=$(
|
||||
RUST_BACKTRACE=full RUST_LOG=debug $DAEMON "$DAEMONOPTS" >/dev/null 2>&1 &
|
||||
echo $!
|
||||
)
|
||||
#echo "Saving PID" $PID " to " $PIDFILE
|
||||
if [ -z "$PID" ]; then
|
||||
printf "%s\n" "Fail"
|
||||
else
|
||||
echo "$PID" >"$PIDFILE"
|
||||
printf "%s\n" "Ok"
|
||||
fi
|
||||
else
|
||||
echo "$PID" >"$PIDFILE"
|
||||
printf "%s\n" "Ok"
|
||||
echo "Service not enabled. ENABLED file not found"
|
||||
fi
|
||||
;;
|
||||
status)
|
13
kdash_client/extensions/kdash/menu.json
Normal file
13
kdash_client/extensions/kdash/menu.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "kdash",
|
||||
"items": [
|
||||
{ "name": "Enable", "priority": 0, "action": "./daemon.sh enable" },
|
||||
{ "name": "Disable", "priority": 0, "action": "./daemon.sh disable" },
|
||||
{ "name": "Start", "priority": 0, "action": "./daemon.sh start" },
|
||||
{ "name": "Stop", "priority": 0, "action": "./daemon.sh stop" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
4
kdash_client/extensions/kdash/startup.sh
Normal file
4
kdash_client/extensions/kdash/startup.sh
Normal file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
sleep 120
|
||||
/mnt/us/extensions/kdash/daemon.sh start
|
3
kdash_client/kite/onboot/kdash.sh
Normal file
3
kdash_client/kite/onboot/kdash.sh
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
/mnt/us/extensions/kdash/startup.sh &
|
|
@ -2,6 +2,8 @@ use anyhow::{bail, Result};
|
|||
use kdash_protocol::Orientation;
|
||||
use url::Url;
|
||||
|
||||
use crate::battery::BatteryStatus;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Api {
|
||||
pub jwt: String,
|
||||
|
@ -22,7 +24,7 @@ impl Api {
|
|||
format!("Bearer {}", bearer)
|
||||
}
|
||||
|
||||
pub async fn fetch_config(&self) -> Result<kdash_protocol::Config, reqwest::Error> {
|
||||
pub async fn fetch_config(&self) -> Result<kdash_protocol::Config> {
|
||||
let config = reqwest::Client::new()
|
||||
.get(self.config_url.to_owned())
|
||||
.header(
|
||||
|
@ -37,6 +39,30 @@ impl Api {
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
pub async fn fetch_config_with_post_device_state(
|
||||
&self,
|
||||
battery: &BatteryStatus,
|
||||
) -> Result<kdash_protocol::Config> {
|
||||
let config = reqwest::Client::new()
|
||||
.post(self.config_url.to_owned())
|
||||
.header(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
Self::authorization_bearer(&self.jwt),
|
||||
)
|
||||
.json(&kdash_protocol::DeviceStatePost {
|
||||
battery_charging: battery.is_charging,
|
||||
battery_level: kdash_protocol::Percent::try_new(battery.percentage)?,
|
||||
battery_current: battery.current,
|
||||
battery_voltage: battery.voltage,
|
||||
})
|
||||
.send()
|
||||
.await?
|
||||
.json::<kdash_protocol::Config>()
|
||||
.await?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub async fn fetch_image<'a>(
|
||||
&self,
|
||||
size: (u32, u32),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{net::IpAddr, path::PathBuf, rc::Rc, sync::Mutex};
|
||||
use std::{net::IpAddr, rc::Rc, sync::Mutex};
|
||||
|
||||
use openlipc_dyn::Lipc;
|
||||
|
||||
|
@ -7,18 +7,11 @@ use crate::api;
|
|||
pub struct State {
|
||||
pub api: api::Api,
|
||||
pub app_config: AppConfig,
|
||||
pub config: Rc<Mutex<kdash_protocol::Config>>,
|
||||
pub config: Rc<tokio::sync::Mutex<kdash_protocol::Config>>,
|
||||
pub lipc: Rc<Mutex<Lipc>>,
|
||||
}
|
||||
|
||||
pub struct AppConfig {
|
||||
pub net: String,
|
||||
pub router_ip: IpAddr,
|
||||
pub assets_path: PathBuf,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn asset(&self, path: &str) -> PathBuf {
|
||||
self.assets_path.join(path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ pub struct BatteryStatus {
|
|||
pub is_charging: bool,
|
||||
pub percentage: u8,
|
||||
pub current: i16,
|
||||
pub voltage: i16,
|
||||
}
|
||||
|
||||
pub fn get_battery_status() -> Result<BatteryStatus> {
|
||||
|
@ -23,10 +24,14 @@ pub fn get_battery_status() -> Result<BatteryStatus> {
|
|||
let charge_current_str = utils::exec_command("gasgauge-info", &["-l"])?;
|
||||
let charge_current = parse::parse_number_from_start_signed::<i16>(&charge_current_str)?;
|
||||
|
||||
let voltage_str = utils::exec_command("gasgauge-info", &["-v"])?;
|
||||
let voltage = parse::parse_number_from_start_signed::<i16>(&voltage_str)?;
|
||||
|
||||
let battery = BatteryStatus {
|
||||
is_charging,
|
||||
percentage: charge,
|
||||
current: charge_current,
|
||||
voltage,
|
||||
};
|
||||
|
||||
log::info!("Got battery status: {:?}", battery);
|
||||
|
@ -44,13 +49,13 @@ pub fn restart_powerd_config_condition(
|
|||
battery_config: &kdash_protocol::BatteryConfig,
|
||||
) -> Result<()> {
|
||||
if battery.is_charging
|
||||
&& battery.percentage <= *battery_config.restart_powerd_threshold
|
||||
&& battery.percentage <= battery_config.restart_powerd_threshold.into_inner()
|
||||
&& battery.current <= 0
|
||||
{
|
||||
log::info!(
|
||||
"Battery charge below threshold ({} <= {}): restarting powerd",
|
||||
battery.percentage,
|
||||
*battery_config.restart_powerd_threshold
|
||||
battery_config.restart_powerd_threshold.into_inner()
|
||||
);
|
||||
restart_powerd()
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use envconfig::Envconfig;
|
||||
use std::{net, path::PathBuf};
|
||||
use std::net;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Envconfig, Debug)]
|
||||
|
@ -15,7 +15,4 @@ pub struct Config {
|
|||
|
||||
#[envconfig(from = "NET")]
|
||||
pub net: String,
|
||||
|
||||
#[envconfig(from = "ASSETS")]
|
||||
pub assets: PathBuf,
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ pub fn eips_clear() -> Result<()> {
|
|||
utils::exec_command_discard("eips", &["-c"])
|
||||
}
|
||||
|
||||
pub fn image_buf_to_raw<'a>(
|
||||
buf: &'a image::ImageBuffer<image::Luma<u8>, Vec<u8>>,
|
||||
) -> ImageRawBE<'a, Gray8> {
|
||||
pub fn image_buf_to_raw(
|
||||
buf: &image::ImageBuffer<image::Luma<u8>, Vec<u8>>,
|
||||
) -> ImageRawBE<'_, Gray8> {
|
||||
ImageRaw::new(buf.as_raw(), buf.dimensions().0)
|
||||
}
|
||||
|
||||
|
@ -186,10 +186,7 @@ impl DrawTarget for FramebufferDisplay {
|
|||
|
||||
impl OriginDimensions for FramebufferDisplay {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(
|
||||
self.fb_orientation.virtual_x as u32,
|
||||
self.fb_orientation.virtual_y as u32,
|
||||
)
|
||||
Size::new(self.fb_orientation.virtual_x, self.fb_orientation.virtual_y)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,18 +19,18 @@ pub async fn run_once(
|
|||
state: &app::State,
|
||||
display: &mut fb::FramebufferDisplay,
|
||||
) -> Result<Option<kdash_protocol::Config>> {
|
||||
let config = state.config.lock().unwrap();
|
||||
let config = state.config.lock().await;
|
||||
|
||||
utils::set_cpu_powersaving()?;
|
||||
utils::prevent_screensaver(&state.lipc.lock().unwrap())?;
|
||||
|
||||
let battery = battery::get_battery_status()?;
|
||||
battery::restart_powerd_config_condition(&battery, &config.battery)?;
|
||||
if battery.percentage <= *config.battery.low {
|
||||
if battery.percentage <= config.battery.low.into_inner() {
|
||||
log::info!(
|
||||
"Battery low: {}% <= {}%",
|
||||
battery.percentage,
|
||||
*config.battery.low
|
||||
config.battery.low.into_inner()
|
||||
);
|
||||
|
||||
Image::new(&*assets::ERROR_BATTERY_LOW_IMAGE, Point::zero())
|
||||
|
@ -66,7 +66,11 @@ pub async fn run_once(
|
|||
|
||||
log::info!("Fetching config");
|
||||
|
||||
let fetch_succ = match state.api.fetch_config().await {
|
||||
let fetch_succ = match state
|
||||
.api
|
||||
.fetch_config_with_post_device_state(&battery)
|
||||
.await
|
||||
{
|
||||
Ok(value) => {
|
||||
new_config = Some(value);
|
||||
|
||||
|
@ -107,7 +111,7 @@ pub async fn run_once(
|
|||
.draw(display)?;
|
||||
}
|
||||
|
||||
if battery.percentage <= *config.battery.alert {
|
||||
if battery.percentage <= config.battery.alert.into_inner() {
|
||||
let now_str = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
|
||||
let text = format!("Battery at {}%, please charge!", battery.percentage);
|
||||
let status_box = fb::widgets::status_box::StatusBox::new_with_default_style(
|
||||
|
|
|
@ -4,8 +4,6 @@ use envconfig::Envconfig;
|
|||
use openlipc_dyn::Lipc;
|
||||
use std::{rc::Rc, sync::Mutex, thread, time};
|
||||
|
||||
use kdash_client;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
@ -13,7 +11,9 @@ async fn main() -> Result<()> {
|
|||
let env_config = kdash_client::config::Config::init_from_env().unwrap();
|
||||
|
||||
let api = kdash_client::api::Api::new(env_config.kdash_jwt, env_config.kdash_url)?;
|
||||
let config = api.fetch_config().await?;
|
||||
|
||||
let battery = kdash_client::battery::get_battery_status()?;
|
||||
let config = api.fetch_config_with_post_device_state(&battery).await?;
|
||||
|
||||
let lipc = Lipc::load(None)?;
|
||||
|
||||
|
@ -22,15 +22,14 @@ async fn main() -> Result<()> {
|
|||
app_config: kdash_client::app::AppConfig {
|
||||
net: env_config.net,
|
||||
router_ip: env_config.router_ip,
|
||||
assets_path: env_config.assets,
|
||||
},
|
||||
config: Rc::new(Mutex::new(config)),
|
||||
config: Rc::new(tokio::sync::Mutex::new(config)),
|
||||
lipc: Rc::new(Mutex::new(lipc)),
|
||||
};
|
||||
|
||||
let mut display = kdash_client::fb::FramebufferDisplay::new(
|
||||
kdash_client::fb::DEFAULT_FB,
|
||||
state.config.lock().unwrap().device.orientation,
|
||||
state.config.lock().await.device.orientation,
|
||||
)?;
|
||||
|
||||
kdash_client::utils::kill_kindle()?;
|
||||
|
@ -44,7 +43,7 @@ async fn main() -> Result<()> {
|
|||
match kdash_client::run_once(&state, &mut display).await {
|
||||
Ok(Some(new_config)) => {
|
||||
log::info!("Updating config");
|
||||
let mut config = state.config.lock().unwrap();
|
||||
let mut config = state.config.lock().await;
|
||||
if display.fb_orientation.orientation != new_config.device.orientation {
|
||||
log::info!(
|
||||
"Updating orientation: {:?} -> {:?}",
|
||||
|
@ -65,7 +64,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
let config = state.config.lock().unwrap();
|
||||
let config = state.config.lock().await;
|
||||
|
||||
thread::sleep(config.time.delay_before_suspend.to_std()?);
|
||||
|
||||
|
|
|
@ -7,6 +7,6 @@ edition = "2021"
|
|||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
chrono-tz = { version = "0.10.0", features = ["serde"] }
|
||||
cron = { version = "0.13.0", features = ["serde"] }
|
||||
nutype = { version = "0.5.0", features = ["serde"] }
|
||||
serde = { version = "1.0.214", features = ["derive"] }
|
||||
serde_with = { version = "3.11.0", features = ["chrono_0_4"] }
|
||||
validated_newtype = "0.1.1"
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use chrono::Duration;
|
||||
use chrono_tz::Tz;
|
||||
use cron::Schedule;
|
||||
use nutype::nutype;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validated_newtype::validated_newtype;
|
||||
|
||||
validated_newtype! {
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Clone, Copy)]
|
||||
u8 => pub Percent
|
||||
if |n: &u8| *n <= 100;
|
||||
error "percent must in range 0-100"
|
||||
#[nutype(
|
||||
derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq),
|
||||
validate(predicate = is_valid_percent)
|
||||
)]
|
||||
pub struct Percent(u8);
|
||||
|
||||
fn is_valid_percent(n: &u8) -> bool {
|
||||
*n <= 100
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
|
@ -66,3 +69,11 @@ pub enum Corner {
|
|||
BottomLeft,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
pub struct DeviceStatePost {
|
||||
pub battery_charging: bool,
|
||||
pub battery_level: Percent,
|
||||
pub battery_current: i16,
|
||||
pub battery_voltage: i16,
|
||||
}
|
||||
|
|
|
@ -11,11 +11,16 @@ chrono-tz = { version = "0.10.0", features = ["serde"] }
|
|||
clap = { version = "4.5.20", features = ["derive"] }
|
||||
cron = { version = "0.13.0", features = ["serde"] }
|
||||
env_logger = "0.11.5"
|
||||
envconfig = "0.11.0"
|
||||
futures = "0.3.31"
|
||||
futures-util = "0.3.31"
|
||||
jsonwebtoken = "9.3.0"
|
||||
kdash_protocol = { path = "../kdash_protocol" }
|
||||
reqwest = { version = "0.12.9", features = ["stream"] }
|
||||
rumqttc = "0.24.0"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
serde_json = "1.0.132"
|
||||
serde_with = { version = "3.11.0", features = ["chrono_0_4"] }
|
||||
tokio = { version = "1.41.1", features = ["full"] }
|
||||
url = { version = "2.5.3", features = ["serde"] }
|
||||
uuid = { version = "1.11.0", features = ["serde"] }
|
||||
|
|
22
kdash_server/src/config.rs
Normal file
22
kdash_server/src/config.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use envconfig::Envconfig;
|
||||
|
||||
#[derive(Envconfig, Clone)]
|
||||
pub struct Config {
|
||||
#[envconfig(from = "KDASH_MQTT_SERVER")]
|
||||
pub kdash_mqtt_server: String,
|
||||
|
||||
#[envconfig(from = "KDASH_MQTT_PORT")]
|
||||
pub kdash_mqtt_port: u16,
|
||||
|
||||
#[envconfig(from = "KDASH_MQTT_USERNAME")]
|
||||
pub kdash_mqtt_username: String,
|
||||
|
||||
#[envconfig(from = "KDASH_MQTT_PASSWORD")]
|
||||
pub kdash_mqtt_password: String,
|
||||
|
||||
#[envconfig(from = "KDASH_MQTT_TOPIC")]
|
||||
pub kdash_mqtt_topic: String,
|
||||
|
||||
#[envconfig(from = "KDASH_MQTT_DISCOVERY_PREFIX")]
|
||||
pub kdash_mqtt_discovery_prefix: Option<String>,
|
||||
}
|
|
@ -1,31 +1,24 @@
|
|||
use actix_web::{dev::PeerAddr, get, http, web, Error, HttpRequest, HttpResponse};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{AppState, Claims};
|
||||
use actix_web::{dev::PeerAddr, get, post, web, Error, HttpResponse};
|
||||
|
||||
use crate::{
|
||||
AppState, Claims, MqttClient, MqttDeviceDiscovery, MqttDeviceDiscoveryComponent,
|
||||
MqttDeviceDiscoveryDevice, MqttDeviceDiscoveryOrigin, MqttState, VERSION,
|
||||
};
|
||||
|
||||
#[get("/config")]
|
||||
pub async fn get_config(req: HttpRequest, data: web::Data<AppState>) -> HttpResponse {
|
||||
let token = match req
|
||||
.headers()
|
||||
.get(http::header::AUTHORIZATION)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.filter(|s| s.starts_with("Bearer "))
|
||||
.map(|s| &s[7..])
|
||||
{
|
||||
Some(s) => s,
|
||||
pub async fn get_config(data: web::Data<AppState>, claims: Option<Claims>) -> HttpResponse {
|
||||
let claims = match claims {
|
||||
Some(x) => x,
|
||||
None => return HttpResponse::Unauthorized().finish(),
|
||||
};
|
||||
|
||||
let decoded =
|
||||
match jsonwebtoken::decode::<Claims>(token, &data.jwt_decoding_key, &data.jwt_validation) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return HttpResponse::BadRequest().finish(),
|
||||
};
|
||||
|
||||
if !data.device_ids.contains(&decoded.claims.sub) {
|
||||
if !data.device_ids.contains(&claims.sub) {
|
||||
return HttpResponse::Unauthorized().finish();
|
||||
}
|
||||
|
||||
let buf = match data.devices_api_json.get(&decoded.claims.sub) {
|
||||
let buf = match data.devices_api_json.get(&claims.sub) {
|
||||
Some(x) => x,
|
||||
None => return HttpResponse::InternalServerError().finish(),
|
||||
};
|
||||
|
@ -35,35 +28,153 @@ pub async fn get_config(req: HttpRequest, data: web::Data<AppState>) -> HttpResp
|
|||
.body(buf.clone())
|
||||
}
|
||||
|
||||
#[post("/config")]
|
||||
pub async fn post_config(
|
||||
claims: Option<Claims>,
|
||||
data: web::Data<AppState>,
|
||||
mqtt_client: web::Data<MqttClient>,
|
||||
device_state: web::Json<kdash_protocol::DeviceStatePost>,
|
||||
) -> HttpResponse {
|
||||
let claims = match claims {
|
||||
Some(x) => x,
|
||||
None => return HttpResponse::Unauthorized().finish(),
|
||||
};
|
||||
|
||||
if !data.device_ids.contains(&claims.sub) {
|
||||
return HttpResponse::Unauthorized().finish();
|
||||
}
|
||||
|
||||
let (config, config_buf) = match (
|
||||
data.devices_api.get(&claims.sub),
|
||||
data.devices_api_json.get(&claims.sub),
|
||||
) {
|
||||
(Some(config), Some(config_buf)) => (config, config_buf),
|
||||
_ => return HttpResponse::InternalServerError().finish(),
|
||||
};
|
||||
|
||||
let device_id = claims.sub.to_string();
|
||||
|
||||
let state_topic = format!("{}/device/{}/state", mqtt_client.topic, device_id);
|
||||
|
||||
let state_payload = match serde_json::to_vec(&MqttState {
|
||||
battery_charging: device_state.battery_charging,
|
||||
battery_level: device_state.battery_level,
|
||||
battery_current: device_state.battery_current,
|
||||
battery_voltage: device_state.battery_voltage,
|
||||
}) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return HttpResponse::InternalServerError().finish(),
|
||||
};
|
||||
|
||||
if mqtt_client
|
||||
.client
|
||||
.publish(
|
||||
&state_topic,
|
||||
rumqttc::v5::mqttbytes::QoS::AtLeastOnce,
|
||||
false,
|
||||
state_payload,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return HttpResponse::InternalServerError().finish();
|
||||
}
|
||||
|
||||
if let Some(discovery_topic) = &mqtt_client.discovery_topic {
|
||||
let unique_id = format!("kdash_{}", device_id);
|
||||
let unique_id_battery_level = format!("{}_battery_level", unique_id);
|
||||
let unique_id_battery_current = format!("{}_battery_current", unique_id);
|
||||
let unique_id_battery_voltage = format!("{}_battery_voltage", unique_id);
|
||||
|
||||
let mut components = HashMap::new();
|
||||
components.insert(
|
||||
"battery_level".to_string(),
|
||||
MqttDeviceDiscoveryComponent {
|
||||
platform: "sensor",
|
||||
device_class: "battery",
|
||||
unit_of_measurement: Some("%"),
|
||||
value_template: "{{ value_json.battery_level }}",
|
||||
unique_id: &unique_id_battery_level,
|
||||
},
|
||||
);
|
||||
components.insert(
|
||||
"battery_current".to_string(),
|
||||
MqttDeviceDiscoveryComponent {
|
||||
platform: "sensor",
|
||||
device_class: "current",
|
||||
unit_of_measurement: Some("mA"),
|
||||
value_template: "{{ value_json.battery_current }}",
|
||||
unique_id: &unique_id_battery_current,
|
||||
},
|
||||
);
|
||||
components.insert(
|
||||
"battery_voltage".to_string(),
|
||||
MqttDeviceDiscoveryComponent {
|
||||
platform: "sensor",
|
||||
device_class: "voltage",
|
||||
unit_of_measurement: Some("mV"),
|
||||
value_template: "{{ value_json.battery_voltage }}",
|
||||
unique_id: &unique_id_battery_voltage,
|
||||
},
|
||||
);
|
||||
|
||||
let discovery = MqttDeviceDiscovery {
|
||||
device: MqttDeviceDiscoveryDevice {
|
||||
identifiers: &unique_id,
|
||||
name: config.name.to_owned(),
|
||||
},
|
||||
origin: MqttDeviceDiscoveryOrigin {
|
||||
name: "kdash",
|
||||
software_version: VERSION,
|
||||
url: "https://git.dergrimm.net/dergrimm/kdash",
|
||||
},
|
||||
components,
|
||||
state_topic: &state_topic,
|
||||
qos: 2,
|
||||
};
|
||||
|
||||
let discovery_payload = match serde_json::to_vec(&discovery) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return HttpResponse::InternalServerError().finish(),
|
||||
};
|
||||
|
||||
if mqtt_client
|
||||
.client
|
||||
.publish(
|
||||
format!("{}/device/kdash/{}/config", discovery_topic, device_id),
|
||||
rumqttc::v5::mqttbytes::QoS::AtLeastOnce,
|
||||
false,
|
||||
discovery_payload,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return HttpResponse::InternalServerError().finish();
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(config_buf.clone())
|
||||
}
|
||||
|
||||
#[get("/image")]
|
||||
pub async fn get_image(
|
||||
req: HttpRequest,
|
||||
claims: Option<Claims>,
|
||||
peer_addr: Option<PeerAddr>,
|
||||
data: web::Data<AppState>,
|
||||
client: web::Data<reqwest::Client>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let token = match req
|
||||
.headers()
|
||||
.get(http::header::AUTHORIZATION)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.filter(|s| s.starts_with("Bearer "))
|
||||
.map(|s| &s[7..])
|
||||
{
|
||||
Some(s) => s,
|
||||
let claims = match claims {
|
||||
Some(x) => x,
|
||||
None => return Ok(HttpResponse::Unauthorized().finish()),
|
||||
};
|
||||
|
||||
let decoded =
|
||||
match jsonwebtoken::decode::<Claims>(token, &data.jwt_decoding_key, &data.jwt_validation) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return Ok(HttpResponse::BadRequest().finish()),
|
||||
};
|
||||
|
||||
if !data.device_ids.contains(&decoded.claims.sub) {
|
||||
if !data.device_ids.contains(&claims.sub) {
|
||||
return Ok(HttpResponse::Unauthorized().finish());
|
||||
}
|
||||
|
||||
let config = match data.devices.get(&decoded.claims.sub) {
|
||||
let config = match data.devices.get(&claims.sub) {
|
||||
Some(x) => x,
|
||||
None => return Ok(HttpResponse::InternalServerError().finish()),
|
||||
};
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
|
||||
use actix_web::{http, FromRequest, HttpMessage};
|
||||
use anyhow::Result;
|
||||
use futures::future::{err, ok, LocalBoxFuture};
|
||||
use jsonwebtoken::{DecodingKey, Validation};
|
||||
use kdash_protocol::Percent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::future::{ready, Ready};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod config;
|
||||
pub mod device;
|
||||
pub mod handlers;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub devices: HashMap<Uuid, device::Config>,
|
||||
|
@ -35,3 +45,177 @@ pub struct Claims {
|
|||
pub exp: i64,
|
||||
pub sub: Uuid,
|
||||
}
|
||||
|
||||
impl FromRequest for Claims {
|
||||
type Error = actix_web::Error;
|
||||
type Future = futures::future::Ready<Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(
|
||||
req: &actix_web::HttpRequest,
|
||||
_payload: &mut actix_web::dev::Payload,
|
||||
) -> Self::Future {
|
||||
match req.extensions().get::<Claims>() {
|
||||
Some(claims) => ok(claims.clone()),
|
||||
None => err(actix_web::error::ErrorUnauthorized("Unauthorized")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Authority {
|
||||
pub decoding_key: DecodingKey,
|
||||
pub validation: Validation,
|
||||
}
|
||||
|
||||
pub struct JwtAuth<Claims> {
|
||||
pub auth: Arc<Authority>,
|
||||
claims_marker: PhantomData<Claims>,
|
||||
}
|
||||
|
||||
impl<Claims> JwtAuth<Claims> {
|
||||
pub fn new(auth: Arc<Authority>) -> Self {
|
||||
Self {
|
||||
auth,
|
||||
claims_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, Claims> Transform<S, ServiceRequest> for JwtAuth<Claims>
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = actix_web::Error;
|
||||
type InitError = ();
|
||||
type Transform = JwtAuthMiddleware<S, Claims>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ready(Ok(JwtAuthMiddleware {
|
||||
service,
|
||||
auth: Arc::clone(&self.auth),
|
||||
claims_marker: PhantomData,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JwtAuthMiddleware<S, Claims> {
|
||||
service: S,
|
||||
auth: Arc<Authority>,
|
||||
claims_marker: PhantomData<Claims>,
|
||||
}
|
||||
|
||||
impl<S, B, C> Service<ServiceRequest> for JwtAuthMiddleware<S, C>
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = actix_web::Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
forward_ready!(service);
|
||||
|
||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||
let decoded = req
|
||||
.headers()
|
||||
.get(http::header::AUTHORIZATION)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.filter(|s| s.starts_with("Bearer "))
|
||||
.map(|s| &s[7..])
|
||||
.and_then(|token| {
|
||||
jsonwebtoken::decode::<Claims>(
|
||||
token,
|
||||
&self.auth.decoding_key,
|
||||
&self.auth.validation,
|
||||
)
|
||||
.ok()
|
||||
});
|
||||
|
||||
if let Some(data) = decoded {
|
||||
req.extensions_mut().insert(data.claims);
|
||||
dbg!(req.extensions());
|
||||
}
|
||||
|
||||
Box::pin(self.service.call(req))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MqttClient {
|
||||
pub client: rumqttc::v5::AsyncClient,
|
||||
pub topic: String,
|
||||
pub discovery_topic: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MqttState {
|
||||
pub battery_charging: bool,
|
||||
pub battery_level: Percent,
|
||||
pub battery_current: i16,
|
||||
pub battery_voltage: i16,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MqttDeviceDiscovery<'a> {
|
||||
#[serde(rename = "dev")]
|
||||
pub device: MqttDeviceDiscoveryDevice<'a>,
|
||||
|
||||
#[serde(rename = "o")]
|
||||
pub origin: MqttDeviceDiscoveryOrigin<'a>,
|
||||
|
||||
#[serde(rename = "cmps")]
|
||||
pub components: HashMap<String, MqttDeviceDiscoveryComponent<'a>>,
|
||||
|
||||
pub state_topic: &'a str,
|
||||
|
||||
pub qos: u8,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MqttDeviceDiscoveryDevice<'a> {
|
||||
#[serde(rename = "ids")]
|
||||
pub identifiers: &'a str,
|
||||
|
||||
pub name: String,
|
||||
// #[serde(rename = "mf")]
|
||||
// pub manufacturer: &'a str,
|
||||
|
||||
// #[serde(rename = "mdl")]
|
||||
// pub default_manufacturer: &'a str,
|
||||
|
||||
// #[serde(rename = "sw")]
|
||||
// pub sw_version: &'a str,
|
||||
|
||||
// #[serde(rename = "sn")]
|
||||
// pub serial_number: &'a str,
|
||||
|
||||
// #[serde(rename = "hw")]
|
||||
// pub hw_version: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MqttDeviceDiscoveryOrigin<'a> {
|
||||
pub name: &'a str,
|
||||
|
||||
#[serde(rename = "sw")]
|
||||
pub software_version: &'a str,
|
||||
|
||||
pub url: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MqttDeviceDiscoveryComponent<'a> {
|
||||
#[serde(rename = "p")]
|
||||
pub platform: &'a str,
|
||||
pub device_class: &'a str,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub unit_of_measurement: Option<&'a str>,
|
||||
|
||||
pub value_template: &'a str,
|
||||
pub unique_id: &'a str,
|
||||
}
|
||||
|
|
|
@ -2,11 +2,14 @@ use actix_web::{middleware::Logger, web, App, HttpServer};
|
|||
use anyhow::Result;
|
||||
use chrono::prelude::*;
|
||||
use clap::{Parser, Subcommand};
|
||||
use envconfig::Envconfig;
|
||||
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -40,6 +43,8 @@ async fn main() -> Result<()> {
|
|||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let env_config = kdash_server::config::Config::init_from_env()?;
|
||||
|
||||
let config = kdash_server::read_config(&cli.config)?;
|
||||
|
||||
let sk = fs::read(cli.private_key)?;
|
||||
|
@ -61,6 +66,25 @@ async fn main() -> Result<()> {
|
|||
println!("{}", token);
|
||||
}
|
||||
Commands::Start => {
|
||||
let mut mqtt_options = rumqttc::v5::MqttOptions::new(
|
||||
"kdash-server",
|
||||
env_config.kdash_mqtt_server,
|
||||
env_config.kdash_mqtt_port,
|
||||
);
|
||||
mqtt_options.set_credentials(
|
||||
env_config.kdash_mqtt_username,
|
||||
env_config.kdash_mqtt_password,
|
||||
);
|
||||
mqtt_options.set_keep_alive(Duration::from_secs(10));
|
||||
|
||||
let (mqtt_client, mut eventloop) = rumqttc::v5::AsyncClient::new(mqtt_options, 20);
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
eventloop.poll().await.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
HttpServer::new(move || {
|
||||
let devices = config.devices.to_owned();
|
||||
let device_ids: HashSet<Uuid> = config.devices.keys().cloned().collect();
|
||||
|
@ -88,8 +112,20 @@ async fn main() -> Result<()> {
|
|||
jwt_validation: Validation::new(ALGORITHM),
|
||||
}))
|
||||
.app_data(web::Data::new(reqwest_client))
|
||||
.app_data(web::Data::new(kdash_server::MqttClient {
|
||||
client: mqtt_client.to_owned(),
|
||||
topic: env_config.kdash_mqtt_topic.to_owned(),
|
||||
discovery_topic: env_config.kdash_mqtt_discovery_prefix.to_owned(),
|
||||
}))
|
||||
.wrap(Logger::default())
|
||||
.wrap(kdash_server::JwtAuth::<kdash_server::Claims>::new(
|
||||
Arc::new(kdash_server::Authority {
|
||||
decoding_key: decoding_key.to_owned(),
|
||||
validation: Validation::new(ALGORITHM),
|
||||
}),
|
||||
))
|
||||
.service(kdash_server::handlers::get_config)
|
||||
.service(kdash_server::handlers::post_config)
|
||||
.service(kdash_server::handlers::get_image)
|
||||
})
|
||||
.bind(("0.0.0.0", 8080))?
|
||||
|
|
Loading…
Reference in a new issue