Skip to content

Commit

Permalink
Merge pull request #33 from hnez/usb-limits
Browse files Browse the repository at this point in the history
 usb: display power supply overload notifications
  • Loading branch information
hnez committed Oct 6, 2023
2 parents cca43b5 + 0fef424 commit c19e3e9
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 7 deletions.
16 changes: 16 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,22 @@ paths:
schema:
$ref: '#/components/schemas/UsbDevice'

/v1/usb/host/overload:
get:
summary: Get the name of the currently overloaded port (if any)
tags: [USB Host]
responses:
'200':
content:
application/json:
schema:
type: string
enum:
- Total
- Port1
- Port2
- Port3

/v1/tac/temperatures/soc:
get:
summary: Get the current temperature inside the SoC
Expand Down
8 changes: 7 additions & 1 deletion src/adc/iio/demo_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,14 @@ impl CalibratedChannel {

let mut value = f32::from_bits(self.inner.value.load(Ordering::Relaxed));

let decay = if time_constant.abs() < 0.01 {
0.0
} else {
(-dt / time_constant).exp()
};

value -= nominal;
value *= (-dt / time_constant).exp();
value *= decay;
value += (2.0 * thread_rng().gen::<f32>() - 1.0) * self.inner.noise;
value += self
.inner
Expand Down
8 changes: 7 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ async fn main() -> Result<()> {
let dig_io = DigitalIo::new(&mut bb, led.out_0.clone(), led.out_1.clone());
let regulators = Regulators::new(&mut bb);
let temperatures = Temperatures::new(&mut bb);
let usb_hub = UsbHub::new(&mut bb);
let usb_hub = UsbHub::new(
&mut bb,
adc.usb_host_curr.fast.clone(),
adc.usb_host1_curr.fast.clone(),
adc.usb_host2_curr.fast.clone(),
adc.usb_host3_curr.fast.clone(),
);

// Expose other software on the TAC via the broker framework by connecting
// to them via HTTP / DBus APIs.
Expand Down
4 changes: 4 additions & 0 deletions src/ui/screens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ mod uart;
mod update_available;
mod update_installation;
mod usb;
mod usb_overload;

use dig_out::DigOutScreen;
use help::HelpScreen;
Expand All @@ -57,6 +58,7 @@ use uart::UartScreen;
use update_available::UpdateAvailableScreen;
use update_installation::UpdateInstallationScreen;
use usb::UsbScreen;
use usb_overload::UsbOverloadScreen;

use super::buttons;
use super::widgets;
Expand Down Expand Up @@ -84,6 +86,7 @@ pub enum AlertScreen {
RebootConfirm,
UpdateAvailable,
UpdateInstallation,
UsbOverload,
Help,
Setup,
OverTemperature,
Expand Down Expand Up @@ -207,5 +210,6 @@ pub(super) fn init(
&res.temperatures.warning,
)),
Box::new(LocatorScreen::new(alerts, locator)),
Box::new(UsbOverloadScreen::new(alerts, &res.usb_hub.overload)),
]
}
7 changes: 3 additions & 4 deletions src/ui/screens/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ use super::{
};
use crate::broker::Topic;
use crate::measurement::Measurement;
use crate::usb_hub::{MAX_PORT_CURRENT, MAX_TOTAL_CURRENT};

const SCREEN_TYPE: NormalScreen = NormalScreen::Usb;
const CURRENT_LIMIT_PER_PORT: f32 = 0.5;
const CURRENT_LIMIT_TOTAL: f32 = 0.7;
const OFFSET_INDICATOR: Point = Point::new(92, -10);
const OFFSET_BAR: Point = Point::new(122, -14);
const WIDTH_BAR: u32 = 90;
Expand Down Expand Up @@ -103,7 +102,7 @@ impl ActivatableScreen for UsbScreen {
row_anchor(0) + OFFSET_BAR,
WIDTH_BAR,
HEIGHT_BAR,
Box::new(|meas: &Measurement| meas.value / CURRENT_LIMIT_TOTAL),
Box::new(|meas: &Measurement| meas.value / MAX_TOTAL_CURRENT),
)
});

