Skip to content

Commit c19e3e9

Browse files
authored
Merge pull request #33 from hnez/usb-limits
usb: display power supply overload notifications
2 parents cca43b5 + 0fef424 commit c19e3e9

File tree

9 files changed

+312
-7
lines changed

9 files changed

+312
-7
lines changed

openapi.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,22 @@ paths:
272272
schema:
273273
$ref: '#/components/schemas/UsbDevice'
274274

275+
/v1/usb/host/overload:
276+
get:
277+
summary: Get the name of the currently overloaded port (if any)
278+
tags: [USB Host]
279+
responses:
280+
'200':
281+
content:
282+
application/json:
283+
schema:
284+
type: string
285+
enum:
286+
- Total
287+
- Port1
288+
- Port2
289+
- Port3
290+
275291
/v1/tac/temperatures/soc:
276292
get:
277293
summary: Get the current temperature inside the SoC

src/adc/iio/demo_mode.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,14 @@ impl CalibratedChannel {
128128

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

131+
let decay = if time_constant.abs() < 0.01 {
132+
0.0
133+
} else {
134+
(-dt / time_constant).exp()
135+
};
136+
131137
value -= nominal;
132-
value *= (-dt / time_constant).exp();
138+
value *= decay;
133139
value += (2.0 * thread_rng().gen::<f32>() - 1.0) * self.inner.noise;
134140
value += self
135141
.inner

src/main.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ async fn main() -> Result<()> {
8080
let dig_io = DigitalIo::new(&mut bb, led.out_0.clone(), led.out_1.clone());
8181
let regulators = Regulators::new(&mut bb);
8282
let temperatures = Temperatures::new(&mut bb);
83-
let usb_hub = UsbHub::new(&mut bb);
83+
let usb_hub = UsbHub::new(
84+
&mut bb,
85+
adc.usb_host_curr.fast.clone(),
86+
adc.usb_host1_curr.fast.clone(),
87+
adc.usb_host2_curr.fast.clone(),
88+
adc.usb_host3_curr.fast.clone(),
89+
);
8490

8591
// Expose other software on the TAC via the broker framework by connecting
8692
// to them via HTTP / DBus APIs.

src/ui/screens.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod uart;
4141
mod update_available;
4242
mod update_installation;
4343
mod usb;
44+
mod usb_overload;
4445

4546
use dig_out::DigOutScreen;
4647
use help::HelpScreen;
@@ -57,6 +58,7 @@ use uart::UartScreen;
5758
use update_available::UpdateAvailableScreen;
5859
use update_installation::UpdateInstallationScreen;
5960
use usb::UsbScreen;
61+
use usb_overload::UsbOverloadScreen;
6062

6163
use super::buttons;
6264
use super::widgets;
@@ -84,6 +86,7 @@ pub enum AlertScreen {
8486
RebootConfirm,
8587
UpdateAvailable,
8688
UpdateInstallation,
89+
UsbOverload,
8790
Help,
8891
Setup,
8992
OverTemperature,
@@ -207,5 +210,6 @@ pub(super) fn init(
207210
&res.temperatures.warning,
208211
)),
209212
Box::new(LocatorScreen::new(alerts, locator)),
213+
Box::new(UsbOverloadScreen::new(alerts, &res.usb_hub.overload)),
210214
]
211215
}

src/ui/screens/usb.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@ use super::{
2828
};
2929
use crate::broker::Topic;
3030
use crate::measurement::Measurement;
31+
use crate::usb_hub::{MAX_PORT_CURRENT, MAX_TOTAL_CURRENT};
3132

3233
const SCREEN_TYPE: NormalScreen = NormalScreen::Usb;
33-
const CURRENT_LIMIT_PER_PORT: f32 = 0.5;
34-
const CURRENT_LIMIT_TOTAL: f32 = 0.7;
3534
const OFFSET_INDICATOR: Point = Point::new(92, -10);
3635
const OFFSET_BAR: Point = Point::new(122, -14);
3736
const WIDTH_BAR: u32 = 90;
@@ -103,7 +102,7 @@ impl ActivatableScreen for UsbScreen {
103102
row_anchor(0) + OFFSET_BAR,
104103
WIDTH_BAR,
105104
HEIGHT_BAR,
106-
Box::new(|meas: &Measurement| meas.value / CURRENT_LIMIT_TOTAL),
105+
Box::new(|meas: &Measurement| meas.value / MAX_TOTAL_CURRENT),
107106
)
108107
});
109108

@@ -143,7 +142,7 @@ impl ActivatableScreen for UsbScreen {
143142
anchor_bar,
144143
WIDTH_BAR,
145144
HEIGHT_BAR,
146-
Box::new(|meas: &Measurement| meas.value / CURRENT_LIMIT_PER_PORT),
145+
Box::new(|meas: &Measurement| meas.value / MAX_PORT_CURRENT),
147146
)
148147
});
149148
}

