(data)
| 327 | // ---- Data handling ----------------------------------------------------- |
| 328 | |
| 329 | _handleData(data) { |
| 330 | this._lastMessage = data; |
| 331 | |
| 332 | // Track the server's source field from each frame so the UI |
| 333 | // can react if the server switches between esp32 ↔ simulated at runtime. |
| 334 | if (data.source && this._state === 'connected') { |
| 335 | const raw = data.source; |
| 336 | if (raw !== this._serverSource) { |
| 337 | this._applyServerSource(raw); |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | // Update RSSI history for sparkline |
| 342 | if (data.features && data.features.mean_rssi != null) { |
| 343 | this._rssiHistory.push(data.features.mean_rssi); |
| 344 | if (this._rssiHistory.length > this._maxHistory) { |
| 345 | this._rssiHistory.shift(); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | // Per-node RSSI tracking |
| 350 | if (!this._perNodeRssiHistory) this._perNodeRssiHistory = {}; |
| 351 | if (data.node_features) { |
| 352 | for (const nf of data.node_features) { |
| 353 | if (!this._perNodeRssiHistory[nf.node_id]) { |
| 354 | this._perNodeRssiHistory[nf.node_id] = []; |
| 355 | } |
| 356 | this._perNodeRssiHistory[nf.node_id].push(nf.rssi_dbm); |
| 357 | if (this._perNodeRssiHistory[nf.node_id].length > this._maxHistory) { |
| 358 | this._perNodeRssiHistory[nf.node_id].shift(); |
| 359 | } |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | // Notify all listeners |
| 364 | for (const cb of this._listeners) { |
| 365 | try { |
| 366 | cb(data); |
| 367 | } catch (e) { |
| 368 | console.error('[Sensing] Listener error:', e); |
| 369 | } |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | // ---- State management -------------------------------------------------- |
| 374 |
no test coverage detected