WIP: Battery Display Feature Implementation #31
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "notfence/meshmap-lite:master"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Battery Display Feature Implementation
WARNING! Vibecoded (but tested as much as I can)
Please check code before merging!
Overview
This document describes the implementation of a battery display feature for the Meshmap-Lite application. When users click on a node marker on the map, a popup now displays the node's battery voltage and charge level (if available), alongside a battery icon.
Feature Requirements
0%as a valid value (not as "no data")Technical Implementation
1. Backend Changes
internal/repo/repo.goChange: Added optional
Telemetryfield to theMapNodestructWhy: Map nodes now include the latest telemetry data, allowing the frontend to display battery information alongside node identity and position data.
internal/persistence/sqlite/store.goChanges:
Modified
GetMapNodes()function:node_telemetry_snapshotstablepower_voltage,power_battery_level,source_channel,observed_at,updated_atCreated
scanMapNodeWithTelemetry()function:scanMapNode())NodeTelemetrySnapshotfrom telemetry columns(domain.Node, *domain.NodePosition, *domain.NodeTelemetrySnapshot, error)nilif no telemetry existsWhy: This ensures that every map node has the latest telemetry data fetched in a single database query, avoiding N+1 query problems.
2. Frontend Changes
web/src/api/types.tsChange: Added optional
telemetryfield to theMapNodeinterfaceWhy: TypeScript interface must match the backend API response structure.
web/src/maps/leafletMap.tsChanges:
Added
STALE_TELEMETRY_AGE_MSconstant:Defines the maximum age of telemetry data (1 hour). Data older than this is considered stale and won't be displayed.
Created
formatBatteryInfo()function:Purpose: Formats battery data for display
Logic:
!== undefined && !== nullchecks)4.03V95%map-popup-batteryfor stylingKey Detail: Uses explicit null/undefined checks instead of truthy checks to properly handle
0%as a valid valueExample outputs:
<span class="map-popup-battery">4.03V 🔋 95%</span><span class="map-popup-battery">4.03V 🔋</span><span class="map-popup-battery">🔋 0%</span>''(empty string)Created
isTelemetryStale()function:Purpose: Checks if telemetry data is too old to display
Logic:
trueif telemetry is undefined/nullobserved_attrueif age exceedsSTALE_TELEMETRY_AGE_MS(1 hour)Why: Ensures stale data doesn't persist on the map after a node stops transmitting
Updated
popupHtml()function:Changes:
telemetryparameterformatBatteryInfo()only if telemetry is fresh.map-popup-details-sectioncontainer alongside the "Details" linkDecision Logic:
Updated marker rendering in
render()method:n.telemetryto thepopupHtml()functionweb/src/styles.cssChanges:
Replaced the old
.map-popup-actionssingle-action layout with a new flexbox layout:Layout: A horizontal flexbox that places the "Details" link on the left and battery info on the right, separated by available space.
Data Flow
Edge Cases Handled
Testing Checklist
0%displays correctly (not hidden as falsy value)Constants Configuration
The telemetry staleness threshold can be adjusted in
web/src/maps/leafletMap.ts:Change the value for different thresholds:
3 * 60 * 1000= 3 minutes (testing)15 * 60 * 1000= 15 minutes60 * 60 * 1000= 1 hour (current)24 * 60 * 60 * 1000= 1 day7 * 24 * 60 * 60 * 1000= 7 daysFiles Modified
Backend
internal/repo/repo.go- MapNode structinternal/persistence/sqlite/store.go- GetMapNodes() and scanMapNodeWithTelemetry()Frontend
web/src/api/types.ts- MapNode interfaceweb/src/maps/leafletMap.ts- Constants, formatBatteryInfo(), isTelemetryStale(), popupHtml()web/src/styles.css- Battery display stylingPerformance Considerations
Future Enhancements
Potential improvements for future iterations:
STALE_TELEMETRY_AGE_MSconfigurable via API responseRelated Documentation
internal/domain/models.go-NodeTelemetrySnapshotdocs/api.md-/api/v1/map/nodesendpointweb/src/stores/nodes.ts- State managementOther changes
masterto Battery Display Feature ImplementationВ целом более-менее ок, но нужно сделать ряд исправлений и убедиться в том, что фича реализована полноценно (см. тред про WebSocket).
Нужно убрать лишнее, сделать мелкие правки по неявному неймингу и убедиться в полноценной реализации обновления состояния.
@ -24,3 +24,2 @@1. `go run ./cmd/server --config ./config.example.yaml`2. Open `http://localhost:8080`1. `git clone https://git.skobk.in/skobkin/meshmap-lite.git`Не нужно менять документацию в PR не касающемся документации если документация не касается фичи, которая меняется.
@ -1,6 +1,6 @@module meshmap-litego 1.25go 1.25.0Не нужно коммитить такие изменения. От них сборка фичи не зависит.
@ -702,6 +704,94 @@ func scanMapNode(rows *sql.Rows) (domain.Node, *domain.NodePosition, error) {return n, pos, nil}func scanMapNodeWithTelemetry(rows *sql.Rows) (domain.Node, *domain.NodePosition, *domain.NodeTelemetrySnapshot, error) {Этот метод стоит расположить рядом с оригинальным методом
scanMapNode.вроде бы и так рядом? func scanMapNodeWithTelemetry идет сразу после func scanMapNode
@ -705,0 +769,4 @@}}var telemetry *domain.NodeTelemetrySnapshotТут стоило бы посмотреть как сейчас упаковывается телеметрия и, возможно, использовать общий код для этого чтобы не создавать разночтений.
@ -54,7 +54,6 @@"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==","dev": true,"license": "MIT","peer": true,Этих изменений в PR не должно быть.
@ -129,3 +130,3 @@row('FW', displayValue(n.node.firmware_version))]))]))]), n.telemetry)Коммент не к этой конкретно сточке, а чтобы открыть тред, который должен быть закрыт перед мерджем.
Не увидел ни в коде ни в описании PR в эджкейсах или тестировании ничего о live-апдейтах из вебсокетов.
Ответы на вопросы о live-обновлениях батареи
1️⃣ Мы получаем из вебсокетов события с телеметрией на фронтенд?
✅ ДА, события отправляются.
Бэкенд отправляет событие:
Событие типа "node.telemetry" содержит полный NodeTelemetrySnapshot с voltage, battery_level и observed_at.
2️⃣ Мы их обрабатываем?
❌ НЕТ
Посмотри на ws.ts (строка 47-90):
Событие "node.telemetry" просто игнорируется!
3️⃣ Хранилище состояния нод на фронтенде обновляется из этих данных?
❌ НЕТ.
В nodes.ts есть методы:
upsertMapNode(item: MapNode) — обновляет весь MapNode с телеметрией
upsertNode(node: Node) — обновляет только Node
upsertPosition(position: NodePosition) — обновляет только Position
НЕТ метода upsertTelemetry()
События батареи вообще не попадают в стор.
4️⃣ Попап на маркере будет обновлён после поступления такого события?
❌ НЕТ, попап не обновится.
Попап перерендерится только если:
Пользователь повторно откроет его
Произойдет перезагрузка всего списка нод через REST API
🔴 Что нужно сделать перед мерджем
Нужно добавить обработчик события "node.telemetry" в ws.ts:
Плюс нужна guard-функция:
Это обязательно перед мерджем, иначе батарея не будет обновляться в реальном времени!
//
от себя скажу, процент и вольтаж обновляются только при перезагрузке страницы. В принципе нам и не нужно в реальном времени обновлять эти данные
Это идёт вразрез с текущей концепцией карты. Она по возможности должна без обновления показывать актуальную информацию если это не вызывает каких-то совсем неприемлемых доработок.
@ -488,0 +494,4 @@return ''}const voltage = hasVoltage ? power.voltage!.toFixed(2) + 'V' : ''@ -488,0 +496,4 @@const voltage = hasVoltage ? power.voltage!.toFixed(2) + 'V' : ''// Handle 0% as a valid value - use explicit null/undefined checks instead of truthy checkconst battery = hasBatteryLevel ? Math.round(power.battery_level!) + '%' : ''@ -488,0 +508,4 @@return parts.length > 0 ? `<span class="map-popup-battery">${parts.join(' ')}</span>` : ''}function isTelemetryStale(telemetry?: { observed_at: string }): boolean {По-хорошему это должно управляться с бэкенда из конфигурации.
Но я заведу на это #32 сам.
Battery Display Feature Implementationto WIP: Battery Display Feature Implementation77721d22b3to9d429fa3a59d429fa3a5to781e53e57dClosing in favor of #33
Pull request closed