|
12 | 12 | * displays a surface that is provided via shm by external program. Right now it is used to display |
13 | 13 | * MSP/Displayport OSD. |
14 | 14 | */ |
| 15 | +#include <cmath> |
15 | 16 | extern "C" { |
16 | 17 | #include "drm.h" |
17 | 18 | #include "mavlink.h" |
@@ -344,6 +345,60 @@ class Fact { |
344 | 345 | return type != T_UNDEF; |
345 | 346 | } |
346 | 347 |
|
| 348 | + operator bool() { |
| 349 | + switch (type) { |
| 350 | + case T_BOOL: |
| 351 | + return getBoolValue(); |
| 352 | + case T_UINT: |
| 353 | + return getUintValue() != 0; |
| 354 | + case T_INT: |
| 355 | + return getIntValue() != 0; |
| 356 | + case T_DOUBLE: |
| 357 | + return getDoubleValue() != 0.0; |
| 358 | + case T_STRING: |
| 359 | + return getStrValue() != ""; |
| 360 | + } |
| 361 | + } |
| 362 | + |
| 363 | + operator long() { |
| 364 | + switch (type) { |
| 365 | + case T_BOOL: |
| 366 | + return getBoolValue() ? 1 : 0; |
| 367 | + case T_UINT: |
| 368 | + return (long)getUintValue(); |
| 369 | + case T_INT: |
| 370 | + return getIntValue(); |
| 371 | + case T_DOUBLE: |
| 372 | + return round(getDoubleValue()); |
| 373 | + } |
| 374 | + } |
| 375 | + |
| 376 | + operator ulong() { |
| 377 | + switch (type) { |
| 378 | + case T_BOOL: |
| 379 | + return getBoolValue() ? 1 : 0; |
| 380 | + case T_UINT: |
| 381 | + return getUintValue(); |
| 382 | + case T_INT: |
| 383 | + return (ulong)getIntValue(); |
| 384 | + case T_DOUBLE: |
| 385 | + return round(getDoubleValue()); |
| 386 | + } |
| 387 | + } |
| 388 | + |
| 389 | + operator double() { |
| 390 | + switch (type) { |
| 391 | + case T_BOOL: |
| 392 | + return getBoolValue() ? 1.0 : 0.0; |
| 393 | + case T_UINT: |
| 394 | + return getUintValue() * 1.0; |
| 395 | + case T_INT: |
| 396 | + return getIntValue() * 1.0; |
| 397 | + case T_DOUBLE: |
| 398 | + return getDoubleValue(); |
| 399 | + } |
| 400 | + } |
| 401 | + |
347 | 402 | // TODO: try to cast instead of crash |
348 | 403 | bool getBoolValue() const { |
349 | 404 | assertType(T_BOOL); |
@@ -1229,6 +1284,85 @@ class GPSWidget: public Widget { |
1229 | 1284 | } |
1230 | 1285 | }; |
1231 | 1286 |
|
| 1287 | +/** |
| 1288 | + * Widget that shows approximate voltage of a battery cell. |
| 1289 | + * If the number of cells is 0, it estimates it from the pack voltage based on max_voltage_mv; |
| 1290 | + * If the number of cells is -1, it estimates only even cell numbers (2, 4, 6, 8, ...) - this |
| 1291 | + * fixes the situation when, eg, discharged to 20v 6s LiIon would be recognized as 5s. |
| 1292 | + * |
| 1293 | + * Widget's text is drawn in white when battery is above 20% from critical. And below 20% it |
| 1294 | + * gradually transitions from yellow through orange to red. |
| 1295 | + */ |
| 1296 | +class BatteryCellWidget: public TplTextWidget { |
| 1297 | +public: |
| 1298 | + float warn_percentage = 0.2; |
| 1299 | + |
| 1300 | + BatteryCellWidget(int pos_x, int pos_y, |
| 1301 | + int critical_voltage_mv, int max_voltage_mv, int num_cells, |
| 1302 | + std::string tpl, uint num_args) : |
| 1303 | + TplTextWidget(pos_x, pos_y, tpl, num_args), critical_voltage_mv(critical_voltage_mv), |
| 1304 | + max_voltage_mv(max_voltage_mv), num_cells(num_cells) { |
| 1305 | + assert(num_args == 1); |
| 1306 | + }; |
| 1307 | + |
| 1308 | + virtual void setFact(uint idx, Fact fact) { |
| 1309 | + assert(idx == 0); |
| 1310 | + // replace the pack value with per-cell value |
| 1311 | + long voltage_mv = fact.getIntValue(); |
| 1312 | + int cells; |
| 1313 | + if (num_cells > 0) { |
| 1314 | + cells = num_cells; |
| 1315 | + } else if (num_cells == 0) { |
| 1316 | + // estimate any number of cells |
| 1317 | + cells = (voltage_mv / max_voltage_mv) + 1; |
| 1318 | + } else { |
| 1319 | + // estimate even number of cells |
| 1320 | + cells = (voltage_mv / max_voltage_mv) + 1; |
| 1321 | + if (cells % 2 != 0) { |
| 1322 | + cells++; |
| 1323 | + } |
| 1324 | + } |
| 1325 | + long cell_voltage_mv = voltage_mv / cells; |
| 1326 | + args[0] = Fact(FactMeta("volts"), (double)cell_voltage_mv / 1000.0); |
| 1327 | + } |
| 1328 | + |
| 1329 | + |
| 1330 | + virtual void draw(cairo_t *cr) { |
| 1331 | + auto [x, y] = xy(cr); |
| 1332 | + const Fact& fact = args[0]; |
| 1333 | + auto cell_voltage = fact.getDoubleValue(); |
| 1334 | + auto cell_voltage_mv = cell_voltage * 1000; |
| 1335 | + |
| 1336 | + std::unique_ptr<std::string> msg = render_tpl(); |
| 1337 | + |
| 1338 | + if (cell_voltage_mv <= critical_voltage_mv) { |
| 1339 | + // Draw in red |
| 1340 | + cairo_set_source_rgba(cr, 255.0, 0, 0, 1); |
| 1341 | + } else { |
| 1342 | + // Now we know voltage is above critical |
| 1343 | + float remaining_percentage = |
| 1344 | + (float)(cell_voltage_mv - critical_voltage_mv) / |
| 1345 | + (max_voltage_mv - critical_voltage_mv); |
| 1346 | + |
| 1347 | + if (remaining_percentage < warn_percentage) { |
| 1348 | + // Calculate green based on remaining percentage (0--warn_percentage% range) |
| 1349 | + double green_value = 255.0 * (remaining_percentage / warn_percentage); |
| 1350 | + // Transition from yellow through orange to red |
| 1351 | + cairo_set_source_rgba(cr, 255.0, green_value, 0, 1); |
| 1352 | + } else { |
| 1353 | + // White when above 20% |
| 1354 | + cairo_set_source_rgba(cr, 255.0, 255.0, 255.0, 1); |
| 1355 | + } |
| 1356 | + } |
| 1357 | + cairo_move_to(cr, x, y); |
| 1358 | + cairo_show_text(cr, msg->c_str()); |
| 1359 | + } |
| 1360 | +protected: |
| 1361 | + int critical_voltage_mv; |
| 1362 | + int max_voltage_mv; |
| 1363 | + int num_cells; |
| 1364 | +}; |
| 1365 | + |
1232 | 1366 | class DebugWidget: public Widget { |
1233 | 1367 | public: |
1234 | 1368 | DebugWidget(int pos_x, int pos_y, uint num_args) : |
@@ -1570,11 +1704,36 @@ class Osd { |
1570 | 1704 | matchers); |
1571 | 1705 | } else if (type == "GPSWidget") { |
1572 | 1706 | addWidget(new GPSWidget(x, y, (uint)matchers.size()), matchers); |
1573 | | - } else if(type == "PopupWidget") { |
| 1707 | + } else if (type == "BatteryCellWidget") { |
| 1708 | + int critical_mv = 3500; |
| 1709 | + int max_mv = 4200; |
| 1710 | + int num_cells = -1; |
| 1711 | + auto tpl = widget_j.at("template").template get<std::string>(); |
| 1712 | + if (widget_j.contains("critical_voltage")) { |
| 1713 | + critical_mv = (int)(widget_j.at("critical_voltage").template get<float>() * 1000); |
| 1714 | + } |
| 1715 | + if (widget_j.contains("max_voltage")) { |
| 1716 | + max_mv = (int)(widget_j.at("max_voltage").template get<float>() * 1000); |
| 1717 | + } |
| 1718 | + if (widget_j.contains("num_cells")) { |
| 1719 | + std::string cells = widget_j["num_cells"]; |
| 1720 | + if (cells == "auto") { |
| 1721 | + num_cells = 0; |
| 1722 | + } else if (cells == "even") { |
| 1723 | + num_cells = -1; |
| 1724 | + } else { |
| 1725 | + num_cells = widget_j["num_cells"].get<int>(); |
| 1726 | + } |
| 1727 | + } |
| 1728 | + assert(critical_mv < max_mv); |
| 1729 | + addWidget(new BatteryCellWidget(x, y, critical_mv, max_mv, num_cells, |
| 1730 | + tpl, (uint)matchers.size()), |
| 1731 | + matchers); |
| 1732 | + } else if (type == "PopupWidget") { |
1574 | 1733 | auto timeout_ms = widget_j.at("timeout_ms").template get<uint>(); |
1575 | 1734 | addWidget(new PopupWidget(x, y, timeout_ms, (uint)matchers.size()), |
1576 | 1735 | matchers); |
1577 | | - } else if(type == "DebugWidget") { |
| 1736 | + } else if (type == "DebugWidget") { |
1578 | 1737 | addWidget(new DebugWidget(x, y, (uint)matchers.size()), matchers); |
1579 | 1738 | } else { |
1580 | 1739 | spdlog::warn("Widget '{}': unknown type: {}", name, type); |
|
0 commit comments