Skip to content

Commit faeb54d

Browse files
authored
Merge pull request #34 from hnez/dut-pwr-alerts
dut_pwr: display notifcations on power failure
2 parents c19e3e9 + 3745abd commit faeb54d

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed

src/ui/screens.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod iobus_health;
3333
mod locator;
3434
mod overtemperature;
3535
mod power;
36+
mod power_fail;
3637
mod reboot;
3738
mod screensaver;
3839
mod setup;
@@ -50,6 +51,7 @@ use iobus_health::IoBusHealthScreen;
5051
use locator::LocatorScreen;
5152
use overtemperature::OverTemperatureScreen;
5253
use power::PowerScreen;
54+
use power_fail::PowerFailScreen;
5355
use reboot::RebootConfirmScreen;
5456
use screensaver::ScreenSaverScreen;
5557
use setup::SetupScreen;
@@ -82,6 +84,7 @@ pub enum NormalScreen {
8284
pub enum AlertScreen {
8385
ScreenSaver,
8486
IoBusHealth,
87+
PowerFail,
8588
Locator,
8689
RebootConfirm,
8790
UpdateAvailable,
@@ -211,5 +214,6 @@ pub(super) fn init(
211214
)),
212215
Box::new(LocatorScreen::new(alerts, locator)),
213216
Box::new(UsbOverloadScreen::new(alerts, &res.usb_hub.overload)),
217+
Box::new(PowerFailScreen::new(alerts, &res.dut_pwr.state)),
214218
]
215219
}

src/ui/screens/power_fail.rs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
use serde::{Deserialize, Serialize};
26+
27+
use super::widgets::*;
28+
use super::{
29+
row_anchor, ActivatableScreen, ActiveScreen, AlertList, AlertScreen, Alerter, Display,
30+
InputEvent, Screen, Ui,
31+
};
32+
use crate::broker::Topic;
33+
use crate::dut_power::{OutputRequest, OutputState};
34+
35+
const SCREEN_TYPE: AlertScreen = AlertScreen::PowerFail;
36+
37+
pub struct PowerFailScreen;
38+
39+
#[derive(Serialize, Deserialize, Clone)]
40+
enum Highlight {
41+
TurnOn,
42+
KeepOff,
43+
}
44+
45+
impl Highlight {
46+
fn next(&self) -> Self {
47+
match self {
48+
Self::TurnOn => Self::KeepOff,
49+
Self::KeepOff => Self::TurnOn,
50+
}
51+
}
52+
}
53+
54+
struct Active {
55+
widgets: WidgetContainer,
56+
highlight: Arc<Topic<Highlight>>,
57+
request: Arc<Topic<OutputRequest>>,
58+
}
59+
60+
impl PowerFailScreen {
61+
pub fn new(alerts: &Arc<Topic<AlertList>>, out_state: &Arc<Topic<OutputState>>) -> Self {
62+
let (mut out_state_events, _) = out_state.clone().subscribe_unbounded();
63+
64+
let alerts = alerts.clone();
65+
66+
spawn(async move {
67+
while let Some(state) = out_state_events.next().await {
68+
match state {
69+
OutputState::On | OutputState::Off | OutputState::OffFloating => {
70+
alerts.deassert(SCREEN_TYPE)
71+
}
72+
OutputState::InvertedPolarity
73+
| OutputState::OverCurrent
74+
| OutputState::OverVoltage
75+
| OutputState::RealtimeViolation => alerts.assert(SCREEN_TYPE),
76+
OutputState::Changing => {}
77+
}
78+
}
79+
});
80+
81+
Self
82+
}
83+
}
84+
85+
impl ActivatableScreen for PowerFailScreen {
86+
fn my_type(&self) -> Screen {
87+
Screen::Alert(SCREEN_TYPE)
88+
}
89+
90+
fn activate(&mut self, ui: &Ui, display: Display) -> Box<dyn ActiveScreen> {
91+
let ui_text_style: MonoTextStyle<BinaryColor> =
92+
MonoTextStyle::new(&UI_TEXT_FONT, BinaryColor::On);
93+
94+
display.with_lock(|target| {
95+
Text::new(
96+
"DUT Power error",
97+
row_anchor(0) - (row_anchor(1) - row_anchor(0)),
98+
ui_text_style,
99+
)
100+
.draw(target)
101+
.unwrap();
102+
});
103+
104+
let mut widgets = WidgetContainer::new(display);
105+
106+
widgets.push(|display| {
107+
DynamicWidget::text(
108+
ui.res.dut_pwr.state.clone(),
109+
display,
110+
row_anchor(2),
111+
Box::new(|state: &OutputState| {
112+
let msg = match state {
113+
OutputState::On | OutputState::Off | OutputState::OffFloating => {
114+
"The error was resolved"
115+
}
116+
OutputState::InvertedPolarity => {
117+
"Output disabled due to\ninverted polarity."
118+
}
119+
OutputState::OverCurrent => "DUT powered off due to\nan overcurrent event.",
120+
OutputState::OverVoltage => "DUT powered off due to\nan overvoltage event.",
121+
OutputState::RealtimeViolation => {
122+
"Output disabled due to\na realtime violation."
123+
}
124+
OutputState::Changing => "",
125+
};
126+
127+
msg.to_string()
128+
}),
129+
)
130+
});
131+
132+
let highlight = Topic::anonymous(Some(Highlight::KeepOff));
133+
134+
widgets.push(|display| {
135+
DynamicWidget::text(
136+
highlight.clone(),
137+
display,
138+
row_anchor(6),
139+
Box::new(|highlight: &Highlight| {
140+
let msg = match highlight {
141+
Highlight::TurnOn => "> Turn output back on\n Keep output off",
142+
Highlight::KeepOff => " Turn output back on\n> Keep output off",
143+
};
144+
145+
msg.to_string()
146+
}),
147+
)
148+
});
149+
150+
let request = ui.res.dut_pwr.request.clone();
151+
152+
Box::new(Active {
153+
widgets,
154+
highlight,
155+
request,
156+
})
157+
}
158+
}
159+
160+
#[async_trait]
161+
impl ActiveScreen for Active {
162+
fn my_type(&self) -> Screen {
163+
Screen::Alert(SCREEN_TYPE)
164+
}
165+
166+
async fn deactivate(mut self: Box<Self>) -> Display {
167+
self.widgets.destroy().await
168+
}
169+
170+
fn input(&mut self, ev: InputEvent) {
171+
match ev {
172+
InputEvent::NextScreen => {}
173+
InputEvent::ToggleAction(_) => {
174+
self.highlight
175+
.modify(|highlight| highlight.map(|s| s.next()));
176+
}
177+
InputEvent::PerformAction(_) => match self.highlight.try_get() {
178+
Some(Highlight::TurnOn) => self.request.set(OutputRequest::On),
179+
Some(Highlight::KeepOff) => self.request.set(OutputRequest::Off),
180+
None => {}
181+
},
182+
}
183+
}
184+
}