Expand Down Expand Up @@ -143,7 +142,7 @@ impl ActivatableScreen for UsbScreen {
anchor_bar,
WIDTH_BAR,
HEIGHT_BAR,
Box::new(|meas: &Measurement| meas.value / CURRENT_LIMIT_PER_PORT),
Box::new(|meas: &Measurement| meas.value / MAX_PORT_CURRENT),
)
});
}
Expand Down
150 changes: 150 additions & 0 deletions src/ui/screens/usb_overload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// This file is part of tacd, the LXA TAC system daemon
// Copyright (C) 2023 Pengutronix e.K.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

use async_std::prelude::*;
use async_std::sync::Arc;
use async_std::task::spawn;
use async_trait::async_trait;
use embedded_graphics::{
mono_font::MonoTextStyle, pixelcolor::BinaryColor, prelude::*, text::Text,
};

use super::widgets::*;
use super::{
row_anchor, ActivatableScreen, ActiveScreen, AlertList, AlertScreen, Alerter, Display,
InputEvent, Screen, Ui,
};
use crate::broker::Topic;
use crate::measurement::Measurement;
use crate::usb_hub::{OverloadedPort, MAX_PORT_CURRENT, MAX_TOTAL_CURRENT};

const SCREEN_TYPE: AlertScreen = AlertScreen::UsbOverload;
const OFFSET_BAR: Point = Point::new(75, -14);
const OFFSET_VAL: Point = Point::new(160, 0);
const WIDTH_BAR: u32 = 80;
const HEIGHT_BAR: u32 = 18;

pub struct UsbOverloadScreen;

struct Active {
widgets: WidgetContainer,
}

impl UsbOverloadScreen {
pub fn new(
alerts: &Arc<Topic<AlertList>>,
overload: &Arc<Topic<Option<OverloadedPort>>>,
) -> Self {
let (mut overload_events, _) = overload.clone().subscribe_unbounded();
let alerts = alerts.clone();

spawn(async move {
while let Some(overload) = overload_events.next().await {
if overload.is_some() {
alerts.assert(SCREEN_TYPE)
} else {
alerts.deassert(SCREEN_TYPE)
}
}
});

Self
}
}

impl ActivatableScreen for UsbOverloadScreen {
fn my_type(&self) -> Screen {
Screen::Alert(SCREEN_TYPE)
}

fn activate(&mut self, ui: &Ui, display: Display) -> Box<dyn ActiveScreen> {
let ui_text_style: MonoTextStyle<BinaryColor> =
MonoTextStyle::new(&UI_TEXT_FONT, BinaryColor::On);

display.with_lock(|target| {
Text::new(
"USB Power Overload",
row_anchor(0) - (row_anchor(1) - row_anchor(0)),
ui_text_style,
)
.draw(target)
.unwrap();

Text::new(
"Disconnect devices or\nuse a powered hub.",
row_anchor(1),
ui_text_style,
)
.draw(target)
.unwrap();

for (row, name) in &[(4, "Total"), (6, "Port 1"), (7, "Port 2"), (8, "Port 3")] {
Text::new(name, row_anchor(*row), ui_text_style)
.draw(target)
.unwrap();
}
});

let mut widgets = WidgetContainer::new(display);

let ports = [
(0, &ui.res.adc.usb_host_curr.topic, MAX_TOTAL_CURRENT),
(2, &ui.res.adc.usb_host1_curr.topic, MAX_PORT_CURRENT),
(3, &ui.res.adc.usb_host2_curr.topic, MAX_PORT_CURRENT),
(4, &ui.res.adc.usb_host3_curr.topic, MAX_PORT_CURRENT),
];

for (idx, current, max_current) in ports {
let anchor_port = row_anchor(idx + 4);

widgets.push(|display| {
DynamicWidget::bar(
current.clone(),
display,
anchor_port + OFFSET_BAR,
WIDTH_BAR,
HEIGHT_BAR,
Box::new(move |meas: &Measurement| meas.value / max_current),
)
});

widgets.push(|display| {
DynamicWidget::text(
current.clone(),
display,
anchor_port + OFFSET_VAL,
Box::new(|meas: &Measurement| format!("{:>4.0}mA", meas.value * 1000.0)),
)
});
}

Box::new(Active { widgets })
}
}

#[async_trait]
impl ActiveScreen for Active {
fn my_type(&self) -> Screen {
Screen::Alert(SCREEN_TYPE)
}

async fn deactivate(mut self: Box<Self>) -> Display {
self.widgets.destroy().await
}

fn input(&mut self, _ev: InputEvent) {}
}
81 changes: 80 additions & 1 deletion src/usb_hub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use async_std::sync::Arc;
use async_std::task::{sleep, spawn};
use serde::{Deserialize, Serialize};