src/ui/screens/usb_overload.rs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// This file is part of tacd, the LXA TAC system daemon
2+
// Copyright (C) 2023 Pengutronix e.K.
3+
//
4+
// This program is free software; you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation; either version 2 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License along
15+
// with this program; if not, write to the Free Software Foundation, Inc.,
16+
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17+
18+
use async_std::prelude::*;
19+
use async_std::sync::Arc;
20+
use async_std::task::spawn;
21+
use async_trait::async_trait;
22+
use embedded_graphics::{
23+
mono_font::MonoTextStyle, pixelcolor::BinaryColor, prelude::*, text::Text,
24+
};
25+
26+
use super::widgets::*;
27+
use super::{
28+
row_anchor, ActivatableScreen, ActiveScreen, AlertList, AlertScreen, Alerter, Display,
29+
InputEvent, Screen, Ui,
30+
};
31+
use crate::broker::Topic;
32+
use crate::measurement::Measurement;
33+
use crate::usb_hub::{OverloadedPort, MAX_PORT_CURRENT, MAX_TOTAL_CURRENT};
34+
35+
const SCREEN_TYPE: AlertScreen = AlertScreen::UsbOverload;
36+
const OFFSET_BAR: Point = Point::new(75, -14);
37+
const OFFSET_VAL: Point = Point::new(160, 0);
38+
const WIDTH_BAR: u32 = 80;
39+
const HEIGHT_BAR: u32 = 18;
40+
41+
pub struct UsbOverloadScreen;
42+
43+
struct Active {
44+
widgets: WidgetContainer,
45+
}
46+
47+
impl UsbOverloadScreen {
48+
pub fn new(
49+
alerts: &Arc<Topic<AlertList>>,
50+
overload: &Arc<Topic<Option<OverloadedPort>>>,
51+
) -> Self {
52+
let (mut overload_events, _) = overload.clone().subscribe_unbounded();
53+
let alerts = alerts.clone();
54+
55+
spawn(async move {
56+
while let Some(overload) = overload_events.next().await {
57+
if overload.is_some() {
58+
alerts.assert(SCREEN_TYPE)
59+
} else {
60+
alerts.deassert(SCREEN_TYPE)
61+
}
62+
}
63+
});
64+
65+
Self
66+
}
67+
}
68+
69+
impl ActivatableScreen for UsbOverloadScreen {
70+
fn my_type(&self) -> Screen {
71+
Screen::Alert(SCREEN_TYPE)
72+
}
73+
74+
fn activate(&mut self, ui: &Ui, display: Display) -> Box<dyn ActiveScreen> {
75+
let ui_text_style: MonoTextStyle<BinaryColor> =
76+
MonoTextStyle::new(&UI_TEXT_FONT, BinaryColor::On);
77+
78+
display.with_lock(|target| {
79+
Text::new(
80+
"USB Power Overload",
81+
row_anchor(0) - (row_anchor(1) - row_anchor(0)),
82+
ui_text_style,
83+
)
84+
.draw(target)
85+
.unwrap();
86+
87+
Text::new(
88+
"Disconnect devices or\nuse a powered hub.",
89+
row_anchor(1),
90+
ui_text_style,
91+
)
92+
.draw(target)
93+
.unwrap();
94+
95+
for (row, name) in &[(4, "Total"), (6, "Port 1"), (7, "Port 2"), (8, "Port 3")] {
96+
Text::new(name, row_anchor(*row), ui_text_style)
97+
.draw(target)
98+
.unwrap();
99+
}
100+
});
101+
102+
let mut widgets = WidgetContainer::new(display);
103+
104+
let ports = [
105+
(0, &ui.res.adc.usb_host_curr.topic, MAX_TOTAL_CURRENT),
106+
(2, &ui.res.adc.usb_host1_curr.topic, MAX_PORT_CURRENT),
107+
(3, &ui.res.adc.usb_host2_curr.topic, MAX_PORT_CURRENT),
108+
(4, &ui.res.adc.usb_host3_curr.topic, MAX_PORT_CURRENT),
109+
];
110+
111+
for (idx, current, max_current) in ports {
112+
let anchor_port = row_anchor(idx + 4);
113+
114+
widgets.push(|display| {
115+
DynamicWidget::bar(
116+
current.clone(),
117+
display,
118+
anchor_port + OFFSET_BAR,
119+
WIDTH_BAR,
120+
HEIGHT_BAR,
121+
Box::new(move |meas: &Measurement| meas.value / max_current),
122+
)
123+
});
124+
125+
widgets.push(|display| {
126+
DynamicWidget::text(
127+
current.clone(),
128+
display,
129+
anchor_port + OFFSET_VAL,
130+
Box::new(|meas: &Measurement| format!("{:>4.0}mA", meas.value * 1000.0)),
131+
)
132+
});
133+
}
134+
135+
Box::new(Active { widgets })
136+
}
137+
}
138+
139+
#[async_trait]
140+
impl ActiveScreen for Active {
141+
fn my_type(&self) -> Screen {
142+
Screen::Alert(SCREEN_TYPE)
143+
}
144+
145+
async fn deactivate(mut self: Box<Self>) -> Display {
146+
self.widgets.destroy().await
147+
}
148+
149+
fn input(&mut self, _ev: InputEvent) {}
150+
}

