BatteryWidget.cpp
Go to the documentation of this file.
1 #include "BatteryWidget.h"
2 
3 #include <QToolTip>
4 
5 namespace armarx
6 {
7 
8  constexpr std::string_view battery_master_default_name = "DiagnosticsUnit";
9 
10  BatteryIcon::BatteryIcon(QWidget* parent) : QWidget(parent)
11  {
12  QSizePolicy policy{QSizePolicy::Policy::Fixed, QSizePolicy::Policy::Fixed};
13  setSizePolicy(policy);
14  }
15 
16  void
17  BatteryIcon::paintEvent(QPaintEvent* event)
18  {
19  QWidget::paintEvent(event);
20 
21  int filled = std::clamp(percentage, 0, 100); // between 0 and 100
22  QColor fg(0, 200, 0);
23 
24  if (filled < 15)
25  {
26  fg = QColor(200, 0, 0);
27  }
28  else if (filled < 45)
29  {
30  fg = QColor(200, 100, 0);
31  }
32 
33  if (state != dto::BatteryState::discharging)
34  {
35  fg = QColor(255, 255, 255);
36  }
37 
38  // Draw inner and cap.
39  QColor bg(fg.red() * .6, fg.green() * .6, fg.blue() * .6);
40  int ht = event->rect().height();
41  int wd = ht / 2 - 1;
42  QPainter painter;
43  painter.setRenderHint(QPainter::Antialiasing, true);
44  painter.begin(this);
45  int m = 1 * ht / 10 + 1;
46  QRect cap(QPoint(m, m), QPoint(wd - m, 2 * m));
47  painter.fillRect(cap, filled == 100 ? fg : bg);
48  QRect body(QPoint(0, 2 * m), QPoint(wd, ht - m));
49  painter.fillRect(body, bg);
50  float dist = (1.0 - filled * .01);
51  QRect bodyFull(QPoint(0, 2 * m + dist * (ht - 3 * m)), QPoint(wd, ht - m));
52  painter.fillRect(bodyFull, fg);
53 
54  // Draw a border.
55  int lWd = ht / 40;
56  QRect a;
57  a = QRect(QPoint(m, m), QPoint(wd - m, m + lWd));
58  painter.fillRect(a, bg);
59  a = QRect(QPoint(wd - m - lWd, m), QPoint(wd - m, 2 * m + lWd));
60  painter.fillRect(a, bg);
61  a = QRect(QPoint(wd - m - lWd, 2 * m), QPoint(wd, 2 * m + lWd));
62  painter.fillRect(a, bg);
63  a = QRect(QPoint(wd - lWd, 2 * m), QPoint(wd, ht - m));
64  painter.fillRect(a, bg);
65  a = QRect(QPoint(0, ht - m - lWd), QPoint(wd, ht - m));
66  painter.fillRect(a, bg);
67  a = QRect(QPoint(0, 2 * m), QPoint(lWd, ht - m));
68  painter.fillRect(a, bg);
69  a = QRect(QPoint(0, 2 * m), QPoint(m + lWd, 2 * m + lWd));
70  painter.fillRect(a, bg);
71  a = QRect(QPoint(m, m), QPoint(m + lWd, 2 * m + lWd));
72  painter.fillRect(a, bg);
73 
74  // Draw lightning if charging.
75  if (state == dto::BatteryState::charging)
76  {
77  QPolygon poly;
78  float lHeight = .6 * ht;
79  int xOff = .1 * ht + 1;
80  int yOff = .2 * ht + 1;
81  poly << QPoint(xOff + 7 * lHeight / 20, yOff)
82  << QPoint(xOff + 5 * lHeight / 20, yOff + 4.5 * lHeight / 10)
83  << QPoint(xOff + 10 * lHeight / 20, yOff + 4.5 * lHeight / 10)
84  << QPoint(xOff + 3 * lHeight / 20, yOff + lHeight)
85  << QPoint(xOff + 5 * lHeight / 20, yOff + 5.5 * lHeight / 10)
86  << QPoint(xOff + 0 * lHeight / 20, yOff + 5.5 * lHeight / 10)
87  << QPoint(xOff + 7 * lHeight / 20, yOff);
88  QPainterPath path;
89  path.addPolygon(poly);
90  painter.fillPath(path, QColor(40, 40, 40));
91  }
92 
93  // Draw cross if unavailable.
94  if (state == dto::BatteryState::unavailable)
95  {
96  int u = .1 * wd;
97  QPolygon poly;
98  poly << QPoint(u, .1 * ht) << QPoint(wd - 2 * u, .9 * ht) << QPoint(wd - u, .9 * ht)
99  << QPoint(2 * u, .1 * ht) << QPoint(u, .1 * ht);
100  QPainterPath path;
101  path.addPolygon(poly);
102  painter.fillPath(path, QColor(200, 0, 0));
103  QPolygon poly2;
104  poly2 << QPoint(wd - 2 * u, .1 * ht) << QPoint(wd - u, .1 * ht)
105  << QPoint(2 * u, .9 * ht) << QPoint(u, .9 * ht) << QPoint(wd - 2 * u, .1 * ht);
106  QPainterPath path2;
107  path2.addPolygon(poly2);
108  painter.fillPath(path2, QColor(200, 0, 0));
109  }
110 
111  painter.end();
112  }
113 
114  void
116  {
117  this->percentage = std::clamp(percentage, 0, 100);
118  }
119 
120  void
122  {
123  this->state = state;
124  }
125 
126  QSize
128  {
129  return QSize{16, 32};
130  }
131 
132  BatteryWidget::BatteryWidget(QWidget* parent, ArmarXMainWindow* mainWindow) :
133  mainWindow(mainWindow), timer(new QTimer(this))
134  {
135  qRegisterMetaType<dto::BatteryStatus>("dto::BatteryStatus");
136  QGridLayout* layout = new QGridLayout(this->getWidget());
137  this->getWidget()->setContentsMargins(4, 0, 0, 0);
138  icon = new BatteryIcon();
139  layout->addWidget(icon, 0, 0);
140  layout->setContentsMargins(0, 0, 0, 0);
141  this->getWidget()->setLayout(layout);
142  icon->setVisible(true);
143  lbl = new QLabel();
144  txtPercentage = QString("%1%").arg(lastStatus.energyFromFullCharge_pct);
145  lbl->setText(txtPercentage);
146  lbl->setVisible(false);
147  layout->addWidget(lbl, 0, 1);
148 
149  qRegisterMetaType<dto::BatteryState>("BatteryState");
150 
151  timer->setInterval(std::chrono::milliseconds(1'000));
152  connect(timer, &QTimer::timeout, this, &BatteryWidget::updateBatteryStatus);
153  connect(this, &BatteryWidget::startPeriodicStateUpdate, timer, qOverload<>(&QTimer::start));
154  connect(this, &BatteryWidget::stopPeriodicStateUpdate, timer, &QTimer::stop);
155 
156  QString style = QString("QToolTip {"
157  // StyleSheet for tool tip
158  " background: white;"
159  " padding: 3px;"
160  "}");
161  getWidget()->setStyleSheet(style);
162 
163  updateTooltip();
164  }
165 
166  QWidget*
167  BatteryWidget::getBatteryWidget()
168  {
169  return this->getWidget();
170  }
171 
172  void
173  BatteryWidget::updateTooltip()
174  {
175  dto::BatteryState state = lastStatus.state;
176 
177  QString text;
178 
179  if (state == dto::BatteryState::unavailable)
180  {
181  text = "No battery detected.";
182  }
183  else if (state == dto::BatteryState::full)
184  {
185  text = "Battery fully charged.";
186  }
187  else
188  {
189  QString status;
190  switch (state)
191  {
192  case dto::BatteryState::charging:
193  status = "charging";
194  break;
195  case dto::BatteryState::discharging:
196  status = "discharging";
197  break;
198  case dto::BatteryState::full:
199  status = "full";
200  break;
201  case dto::BatteryState::notCharging:
202  status = "not charging";
203  break;
204  case dto::BatteryState::unavailable:
205  status = "unavailable";
206  break;
207  }
208 
209  QString detailedReport;
210  {
211  QString charge = QString("&nbsp;- Charge: %1<br>").arg(txtPercentage);
212 
213  QString energy = QString("&nbsp;- Energy: %2 Wh / %3 Wh<br>")
214  .arg(lastStatus.energy_Wh, 0, 'f', 1)
215  .arg(lastStatus.fullChargeEnergy_Wh, 0, 'f', 1);
216 
217  QString power = QString("&nbsp;- %1: %2 W (%3 A, %4 V)<br>")
218  .arg(state == dto::BatteryState::charging ? "Charging power"
219  : "Provided power")
220  .arg(std::abs(lastStatus.power_W), 0, 'f', 1)
221  .arg(std::abs(lastStatus.current_A), 0, 'f', 1)
222  .arg(lastStatus.voltage_V, 0, 'f', 1);
223 
224  bool showPrognosis =
225  state == dto::BatteryState::charging or state == dto::BatteryState::discharging;
226  QString prognosis = "";
227  if (showPrognosis)
228  {
229  QString fullOrEmpty = state == dto::BatteryState::charging ? "Full" : "Empty";
230  prognosis = QString("&nbsp;- %1 in: &asymp; %2 h <br>")
231  .arg(fullOrEmpty)
232  .arg(lastStatus.remainingTime_h, 0, 'f', 1);
233  }
234 
235  QString temperature = QString("&nbsp;- Temperature: %6 °C<br>")
236  .arg(lastStatus.temperature_degC, 0, 'f', 1);
237 
238  QString health = QString("&nbsp;- Health: %7%")
239  .arg(lastStatus.fullEnergyFromDesignEnergy_pct, 0, 'f', 1);
240 
241  detailedReport = QString("%1%2%3%4%5%6")
242  .arg(charge, energy, power, prognosis, temperature, health);
243  }
244 
245  QString errorsText =
246  lastStatus.hasError
247  ? "<br><b><font color='red'>Battery error occured! Check the logs.</font></b>"
248  : "";
249 
250  text = QString("Battery %1.<br>%2%3").arg(status).arg(detailedReport).arg(errorsText);
251  }
252 
253  getWidget()->setToolTip(text);
254 
255  if (not QToolTip::isVisible())
256  {
257  return;
258  }
259 
260  QPoint p = getWidget()->mapFromGlobal(QCursor::pos());
261 
262  if (p.x() >= 0 and p.y() >= 0 and p.x() < getWidget()->width() and
263  p.y() < getWidget()->height())
264  {
265  QToolTip::showText(QCursor::pos(), text);
266  }
267  }
268 
269  void
270  BatteryWidget::onInitComponent()
271  {
272  usingProxy(std::string{battery_master_default_name});
273  }
274 
275  void
276  BatteryWidget::onConnectComponent()
277  {
278  QMetaObject::invokeMethod(icon, "setVisible", Qt::QueuedConnection, Q_ARG(bool, true));
279 
280  ARMARX_INFO << "Battery widget connected.";
281  batteryManagement = getProxy<armarx::BatteryManagementInterfacePrx>(
282  std::string{battery_master_default_name});
283 
284  emit startPeriodicStateUpdate();
285  }
286 
287  void
288  BatteryWidget::onDisconnectComponent()
289  {
290  ARMARX_IMPORTANT
291  << "Battery widget disconnected. This is expected if the earlier connected "
292  "robot unit shut down.";
293 
294  QMetaObject::invokeMethod(this,
295  "setBatteryStatus",
296  Qt::QueuedConnection,
297  Q_ARG(dto::BatteryStatus, invalidStatus));
298 
299  emit stopPeriodicStateUpdate();
300  }
301 
302  void
303  BatteryWidget::loadSettings(QSettings* settings)
304  {
305  ;
306  }
307 
308  void
309  BatteryWidget::saveSettings(QSettings* settings)
310  {
311  ;
312  }
313 
314  void
315  BatteryWidget::updateBatteryStatus()
316  {
317  dto::BatteryStatus newStatus;
318 
319  try
320  {
321  newStatus = batteryManagement->getBatteryStatus();
322  }
323  catch (...)
324  {
325  ARMARX_DEBUG << deactivateSpam(60) << "Failed to get current battery percentage";
326  newStatus.state = dto::BatteryState::unavailable;
327  }
328 
329  QMetaObject::invokeMethod(
330  this, "setBatteryStatus", Qt::QueuedConnection, Q_ARG(dto::BatteryStatus, newStatus));
331  }
332 
333  void
334  BatteryWidget::setBatteryStatus(dto::BatteryStatus batteryStatus)
335  {
336  lastStatus = batteryStatus;
337 
338  icon->setPercentage(lastStatus.energyFromFullCharge_pct);
339  txtPercentage = QString("%1%").arg(lastStatus.energyFromFullCharge_pct, 0, 'f', 1);
340  lbl->setText(txtPercentage);
341 
342  dto::BatteryState state = lastStatus.state;
343 
344  icon->setState(state);
345  if (state == dto::BatteryState::unavailable)
346  {
347  lbl->setVisible(false);
348  }
349  else
350  {
351  lbl->setVisible(true);
352  }
353 
354  updateTooltip();
355  icon->update();
356  }
357 
358  std::string
359  BatteryWidget::getDefaultName() const
360  {
361  return "BatteryWidget" + iceNameUUID;
362  }
363 
364 } // namespace armarx
armarx::BatteryIcon::setState
void setState(dto::BatteryState state)
Definition: BatteryWidget.cpp:121
armarx::BatteryWidget::BatteryWidget
BatteryWidget(QWidget *parent=0, ArmarXMainWindow *mainWindow=0)
Definition: BatteryWidget.cpp:132
armarx::ArmarXMainWindow
The ArmarXMainWindow class.
Definition: ArmarXMainWindow.h:76
armarx::BatteryState
BatteryState
Definition: SensorValueBattery.h:10
clamp
double clamp(double x, double a, double b)
Definition: point.hpp:125
armarx::BatteryIcon::BatteryIcon
BatteryIcon(QWidget *parent=0)
Definition: BatteryWidget.cpp:10
armarx::ctrlutil::a
double a(double t, double a0, double j)
Definition: CtrlUtil.h:45
armarx::BatteryIcon::paintEvent
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE
Definition: BatteryWidget.cpp:17
BatteryWidget.h
armarx::BatteryIcon::sizeHint
QSize sizeHint() const Q_DECL_OVERRIDE
Definition: BatteryWidget.cpp:127
armarx::red
QColor red()
Definition: StyleSheets.h:76
armarx::BatteryIcon
Definition: BatteryWidget.h:24
armarx::battery_master_default_name
constexpr std::string_view battery_master_default_name
Definition: BatteryWidget.cpp:8
armarx::ArmarXWidgetController::getWidget
virtual QPointer< QWidget > getWidget()
getWidget returns a pointer to the a widget of this controller.
Definition: ArmarXWidgetController.cpp:54
armarx::BatteryIcon::setPercentage
void setPercentage(int percentage)
Definition: BatteryWidget.cpp:115
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:28