use crate::adc::CalibratedChannel;
use crate::broker::{BrokerBuilder, Topic};

#[cfg(feature = "demo_mode")]
Expand Down Expand Up @@ -135,6 +136,45 @@ const PORTS: &[(&str, &str)] = &[
),
];

// The total current for all ports is limited to 700mA, the per-port current is
// limited to 500mA.
pub const MAX_TOTAL_CURRENT: f32 = 0.7;
pub const MAX_PORT_CURRENT: f32 = 0.5;

// The measurement is not _that_ exact so start warning at 90% utilization.
const CURRENT_MARGIN: f32 = 0.9;
const WARN_TOTAL_CURRENT: f32 = MAX_TOTAL_CURRENT * CURRENT_MARGIN;
const WARN_PORT_CURRENT: f32 = MAX_PORT_CURRENT * CURRENT_MARGIN;

#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub enum OverloadedPort {
Total,
Port1,
Port2,
Port3,
}

impl OverloadedPort {
fn from_currents(total: f32, port1: f32, port2: f32, port3: f32) -> Option<Self> {
// Based on the maximum / per-port limits it should not be possible for two
// individual ports to be overloaded at the same time while the total is not
// overloaded, so reporting either "total" or one of the ports should be
// sufficient.

if total > WARN_TOTAL_CURRENT {
Some(Self::Total)
} else if port1 > WARN_PORT_CURRENT {
Some(Self::Port1)
} else if port2 > WARN_PORT_CURRENT {
Some(Self::Port2)
} else if port3 > WARN_PORT_CURRENT {
Some(Self::Port3)
} else {
None
}
}
}

#[derive(Serialize, Deserialize, PartialEq, Clone)]
pub struct UsbDevice {
id_product: String,
Expand All @@ -151,6 +191,7 @@ pub struct UsbPort {
}

pub struct UsbHub {
pub overload: Arc<Topic<Option<OverloadedPort>>>,
pub port1: UsbPort,
pub port2: UsbPort,
pub port3: UsbPort,
Expand Down Expand Up @@ -236,11 +277,49 @@ fn handle_port(bb: &mut BrokerBuilder, name: &'static str, base: &'static str) -
port
}

fn handle_overloads(
bb: &mut BrokerBuilder,
total: CalibratedChannel,
port1: CalibratedChannel,
port2: CalibratedChannel,
port3: CalibratedChannel,
) -> Arc<Topic<Option<OverloadedPort>>> {
let overload = bb.topic_ro("/v1/usb/host/overload", None);

let overload_task = overload.clone();

spawn(async move {
loop {
let overloaded_port = OverloadedPort::from_currents(
total.get().map(|m| m.value).unwrap_or(0.0),
port1.get().map(|m| m.value).unwrap_or(0.0),
port2.get().map(|m| m.value).unwrap_or(0.0),
port3.get().map(|m| m.value).unwrap_or(0.0),
);

overload_task.set_if_changed(overloaded_port);

sleep(POLL_INTERVAL).await;
}
});

overload
}

impl UsbHub {
pub fn new(bb: &mut BrokerBuilder) -> Self {
pub fn new(
bb: &mut BrokerBuilder,
total: CalibratedChannel,
port1: CalibratedChannel,
port2: CalibratedChannel,
port3: CalibratedChannel,
) -> Self {
let overload = handle_overloads(bb, total, port1, port2, port3);

let mut ports = PORTS.iter().map(|(name, base)| handle_port(bb, name, base));

Self {
overload,
port1: ports.next().unwrap(),
port2: ports.next().unwrap(),
port3: ports.next().unwrap(),
Expand Down
2 changes: 2 additions & 0 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
ProgressNotification,
LocatorNotification,
OverTemperatureNotification,
UsbOverloadNotification,
} from "./TacComponents";

function Navigation() {
Expand Down Expand Up @@ -159,6 +160,7 @@ function Notifications() {
<RebootNotification />
<OverTemperatureNotification />
<ProgressNotification />
<UsbOverloadNotification />
<UpdateNotification />
<LocatorNotification />
<IOBusFaultNotification />
Expand Down
Loading

0 comments on commit c19e3e9

Please sign in to comment.