mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 14:11:54 -07:00
feat: Add device intelligence and manufacturer info for utility meters
- Add getMeterTypeInfo() with ERT endpoint type lookups for utility type (Electric/Gas/Water) and manufacturer (Itron, Landis+Gyr, Neptune, etc.) - Hook addRtlamrReading into trackDevice() for Device Intelligence panel - Add meter protocol handling to generateDeviceId() - Display manufacturer and utility type on meter cards - Show utility type as badge, manufacturer in meta row and details panel Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -995,6 +995,24 @@ const SignalCards = (function() {
|
||||
let html = '';
|
||||
const rawMessage = msg.rawMessage || {};
|
||||
|
||||
// Add device intelligence info at the top
|
||||
if (msg.utility && msg.utility !== 'Unknown') {
|
||||
html += `
|
||||
<div class="signal-advanced-item">
|
||||
<span class="signal-advanced-label">Utility Type</span>
|
||||
<span class="signal-advanced-value">${escapeHtml(msg.utility)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (msg.manufacturer && msg.manufacturer !== 'Unknown') {
|
||||
html += `
|
||||
<div class="signal-advanced-item">
|
||||
<span class="signal-advanced-label">Manufacturer</span>
|
||||
<span class="signal-advanced-value">${escapeHtml(msg.manufacturer)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Display all fields from the raw rtlamr message
|
||||
for (const [key, value] of Object.entries(rawMessage)) {
|
||||
if (value === null || value === undefined) continue;
|
||||
@@ -1066,19 +1084,24 @@ const SignalCards = (function() {
|
||||
const stats = getAddressStats('meter', msg.id);
|
||||
const seenCount = stats ? stats.count : 1;
|
||||
|
||||
// Determine meter type color
|
||||
// Determine meter type color based on utility type
|
||||
let meterTypeClass = 'electric';
|
||||
const utility = (msg.utility || '').toLowerCase();
|
||||
const meterType = (msg.type || '').toLowerCase();
|
||||
if (meterType.includes('gas')) {
|
||||
if (utility === 'gas' || meterType.includes('gas')) {
|
||||
meterTypeClass = 'gas';
|
||||
} else if (meterType.includes('water')) {
|
||||
} else if (utility === 'water' || meterType.includes('water') || meterType.includes('r900')) {
|
||||
meterTypeClass = 'water';
|
||||
}
|
||||
|
||||
// Format utility display
|
||||
const utilityDisplay = msg.utility && msg.utility !== 'Unknown' ? msg.utility : null;
|
||||
const manufacturerDisplay = msg.manufacturer && msg.manufacturer !== 'Unknown' ? msg.manufacturer : null;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="signal-card-header">
|
||||
<div class="signal-card-badges">
|
||||
<span class="signal-proto-badge meter ${meterTypeClass}">${escapeHtml(msg.type || 'Meter')}</span>
|
||||
<span class="signal-proto-badge meter ${meterTypeClass}">${escapeHtml(utilityDisplay || msg.type || 'Meter')}</span>
|
||||
<span class="signal-freq-badge">ID: ${escapeHtml(msg.id || 'N/A')}</span>
|
||||
</div>
|
||||
${status !== 'baseline' ? `
|
||||
@@ -1090,7 +1113,8 @@ const SignalCards = (function() {
|
||||
</div>
|
||||
<div class="signal-card-body">
|
||||
<div class="signal-meta-row">
|
||||
${msg.endpoint_type ? `<span class="signal-msg-type">${escapeHtml(msg.endpoint_type)}</span>` : ''}
|
||||
${manufacturerDisplay ? `<span class="signal-msg-type">${escapeHtml(manufacturerDisplay)}</span>` : ''}
|
||||
${msg.type ? `<span class="signal-msg-type" style="opacity: 0.7">${escapeHtml(msg.type)}</span>` : ''}
|
||||
${seenCount > 1 ? `<span class="signal-seen-count">×${seenCount}</span>` : ''}
|
||||
<span class="signal-timestamp" data-timestamp="${escapeHtml(msg.timestamp)}">${escapeHtml(relativeTime)}</span>
|
||||
</div>
|
||||
|
||||
@@ -3091,6 +3091,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Get meter type info for display
|
||||
const meterInfo = typeof getMeterTypeInfo === 'function'
|
||||
? getMeterTypeInfo(msgData.EndpointType, data.Type)
|
||||
: { utility: 'Unknown', manufacturer: 'Unknown' };
|
||||
|
||||
// Convert rtlamr data to our card format, preserving all raw fields
|
||||
const msg = {
|
||||
id: String(meterId),
|
||||
@@ -3099,6 +3104,8 @@
|
||||
unit: 'units',
|
||||
endpoint_type: msgData.EndpointType,
|
||||
endpoint_id: msgData.EndpointID,
|
||||
utility: meterInfo.utility,
|
||||
manufacturer: meterInfo.manufacturer,
|
||||
timestamp: new Date().toISOString(),
|
||||
rawMessage: msgData // Include all original fields for detailed display
|
||||
};
|
||||
@@ -4106,6 +4113,9 @@
|
||||
return 'WIFI_CLIENT_' + (data.address || 'UNK').replace(/:/g, '');
|
||||
} else if (data.protocol === 'Bluetooth' || data.protocol === 'BLE') {
|
||||
return 'BT_' + (data.address || 'UNK').replace(/:/g, '');
|
||||
} else if (data.protocol === 'Meter') {
|
||||
// Utility meter (rtlamr)
|
||||
return 'METER_' + (data.meterId || data.address || 'UNK');
|
||||
} else if (data.model) {
|
||||
// 433MHz sensor
|
||||
const id = data.id || data.channel || data.unit || '0';
|
||||
@@ -4298,6 +4308,94 @@
|
||||
trackDevice(data);
|
||||
};
|
||||
|
||||
// Hook rtlamr readings into device intelligence
|
||||
const originalAddRtlamrReading = addRtlamrReading;
|
||||
addRtlamrReading = function (data) {
|
||||
originalAddRtlamrReading(data);
|
||||
// Transform rtlamr data for device tracking
|
||||
const msgData = data.Message || {};
|
||||
const meterInfo = getMeterTypeInfo(msgData.EndpointType, data.Type);
|
||||
trackDevice({
|
||||
protocol: 'Meter',
|
||||
meterId: String(msgData.ID || 'Unknown'),
|
||||
address: String(msgData.ID || 'Unknown'),
|
||||
message: `${meterInfo.utility} - ${(msgData.Consumption || 0).toLocaleString()} units`,
|
||||
model: meterInfo.manufacturer || data.Type || 'Unknown',
|
||||
meterType: data.Type,
|
||||
endpointType: msgData.EndpointType,
|
||||
utility: meterInfo.utility,
|
||||
manufacturer: meterInfo.manufacturer,
|
||||
consumption: msgData.Consumption
|
||||
});
|
||||
};
|
||||
|
||||
// Meter type/manufacturer lookup based on ERT endpoint types and message formats
|
||||
function getMeterTypeInfo(endpointType, msgType) {
|
||||
// Common ERT endpoint type mappings (varies by utility)
|
||||
const endpointInfo = {
|
||||
// Electric meter types (0-7 common)
|
||||
0: { utility: 'Electric', manufacturer: 'Generic' },
|
||||
1: { utility: 'Electric', manufacturer: 'Generic' },
|
||||
2: { utility: 'Electric', manufacturer: 'Itron' },
|
||||
3: { utility: 'Electric', manufacturer: 'Itron' },
|
||||
4: { utility: 'Electric', manufacturer: 'Landis+Gyr' },
|
||||
5: { utility: 'Electric', manufacturer: 'Landis+Gyr' },
|
||||
6: { utility: 'Electric', manufacturer: 'Elster' },
|
||||
7: { utility: 'Electric', manufacturer: 'Elster' },
|
||||
// Gas meter types (8-15)
|
||||
8: { utility: 'Gas', manufacturer: 'Itron' },
|
||||
9: { utility: 'Gas', manufacturer: 'Itron' },
|
||||
10: { utility: 'Gas', manufacturer: 'Sensus' },
|
||||
11: { utility: 'Gas', manufacturer: 'Sensus' },
|
||||
12: { utility: 'Gas', manufacturer: 'Badger' },
|
||||
13: { utility: 'Gas', manufacturer: 'Neptune' },
|
||||
// Water meter types (16-23)
|
||||
16: { utility: 'Water', manufacturer: 'Badger' },
|
||||
17: { utility: 'Water', manufacturer: 'Badger' },
|
||||
18: { utility: 'Water', manufacturer: 'Neptune' },
|
||||
19: { utility: 'Water', manufacturer: 'Neptune' },
|
||||
20: { utility: 'Water', manufacturer: 'Sensus' },
|
||||
21: { utility: 'Water', manufacturer: 'Sensus' },
|
||||
22: { utility: 'Water', manufacturer: 'Master Meter' },
|
||||
23: { utility: 'Water', manufacturer: 'Mueller' },
|
||||
// Extended types
|
||||
156: { utility: 'Electric', manufacturer: 'Itron OpenWay' },
|
||||
157: { utility: 'Electric', manufacturer: 'Itron OpenWay' },
|
||||
180: { utility: 'Gas', manufacturer: 'Itron ERT' },
|
||||
188: { utility: 'Water', manufacturer: 'Badger ORION' },
|
||||
220: { utility: 'Electric', manufacturer: 'Landis+Gyr Focus' }
|
||||
};
|
||||
|
||||
// Message type hints
|
||||
const msgTypeInfo = {
|
||||
'SCM': { utility: 'Electric', manufacturer: 'Standard ERT' },
|
||||
'SCM+': { utility: 'Electric', manufacturer: 'Enhanced ERT' },
|
||||
'IDM': { utility: 'Electric', manufacturer: 'Interval Data' },
|
||||
'NetIDM': { utility: 'Electric', manufacturer: 'Network IDM' },
|
||||
'R900': { utility: 'Water', manufacturer: 'Neptune R900' },
|
||||
'R900BCD': { utility: 'Water', manufacturer: 'Neptune R900' }
|
||||
};
|
||||
|
||||
// Try endpoint type first
|
||||
if (endpointType !== undefined && endpointInfo[endpointType]) {
|
||||
return endpointInfo[endpointType];
|
||||
}
|
||||
|
||||
// Fall back to message type
|
||||
if (msgType && msgTypeInfo[msgType]) {
|
||||
return msgTypeInfo[msgType];
|
||||
}
|
||||
|
||||
// Default based on endpoint range
|
||||
if (endpointType !== undefined) {
|
||||
if (endpointType < 8) return { utility: 'Electric', manufacturer: 'Unknown' };
|
||||
if (endpointType < 16) return { utility: 'Gas', manufacturer: 'Unknown' };
|
||||
if (endpointType < 24) return { utility: 'Water', manufacturer: 'Unknown' };
|
||||
}
|
||||
|
||||
return { utility: 'Unknown', manufacturer: 'Unknown' };
|
||||
}
|
||||
|
||||
// Export device database
|
||||
function exportDeviceDB() {
|
||||
const data = [];
|
||||
|
||||
Reference in New Issue
Block a user