src/usb_hub.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use async_std::sync::Arc;
2323
use async_std::task::{sleep, spawn};
2424
use serde::{Deserialize, Serialize};
2525

26+
use crate::adc::CalibratedChannel;
2627
use crate::broker::{BrokerBuilder, Topic};
2728

2829
#[cfg(feature = "demo_mode")]
@@ -135,6 +136,45 @@ const PORTS: &[(&str, &str)] = &[
135136
),
136137
];
137138

139+
// The total current for all ports is limited to 700mA, the per-port current is
140+
// limited to 500mA.
141+
pub const MAX_TOTAL_CURRENT: f32 = 0.7;
142+
pub const MAX_PORT_CURRENT: f32 = 0.5;
143+
144+
// The measurement is not _that_ exact so start warning at 90% utilization.
145+
const CURRENT_MARGIN: f32 = 0.9;
146+
const WARN_TOTAL_CURRENT: f32 = MAX_TOTAL_CURRENT * CURRENT_MARGIN;
147+
const WARN_PORT_CURRENT: f32 = MAX_PORT_CURRENT * CURRENT_MARGIN;
148+
149+
#[derive(Serialize, Deserialize, Clone, PartialEq)]
150+
pub enum OverloadedPort {
151+
Total,
152+
Port1,
153+
Port2,
154+
Port3,
155+
}
156+
157+
impl OverloadedPort {
158+
fn from_currents(total: f32, port1: f32, port2: f32, port3: f32) -> Option<Self> {
159+
// Based on the maximum / per-port limits it should not be possible for two
160+
// individual ports to be overloaded at the same time while the total is not
161+
// overloaded, so reporting either "total" or one of the ports should be
162+
// sufficient.
163+
164+
if total > WARN_TOTAL_CURRENT {
165+
Some(Self::Total)
166+
} else if port1 > WARN_PORT_CURRENT {
167+
Some(Self::Port1)
168+
} else if port2 > WARN_PORT_CURRENT {
169+
Some(Self::Port2)
170+
} else if port3 > WARN_PORT_CURRENT {
171+
Some(Self::Port3)
172+
} else {
173+
None
174+
}
175+
}
176+
}
177+
138178
#[derive(Serialize, Deserialize, PartialEq, Clone)]
139179
pub struct UsbDevice {
140180
id_product: String,
@@ -151,6 +191,7 @@ pub struct UsbPort {
151191
}
152192

153193
pub struct UsbHub {
194+
pub overload: Arc<Topic<Option<OverloadedPort>>>,
154195
pub port1: UsbPort,
155196
pub port2: UsbPort,
156197
pub port3: UsbPort,
@@ -236,11 +277,49 @@ fn handle_port(bb: &mut BrokerBuilder, name: &'static str, base: &'static str) -
236277
port
237278
}
238279

280+
fn handle_overloads(
281+
bb: &mut BrokerBuilder,
282+
total: CalibratedChannel,
283+
port1: CalibratedChannel,
284+
port2: CalibratedChannel,
285+
port3: CalibratedChannel,
286+
) -> Arc<Topic<Option<OverloadedPort>>> {
287+
let overload = bb.topic_ro("/v1/usb/host/overload", None);
288+
289+
let overload_task = overload.clone();
290+
291+
spawn(async move {
292+
loop {
293+
let overloaded_port = OverloadedPort::from_currents(
294+
total.get().map(|m| m.value).unwrap_or(0.0),
295+
port1.get().map(|m| m.value).unwrap_or(0.0),
296+
port2.get().map(|m| m.value).unwrap_or(0.0),
297+
port3.get().map(|m| m.value).unwrap_or(0.0),
298+
);
299+
300+
overload_task.set_if_changed(overloaded_port);
301+
302+
sleep(POLL_INTERVAL).await;
303+
}
304+
});
305+
306+
overload
307+
}
308+
239309
impl UsbHub {
240-
pub fn new(bb: &mut BrokerBuilder) -> Self {
310+
pub fn new(
311+
bb: &mut BrokerBuilder,
312+
total: CalibratedChannel,
313+
port1: CalibratedChannel,
314+
port2: CalibratedChannel,
315+
port3: CalibratedChannel,
316+
) -> Self {
317+
let overload = handle_overloads(bb, total, port1, port2, port3);
318+
241319
let mut ports = PORTS.iter().map(|(name, base)| handle_port(bb, name, base));
242320

243321
Self {
322+
overload,
244323
port1: ports.next().unwrap(),
245324
port2: ports.next().unwrap(),
246325
port3: ports.next().unwrap(),

web/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
ProgressNotification,
3838
LocatorNotification,
3939
OverTemperatureNotification,
40+
UsbOverloadNotification,
4041
} from "./TacComponents";
4142

4243
function Navigation() {
@@ -159,6 +160,7 @@ function Notifications() {
159160
<RebootNotification />
160161
<OverTemperatureNotification />
161162
<ProgressNotification />
163+
<UsbOverloadNotification />
162164
<UpdateNotification />
163165
<LocatorNotification />
164166
<IOBusFaultNotification />

0 commit comments

Comments
 (0)