web/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
IOBusFaultNotification,
3535
RebootNotification,
3636
UpdateNotification,
37+
PowerFailNotification,
3738
ProgressNotification,
3839
LocatorNotification,
3940
OverTemperatureNotification,
@@ -161,6 +162,7 @@ function Notifications() {
161162
<OverTemperatureNotification />
162163
<ProgressNotification />
163164
<UsbOverloadNotification />
165+
<PowerFailNotification />
164166
<UpdateNotification />
165167
<LocatorNotification />
166168
<IOBusFaultNotification />

web/src/TacComponents.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ enum UsbOverload {
9696
Port3 = "Port3",
9797
}
9898

99+
enum OutputState {
100+
On = "On",
101+
Off = "Off",
102+
OffFloating = "OffFloating",
103+
Changing = "Changing",
104+
InvertedPolarity = "InvertedPolarity",
105+
OverCurrent = "OverCurrent",
106+
OverVoltage = "OverVoltage",
107+
RealtimeViolation = "RealtimeViolation",
108+
}
109+
99110
type Duration = {
100111
secs: number;
101112
nanos: number;
@@ -563,3 +574,48 @@ export function UsbOverloadNotification() {
563574
</Alert>
564575
);
565576
}
577+
578+
export function PowerFailNotification() {
579+
const state = useMqttSubscription<OutputState>("/v1/dut/powered");
580+
581+
let reason = null;
582+
583+
switch (state) {
584+
case OutputState.InvertedPolarity:
585+
reason = "an inverted polarity event";
586+
break;
587+
case OutputState.OverCurrent:
588+
reason = "an overcurrent event";
589+
break;
590+
case OutputState.OverVoltage:
591+
reason = "an overvoltage event";
592+
break;
593+
case OutputState.RealtimeViolation:
594+
reason = "a realtime violation";
595+
break;
596+
}
597+
598+
return (
599+
<Alert
600+
statusIconAriaLabel="Info"
601+
visible={reason !== null}
602+
action={
603+
<SpaceBetween size="xs">
604+
<MqttButton iconName="refresh" topic="/v1/dut/powered" send={"On"}>
605+
Turn DUT back on
606+
</MqttButton>
607+
<MqttButton
608+
iconName="status-stopped"
609+
topic="/v1/dut/powered"
610+
send={"Off"}
611+
>
612+
Keep DUT powered off
613+
</MqttButton>
614+
</SpaceBetween>
615+
}
616+
header="DUT powered off"
617+
>
618+
The DUT was powered off due to {reason}.
619+
</Alert>
620+
);
621+
}

0 commit comments

Comments
 (0)