-
-
-
- | Protocol |
- Device Name |
- Address |
- Capabilities |
- Discovered |
- Actions |
-
-
-
- `;
-
- endpoints.forEach(endpoint => {
- const protocolBadge = this.getProtocolBadge(endpoint.protocol_type);
- const capabilities = endpoint.capabilities ? endpoint.capabilities.join(', ') : 'N/A';
- const discoveredTime = endpoint.discovered_at ?
- new Date(endpoint.discovered_at).toLocaleString() : 'N/A';
-
- html += `
-
- | ${protocolBadge} |
- ${endpoint.device_name || 'Unknown Device'} |
- ${endpoint.address}${endpoint.port ? ':' + endpoint.port : ''} |
- ${capabilities} |
- ${discoveredTime} |
-
-
- |
-
- `;
- });
-
- html += `
-
-
+
+ resultsContainer.innerHTML = '
Discovery Results
';
+
+ suggestedSignals.forEach((signal, index) => {
+ const signalCard = document.createElement('div');
+ signalCard.className = 'discovery-result-card';
+ signalCard.innerHTML = `
+
+
${signal.signal_name}
+
+ ${signal.tags.map(tag => `${tag}`).join('')}
-
-
+
+ Protocol: ${signal.protocol_type}
+ Address: ${signal.protocol_address}
-
- `;
-
- resultsContainer.innerHTML = html;
-
- // Re-bind apply button
- document.getElementById('apply-discovery-results')?.addEventListener('click', () => {
- this.applyDiscoveryResults();
+
+ `;
+
+ resultsContainer.appendChild(signalCard);
+ });
+
+ // Add event listeners for use buttons
+ resultsContainer.addEventListener('click', (e) => {
+ if (e.target.classList.contains('use-signal-btn')) {
+ const signalIndex = parseInt(e.target.dataset.signalIndex);
+ const signal = suggestedSignals[signalIndex];
+ this.autoPopulateSignalForm(signal);
+ }
});
}
- /**
- * Apply discovery results as protocol mappings
- */
- async applyDiscoveryResults() {
- if (!this.currentScanId) {
- this.showNotification('No discovery results to apply', 'warning');
- return;
- }
-
- // Get station, equipment, and data type from our metadata
- const stationId = this.getDefaultStationId();
- const equipmentId = this.getDefaultEquipmentId(stationId);
- const dataTypeId = this.getDefaultDataTypeId();
- const dbSource = 'influxdb'; // Default database source
-
+ // Tag-based signal search
+ async searchSignalsByTags(tags) {
try {
- const response = await fetch(`/api/v1/dashboard/discovery/apply/${this.currentScanId}?station_id=${stationId}&equipment_id=${equipmentId}&data_type_id=${dataTypeId}&db_source=${dbSource}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- }
- });
-
- const result = await response.json();
-
- if (result.success) {
- if (result.created_mappings.length > 0) {
- this.showNotification(`Successfully created ${result.created_mappings.length} protocol mappings from discovery results`, 'success');
-
- // Refresh protocol mappings grid
- if (window.loadProtocolMappings) {
- window.loadProtocolMappings();
- }
- } else {
- this.showNotification('No protocol mappings were created. Check the discovery results for compatible endpoints.', 'warning');
- }
+ const params = new URLSearchParams();
+ tags.forEach(tag => params.append('tags', tag));
+
+ const response = await fetch(`/api/v1/dashboard/protocol-signals?${params}`);
+ const data = await response.json();
+
+ if (data.success) {
+ return data.signals;
} else {
- throw new Error(result.detail || 'Failed to apply discovery results');
+ console.error('Failed to search signals by tags:', data.detail);
+ return [];
}
} catch (error) {
- console.error('Error applying discovery results:', error);
- this.showNotification(`Failed to apply discovery results: ${error.message}`, 'error');
+ console.error('Error searching signals by tags:', error);
+ return [];
}
}
- /**
- * Use discovered endpoint in protocol form
- */
- async useDiscoveredEndpoint(endpointId) {
- try {
- // Get current scan results to find the endpoint
- if (!this.currentScanId) {
- this.showNotification('No discovery results available', 'warning');
- return;
- }
-
- const response = await fetch(`/api/v1/dashboard/discovery/results/${this.currentScanId}`);
- const result = await response.json();
-
- if (!result.success) {
- throw new Error('Failed to fetch discovery results');
- }
-
- // Find the specific endpoint
- const endpoint = result.discovered_endpoints.find(ep => ep.device_id === endpointId);
- if (!endpoint) {
- this.showNotification(`Endpoint ${endpointId} not found in current scan`, 'warning');
- return;
- }
-
- // Populate protocol mapping form with endpoint data
- this.populateProtocolForm(endpoint);
-
- // Switch to protocol mapping tab
- this.switchToProtocolMappingTab();
-
- this.showNotification(`Endpoint ${endpoint.device_name || endpointId} selected for protocol mapping`, 'success');
-
- } catch (error) {
- console.error('Error using discovered endpoint:', error);
- this.showNotification(`Failed to use endpoint: ${error.message}`, 'error');
- }
- }
-
- /**
- * Populate protocol mapping form with endpoint data
- */
- populateProtocolForm(endpoint) {
- // Create a new protocol mapping ID
- const mappingId = `${endpoint.device_id}_${endpoint.protocol_type}`;
+ // Signal name suggestions based on device type
+ generateSignalNameSuggestions(deviceName, dataPoint) {
+ const baseName = `${deviceName} ${dataPoint}`;
- // Get default metadata IDs from our sample metadata
- const defaultStationId = this.getDefaultStationId();
- const defaultEquipmentId = this.getDefaultEquipmentId(defaultStationId);
- const defaultDataTypeId = this.getDefaultDataTypeId();
+ const suggestions = [
+ baseName,
+ `${dataPoint} of ${deviceName}`,
+ `${deviceName} ${dataPoint} Reading`,
+ `${dataPoint} Measurement - ${deviceName}`
+ ];
- // Set form values (these would be used when creating a new mapping)
- const formData = {
- mapping_id: mappingId,
- protocol_type: endpoint.protocol_type === 'opc_ua' ? 'opcua' : endpoint.protocol_type,
- protocol_address: this.getDefaultProtocolAddress(endpoint),
- device_name: endpoint.device_name || endpoint.device_id,
- device_address: endpoint.address,
- device_port: endpoint.port || '',
- station_id: defaultStationId,
- equipment_id: defaultEquipmentId,
- data_type_id: defaultDataTypeId
- };
-
- // Store form data for later use
- this.selectedEndpoint = formData;
-
- // Show form data in console for debugging
- console.log('Protocol form populated with:', formData);
-
- // Auto-populate the protocol mapping form
- this.autoPopulateProtocolForm(formData);
- }
-
- /**
- * Auto-populate the protocol mapping form with endpoint data
- */
- autoPopulateProtocolForm(formData) {
- console.log('Auto-populating protocol form with:', formData);
-
- // First, open the "Add New Mapping" modal
- this.openAddMappingModal();
-
- // Wait for modal to be fully loaded and visible
- const waitForModal = setInterval(() => {
- const modal = document.getElementById('mapping-modal');
- const isModalVisible = modal && modal.style.display !== 'none';
-
- if (isModalVisible) {
- clearInterval(waitForModal);
- this.populateModalFields(formData);
- }
- }, 50);
-
- // Timeout after 2 seconds
- setTimeout(() => {
- clearInterval(waitForModal);
- const modal = document.getElementById('mapping-modal');
- if (modal && modal.style.display !== 'none') {
- this.populateModalFields(formData);
- } else {
- console.error('Modal did not open within timeout period');
- this.showNotification('Could not open protocol mapping form. Please try opening it manually.', 'error');
- }
- }, 2000);
- }
-
- /**
- * Populate modal fields with discovery data
- */
- populateModalFields(formData) {
- console.log('Populating modal fields with:', formData);
-
- // Find and populate form fields in the modal
- const mappingIdField = document.getElementById('mapping_id');
- const protocolTypeField = document.getElementById('protocol_type');
- const protocolAddressField = document.getElementById('protocol_address');
- const stationIdField = document.getElementById('station_id');
- const equipmentIdField = document.getElementById('equipment_id');
- const dataTypeIdField = document.getElementById('data_type_id');
- const dbSourceField = document.getElementById('db_source');
-
- console.log('Found fields:', {
- mappingIdField: !!mappingIdField,
- protocolTypeField: !!protocolTypeField,
- protocolAddressField: !!protocolAddressField,
- stationIdField: !!stationIdField,
- equipmentIdField: !!equipmentIdField,
- dataTypeIdField: !!dataTypeIdField,
- dbSourceField: !!dbSourceField
- });
-
- // Populate mapping ID
- if (mappingIdField) {
- mappingIdField.value = formData.mapping_id;
- console.log('Set mapping_id to:', formData.mapping_id);
+ // Add context-specific suggestions
+ if (dataPoint.toLowerCase().includes('speed')) {
+ suggestions.push(`${deviceName} Motor Speed`);
+ suggestions.push(`${deviceName} RPM`);
}
- // Populate protocol type
- if (protocolTypeField) {
- protocolTypeField.value = formData.protocol_type;
- console.log('Set protocol_type to:', formData.protocol_type);
- // Trigger protocol field updates
- protocolTypeField.dispatchEvent(new Event('change'));
+ if (dataPoint.toLowerCase().includes('temperature')) {
+ suggestions.push(`${deviceName} Temperature`);
+ suggestions.push(`Temperature at ${deviceName}`);
}
- // Populate protocol address
- if (protocolAddressField) {
- protocolAddressField.value = formData.protocol_address;
- console.log('Set protocol_address to:', formData.protocol_address);
+ if (dataPoint.toLowerCase().includes('pressure')) {
+ suggestions.push(`${deviceName} Pressure`);
+ suggestions.push(`Pressure Reading - ${deviceName}`);
}
- // Set station, equipment, and data type if they exist in our metadata
- if (stationIdField) {
- // Wait for stations to be loaded if needed
- this.waitForStationsLoaded(() => {
- if (this.isValidStationId(formData.station_id)) {
- stationIdField.value = formData.station_id;
- console.log('Set station_id to:', formData.station_id);
- // Trigger equipment dropdown update
- stationIdField.dispatchEvent(new Event('change'));
-
- // Wait for equipment to be loaded
- setTimeout(() => {
- if (equipmentIdField && this.isValidEquipmentId(formData.equipment_id)) {
- equipmentIdField.value = formData.equipment_id;
- console.log('Set equipment_id to:', formData.equipment_id);
- }
-
- if (dataTypeIdField && this.isValidDataTypeId(formData.data_type_id)) {
- dataTypeIdField.value = formData.data_type_id;
- console.log('Set data_type_id to:', formData.data_type_id);
- }
-
- // Set default database source
- if (dbSourceField && !dbSourceField.value) {
- dbSourceField.value = 'measurements.' + formData.device_name.toLowerCase().replace(/[^a-z0-9]/g, '_');
- }
-
- // Show success message
- this.showNotification(`Protocol form populated with ${formData.device_name}. Please review and complete any missing information.`, 'success');
- }, 100);
- }
- });
- }
- }
-
- /**
- * Open the "Add New Mapping" modal
- */
- openAddMappingModal() {
- console.log('Attempting to open Add New Mapping modal...');
-
- // First try to use the global function
- if (typeof showAddMappingModal === 'function') {
- console.log('Using showAddMappingModal function');
- showAddMappingModal();
- return;
- }
-
- // Try to find and click the "Add New Mapping" button
- const addButton = document.querySelector('button[onclick*="showAddMappingModal"]');
- if (addButton) {
- console.log('Found Add New Mapping button, clicking it');
- addButton.click();
- return;
- }
-
- // Try to find any button that might open the modal
- const buttons = document.querySelectorAll('button');
- for (let button of buttons) {
- const text = button.textContent.toLowerCase();
- if (text.includes('add') && text.includes('mapping')) {
- console.log('Found Add Mapping button by text, clicking it');
- button.click();
- return;
- }
- }
-
- // Last resort: try to show the modal directly
- const modal = document.getElementById('mapping-modal');
- if (modal) {
- console.log('Found mapping-modal, showing it directly');
- modal.style.display = 'block';
- return;
- }
-
- console.error('Could not find any way to open the protocol mapping modal');
- // Fallback: show a message to manually open the modal
- this.showNotification('Please click "Add New Mapping" to create a protocol mapping with the discovered endpoint data.', 'info');
- }
-
- /**
- * Get default protocol address based on endpoint type
- */
- getDefaultProtocolAddress(endpoint) {
- const protocolType = endpoint.protocol_type;
- const deviceName = endpoint.device_name || endpoint.device_id;
-
- switch (protocolType) {
- case 'modbus_tcp':
- return '40001'; // Default holding register
- case 'opc_ua':
- return `ns=2;s=${deviceName.replace(/\s+/g, '_')}`;
- case 'rest_api':
- return `http://${endpoint.address}${endpoint.port ? ':' + endpoint.port : ''}/api/data`;
- default:
- return endpoint.address;
- }
- }
-
- /**
- * Switch to protocol mapping tab
- */
- switchToProtocolMappingTab() {
- // Find and click the protocol mapping tab
- const mappingTab = document.querySelector('[data-tab="protocol-mapping"]');
- if (mappingTab) {
- mappingTab.click();
- } else {
- // Fallback: scroll to protocol mapping section
- const mappingSection = document.querySelector('#protocol-mapping-section');
- if (mappingSection) {
- mappingSection.scrollIntoView({ behavior: 'smooth' });
- }
- }
-
- // Show guidance message
- this.showNotification('Please complete the protocol mapping form with station, pump, and data type information', 'info');
+ return suggestions;
}
- /**
- * Set scanning state
- */
- setScanningState(scanning) {
- this.isScanning = scanning;
- const scanButton = document.getElementById('start-discovery-scan');
- const stopButton = document.getElementById('stop-discovery-scan');
-
- if (scanning) {
- scanButton?.setAttribute('disabled', 'true');
- stopButton?.removeAttribute('disabled');
- } else {
- scanButton?.removeAttribute('disabled');
- stopButton?.setAttribute('disabled', 'true');
+ // Tag suggestions based on device and protocol
+ generateTagSuggestions(deviceName, protocolType, dataPoint) {
+ const suggestions = new Set();
+
+ // Device type tags
+ if (deviceName.toLowerCase().includes('pump')) {
+ suggestions.add('equipment:pump');
+ suggestions.add('fluid:water');
}
+ if (deviceName.toLowerCase().includes('sensor')) {
+ suggestions.add('equipment:sensor');
+ suggestions.add('type:measurement');
+ }
+ if (deviceName.toLowerCase().includes('controller')) {
+ suggestions.add('equipment:controller');
+ suggestions.add('type:control');
+ }
+
+ // Protocol tags
+ suggestions.add(`protocol:${protocolType}`);
+ if (protocolType === 'modbus_tcp' || protocolType === 'modbus_rtu') {
+ suggestions.add('interface:modbus');
+ } else if (protocolType === 'opcua') {
+ suggestions.add('interface:opcua');
+ }
+
+ // Data point tags
+ suggestions.add(`data_point:${dataPoint.toLowerCase().replace(/[^a-z0-9]/g, '_')}`);
+
+ if (dataPoint.toLowerCase().includes('speed')) {
+ suggestions.add('unit:rpm');
+ suggestions.add('type:setpoint');
+ }
+ if (dataPoint.toLowerCase().includes('temperature')) {
+ suggestions.add('unit:celsius');
+ suggestions.add('type:measurement');
+ }
+ if (dataPoint.toLowerCase().includes('pressure')) {
+ suggestions.add('unit:psi');
+ suggestions.add('type:measurement');
+ }
+ if (dataPoint.toLowerCase().includes('status')) {
+ suggestions.add('type:status');
+ suggestions.add('format:boolean');
+ }
+
+ // Discovery tag
+ suggestions.add('discovered:true');
+
+ return Array.from(suggestions);
}
- /**
- * Get protocol badge HTML
- */
- getProtocolBadge(protocolType) {
- const badges = {
- 'modbus_tcp': '
Modbus TCP',
- 'modbus_rtu': '
Modbus RTU',
- 'opc_ua': '
OPC UA',
- 'rest_api': '
REST API'
- };
-
- return badges[protocolType] || `
${protocolType}`;
- }
-
- /**
- * Show notification
- */
showNotification(message, type = 'info') {
- // Use existing notification system or create simple alert
- const alertClass = {
- 'success': 'alert-success',
- 'error': 'alert-danger',
- 'warning': 'alert-warning',
- 'info': 'alert-info'
- }[type] || 'alert-info';
-
const notification = document.createElement('div');
- notification.className = `alert ${alertClass} alert-dismissible fade show`;
- notification.innerHTML = `
- ${message}
-
- `;
-
- const container = document.getElementById('discovery-notifications') || document.body;
- container.appendChild(notification);
-
+ notification.className = `discovery-notification ${type}`;
+ notification.textContent = message;
+
+ document.body.appendChild(notification);
+
// Auto-remove after 5 seconds
setTimeout(() => {
if (notification.parentNode) {
@@ -651,112 +341,12 @@ class ProtocolDiscovery {
}
}, 5000);
}
-
- /**
- * Get default station ID from available metadata
- */
- getDefaultStationId() {
- // Try to get the first station from our metadata
- const stationSelect = document.getElementById('station_id');
- if (stationSelect && stationSelect.options.length > 1) {
- return stationSelect.options[1].value; // First actual station (skip "Select Station")
- }
- // Fallback to our sample metadata IDs
- return 'station_main';
- }
-
- /**
- * Get default equipment ID for a station
- */
- getDefaultEquipmentId(stationId) {
- // Try to get the first equipment for the station
- const equipmentSelect = document.getElementById('equipment_id');
- if (equipmentSelect && equipmentSelect.options.length > 1) {
- return equipmentSelect.options[1].value; // First actual equipment
- }
- // Fallback based on station
- if (stationId === 'station_main') return 'pump_primary';
- if (stationId === 'station_backup') return 'pump_backup';
- if (stationId === 'station_control') return 'controller_plc';
- return 'pump_primary';
- }
-
- /**
- * Get default data type ID
- */
- getDefaultDataTypeId() {
- // Try to get the first data type from our metadata
- const dataTypeSelect = document.getElementById('data_type_id');
- if (dataTypeSelect && dataTypeSelect.options.length > 1) {
- return dataTypeSelect.options[1].value; // First actual data type
- }
- // Fallback to our sample metadata IDs
- return 'speed_pump';
- }
-
- /**
- * Check if station ID exists in our metadata
- */
- isValidStationId(stationId) {
- const stationSelect = document.getElementById('station_id');
- if (!stationSelect) return false;
- return Array.from(stationSelect.options).some(option => option.value === stationId);
- }
-
- /**
- * Check if equipment ID exists in our metadata
- */
- isValidEquipmentId(equipmentId) {
- const equipmentSelect = document.getElementById('equipment_id');
- if (!equipmentSelect) return false;
- return Array.from(equipmentSelect.options).some(option => option.value === equipmentId);
- }
-
- /**
- * Check if data type ID exists in our metadata
- */
- isValidDataTypeId(dataTypeId) {
- const dataTypeSelect = document.getElementById('data_type_id');
- if (!dataTypeSelect) return false;
- return Array.from(dataTypeSelect.options).some(option => option.value === dataTypeId);
- }
-
- /**
- * Wait for stations to be loaded in the dropdown
- */
- waitForStationsLoaded(callback, maxWait = 3000) {
- const stationSelect = document.getElementById('station_id');
- if (!stationSelect) {
- console.error('Station select element not found');
- callback();
- return;
- }
-
- // Check if stations are already loaded (more than just "Select Station")
- if (stationSelect.options.length > 1) {
- console.log('Stations already loaded:', stationSelect.options.length);
- callback();
- return;
- }
-
- // Wait for stations to load
- const startTime = Date.now();
- const checkInterval = setInterval(() => {
- if (stationSelect.options.length > 1) {
- console.log('Stations loaded after wait:', stationSelect.options.length);
- clearInterval(checkInterval);
- callback();
- } else if (Date.now() - startTime > maxWait) {
- console.warn('Timeout waiting for stations to load');
- clearInterval(checkInterval);
- callback();
- }
- }, 100);
- }
}
-// Initialize discovery when DOM is loaded
-document.addEventListener('DOMContentLoaded', () => {
- window.protocolDiscovery = new ProtocolDiscovery();
- window.protocolDiscovery.init();
+// Global instance
+const simplifiedDiscovery = new SimplifiedProtocolDiscovery();
+
+// Initialize when DOM is loaded
+document.addEventListener('DOMContentLoaded', function() {
+ simplifiedDiscovery.init();
});
\ No newline at end of file
diff --git a/static/protocol_mapping.js b/static/protocol_mapping.js
index bae8ee0..3016db6 100644
--- a/static/protocol_mapping.js
+++ b/static/protocol_mapping.js
@@ -1,229 +1,157 @@
-// Protocol Mapping Functions
+// Simplified Protocol Mapping Functions
+// Uses human-readable signal names and tags instead of complex IDs
+
let currentProtocolFilter = 'all';
-let editingMappingId = null;
-let tagMetadata = {
- stations: [],
- equipment: [],
- dataTypes: []
-};
+let editingSignalId = null;
+let allTags = new Set();
-// Tag Metadata Functions
-async function loadTagMetadata() {
+// Simplified Signal Management Functions
+async function loadAllSignals() {
try {
- // Load stations
- const stationsResponse = await fetch('/api/v1/dashboard/metadata/stations');
- const stationsData = await stationsResponse.json();
- if (stationsData.success) {
- tagMetadata.stations = stationsData.stations;
- populateStationDropdown();
- }
-
- // Load data types
- const dataTypesResponse = await fetch('/api/v1/dashboard/metadata/data-types');
- const dataTypesData = await dataTypesResponse.json();
- if (dataTypesData.success) {
- tagMetadata.dataTypes = dataTypesData.data_types;
- populateDataTypeDropdown();
- }
-
- // Load equipment for all stations
- const equipmentResponse = await fetch('/api/v1/dashboard/metadata/equipment');
- const equipmentData = await equipmentResponse.json();
- if (equipmentData.success) {
- tagMetadata.equipment = equipmentData.equipment;
- }
-
- } catch (error) {
- console.error('Error loading tag metadata:', error);
- }
-}
-
-function populateStationDropdown() {
- const stationSelect = document.getElementById('station_id');
- stationSelect.innerHTML = '
';
-
- tagMetadata.stations.forEach(station => {
- const option = document.createElement('option');
- option.value = station.id;
- option.textContent = `${station.name} (${station.id})`;
- stationSelect.appendChild(option);
- });
-}
-
-function populateEquipmentDropdown(stationId = null) {
- const equipmentSelect = document.getElementById('equipment_id');
- equipmentSelect.innerHTML = '
';
-
- let filteredEquipment = tagMetadata.equipment;
- if (stationId) {
- filteredEquipment = tagMetadata.equipment.filter(eq => eq.station_id === stationId);
- }
-
- filteredEquipment.forEach(equipment => {
- const option = document.createElement('option');
- option.value = equipment.id;
- option.textContent = `${equipment.name} (${equipment.id})`;
- equipmentSelect.appendChild(option);
- });
-}
-
-function populateDataTypeDropdown() {
- const dataTypeSelect = document.getElementById('data_type_id');
- dataTypeSelect.innerHTML = '
';
-
- tagMetadata.dataTypes.forEach(dataType => {
- const option = document.createElement('option');
- option.value = dataType.id;
- option.textContent = `${dataType.name} (${dataType.id})`;
- if (dataType.units) {
- option.textContent += ` [${dataType.units}]`;
- }
- dataTypeSelect.appendChild(option);
- });
-}
-
-// Event listener for station selection change
-document.addEventListener('DOMContentLoaded', function() {
- const stationSelect = document.getElementById('station_id');
- if (stationSelect) {
- stationSelect.addEventListener('change', function() {
- const stationId = this.value;
- populateEquipmentDropdown(stationId);
- });
- }
-
- // Load tag metadata when page loads
- loadTagMetadata();
-});
-
-function selectProtocol(protocol) {
- currentProtocolFilter = protocol;
-
- // Update active button
- document.querySelectorAll('.protocol-btn').forEach(btn => {
- btn.classList.remove('active');
- });
- event.target.classList.add('active');
-
- // Reload mappings with filter
- loadProtocolMappings();
-}
-
-async function loadProtocolMappings() {
- try {
- // Ensure tag metadata is loaded first
- if (tagMetadata.stations.length === 0 || tagMetadata.dataTypes.length === 0) {
- await loadTagMetadata();
- }
-
- const params = new URLSearchParams();
- if (currentProtocolFilter !== 'all') {
- params.append('protocol_type', currentProtocolFilter);
- }
-
- const response = await fetch(`/api/v1/dashboard/protocol-mappings?${params}`);
+ const response = await fetch('/api/v1/dashboard/protocol-signals');
const data = await response.json();
if (data.success) {
- displayProtocolMappings(data.mappings);
+ displaySignals(data.signals);
+ updateTagCloud(data.signals);
} else {
- showProtocolMappingAlert('Failed to load protocol mappings', 'error');
+ showSimplifiedAlert('Failed to load signals', 'error');
}
} catch (error) {
- console.error('Error loading protocol mappings:', error);
- showProtocolMappingAlert('Error loading protocol mappings', 'error');
+ console.error('Error loading signals:', error);
+ showSimplifiedAlert('Error loading signals', 'error');
}
}
-function displayProtocolMappings(mappings) {
- const tbody = document.getElementById('protocol-mappings-body');
+function displaySignals(signals) {
+ const tbody = document.getElementById('protocol-signals-body');
tbody.innerHTML = '';
- if (mappings.length === 0) {
- tbody.innerHTML = '
| No protocol mappings found |
';
+ if (signals.length === 0) {
+ tbody.innerHTML = '
| No protocol signals found |
';
return;
}
- mappings.forEach(mapping => {
- // Look up human-readable names from tag metadata
- const station = tagMetadata.stations.find(s => s.id === mapping.station_id);
- const equipment = tagMetadata.equipment.find(e => e.id === mapping.equipment_id);
- const dataType = tagMetadata.dataTypes.find(dt => dt.id === mapping.data_type_id);
-
- const stationDisplay = station ? `${station.name} (${station.id})` : (mapping.station_id || '-');
- const equipmentDisplay = equipment ? `${equipment.name} (${equipment.id})` : (mapping.equipment_id || '-');
- const dataTypeDisplay = dataType ? `${dataType.name} (${dataType.id})` : (mapping.data_type_id || '-');
-
+ signals.forEach(signal => {
const row = document.createElement('tr');
row.innerHTML = `
-
${mapping.id} |
-
${mapping.protocol_type} |
-
${stationDisplay} |
-
${equipmentDisplay} |
-
${dataTypeDisplay} |
-
${mapping.protocol_address} |
-
${mapping.db_source} |
+
${signal.signal_name} |
+
${signal.protocol_type} |
-
-
+ ${signal.tags.map(tag => `${tag}`).join('')}
+ |
+
${signal.protocol_address} |
+
${signal.db_source} |
+
+
+ ${signal.enabled ? 'Enabled' : 'Disabled'}
+
+ |
+
+
+
|
`;
tbody.appendChild(row);
});
}
-function showAddMappingModal() {
- editingMappingId = null;
- document.getElementById('modal-title').textContent = 'Add Protocol Mapping';
- document.getElementById('mapping-form').reset();
- document.getElementById('protocol_address_help').textContent = '';
- document.getElementById('mapping-modal').style.display = 'block';
+function updateTagCloud(signals) {
+ const tagCloud = document.getElementById('tag-cloud');
+ if (!tagCloud) return;
+
+ // Collect all tags
+ const tagCounts = {};
+ signals.forEach(signal => {
+ signal.tags.forEach(tag => {
+ tagCounts[tag] = (tagCounts[tag] || 0) + 1;
+ });
+ });
+
+ // Create tag cloud
+ tagCloud.innerHTML = '';
+ Object.entries(tagCounts).forEach(([tag, count]) => {
+ const tagElement = document.createElement('span');
+ tagElement.className = 'tag-cloud-item';
+ tagElement.textContent = tag;
+ tagElement.title = `${count} signal(s)`;
+ tagElement.onclick = () => filterByTag(tag);
+ tagCloud.appendChild(tagElement);
+ });
}
-function showEditMappingModal(mapping) {
- editingMappingId = mapping.id;
- document.getElementById('modal-title').textContent = 'Edit Protocol Mapping';
- document.getElementById('mapping_id').value = mapping.id;
- document.getElementById('protocol_type').value = mapping.protocol_type;
-
- // Set dropdown values
- const stationSelect = document.getElementById('station_id');
- const equipmentSelect = document.getElementById('equipment_id');
- const dataTypeSelect = document.getElementById('data_type_id');
-
- stationSelect.value = mapping.station_id || '';
- if (mapping.station_id) {
- populateEquipmentDropdown(mapping.station_id);
+function filterByTag(tag) {
+ const filterInput = document.getElementById('tag-filter');
+ if (filterInput) {
+ filterInput.value = tag;
+ applyFilters();
}
- equipmentSelect.value = mapping.equipment_id || '';
- dataTypeSelect.value = mapping.data_type_id || '';
+}
+
+async function applyFilters() {
+ const tagFilter = document.getElementById('tag-filter')?.value || '';
+ const protocolFilter = document.getElementById('protocol-filter')?.value || 'all';
+ const nameFilter = document.getElementById('name-filter')?.value || '';
- document.getElementById('protocol_address').value = mapping.protocol_address;
- document.getElementById('db_source').value = mapping.db_source;
+ const params = new URLSearchParams();
+ if (tagFilter) params.append('tags', tagFilter);
+ if (protocolFilter !== 'all') params.append('protocol_type', protocolFilter);
+ if (nameFilter) params.append('signal_name_contains', nameFilter);
+
+ try {
+ const response = await fetch(`/api/v1/dashboard/protocol-signals?${params}`);
+ const data = await response.json();
+
+ if (data.success) {
+ displaySignals(data.signals);
+ }
+ } catch (error) {
+ console.error('Error applying filters:', error);
+ }
+}
+
+// Modal Functions
+function showAddSignalModal() {
+ editingSignalId = null;
+ document.getElementById('modal-title').textContent = 'Add Protocol Signal';
+ document.getElementById('signal-form').reset();
+ document.getElementById('protocol-address-help').textContent = '';
+ document.getElementById('signal-modal').style.display = 'block';
+}
+
+function showEditSignalModal(signal) {
+ editingSignalId = signal.signal_id;
+ document.getElementById('modal-title').textContent = 'Edit Protocol Signal';
+
+ // Populate form
+ document.getElementById('signal_name').value = signal.signal_name;
+ document.getElementById('tags').value = signal.tags.join(', ');
+ document.getElementById('protocol_type').value = signal.protocol_type;
+ document.getElementById('protocol_address').value = signal.protocol_address;
+ document.getElementById('db_source').value = signal.db_source;
+ document.getElementById('preprocessing_enabled').checked = signal.preprocessing_enabled || false;
updateProtocolFields();
- document.getElementById('mapping-modal').style.display = 'block';
+ document.getElementById('signal-modal').style.display = 'block';
}
-function closeMappingModal() {
- document.getElementById('mapping-modal').style.display = 'none';
- editingMappingId = null;
+function closeSignalModal() {
+ document.getElementById('signal-modal').style.display = 'none';
+ editingSignalId = null;
}
function updateProtocolFields() {
const protocolType = document.getElementById('protocol_type').value;
- const helpText = document.getElementById('protocol_address_help');
+ const helpText = document.getElementById('protocol-address-help');
switch (protocolType) {
case 'modbus_tcp':
+ case 'modbus_rtu':
helpText.textContent = 'Modbus address format: 40001 (holding register), 30001 (input register), 10001 (coil), 00001 (discrete input)';
break;
case 'opcua':
helpText.textContent = 'OPC UA NodeId format: ns=2;s=MyVariable or ns=2;i=1234';
break;
- case 'modbus_rtu':
- helpText.textContent = 'Modbus RTU address format: 40001 (holding register), 30001 (input register), 10001 (coil), 00001 (discrete input)';
- break;
case 'rest_api':
helpText.textContent = 'REST API endpoint format: /api/v1/data/endpoint';
break;
@@ -232,48 +160,22 @@ function updateProtocolFields() {
}
}
-async function validateMapping() {
- const formData = getMappingFormData();
-
- try {
- const response = await fetch(`/api/v1/dashboard/protocol-mappings/${editingMappingId || 'new'}/validate`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(formData)
- });
-
- const data = await response.json();
-
- if (data.success) {
- if (data.valid) {
- showProtocolMappingAlert('Mapping validation successful!', 'success');
- } else {
- showProtocolMappingAlert(`Validation failed: ${data.errors.join(', ')}`, 'error');
- }
- } else {
- showProtocolMappingAlert('Validation error', 'error');
- }
- } catch (error) {
- console.error('Error validating mapping:', error);
- showProtocolMappingAlert('Error validating mapping', 'error');
- }
-}
-
-async function saveMapping(event) {
+// Form Submission
+async function saveSignal(event) {
event.preventDefault();
- const formData = getMappingFormData();
+ const formData = getSignalFormData();
try {
let response;
- if (editingMappingId) {
- response = await fetch(`/api/v1/dashboard/protocol-mappings/${editingMappingId}`, {
+ if (editingSignalId) {
+ response = await fetch(`/api/v1/dashboard/protocol-signals/${editingSignalId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
} else {
- response = await fetch('/api/v1/dashboard/protocol-mappings', {
+ response = await fetch('/api/v1/dashboard/protocol-signals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
@@ -283,76 +185,151 @@ async function saveMapping(event) {
const data = await response.json();
if (data.success) {
- showProtocolMappingAlert(`Protocol mapping ${editingMappingId ? 'updated' : 'created'} successfully!`, 'success');
- closeMappingModal();
- loadProtocolMappings();
+ showSimplifiedAlert(`Protocol signal ${editingSignalId ? 'updated' : 'created'} successfully!`, 'success');
+ closeSignalModal();
+ loadAllSignals();
} else {
- showProtocolMappingAlert(`Failed to save mapping: ${data.detail || 'Unknown error'}`, 'error');
+ showSimplifiedAlert(`Failed to save signal: ${data.detail || 'Unknown error'}`, 'error');
}
} catch (error) {
- console.error('Error saving mapping:', error);
- showProtocolMappingAlert('Error saving mapping', 'error');
+ console.error('Error saving signal:', error);
+ showSimplifiedAlert('Error saving signal', 'error');
}
}
-function getMappingFormData() {
+function getSignalFormData() {
+ const tagsInput = document.getElementById('tags').value;
+ const tags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag);
+
return {
+ signal_name: document.getElementById('signal_name').value,
+ tags: tags,
protocol_type: document.getElementById('protocol_type').value,
- station_id: document.getElementById('station_id').value,
- equipment_id: document.getElementById('equipment_id').value,
- data_type_id: document.getElementById('data_type_id').value,
protocol_address: document.getElementById('protocol_address').value,
- db_source: document.getElementById('db_source').value
+ db_source: document.getElementById('db_source').value,
+ preprocessing_enabled: document.getElementById('preprocessing_enabled').checked
};
}
-async function editMapping(mappingId) {
+// Signal Management
+async function editSignal(signalId) {
try {
- const response = await fetch(`/api/v1/dashboard/protocol-mappings?protocol_type=all`);
+ const response = await fetch(`/api/v1/dashboard/protocol-signals/${signalId}`);
const data = await response.json();
if (data.success) {
- const mapping = data.mappings.find(m => m.id === mappingId);
- if (mapping) {
- showEditMappingModal(mapping);
- } else {
- showProtocolMappingAlert('Mapping not found', 'error');
- }
+ showEditSignalModal(data.signal);
} else {
- showProtocolMappingAlert('Failed to load mapping', 'error');
+ showSimplifiedAlert('Signal not found', 'error');
}
} catch (error) {
- console.error('Error loading mapping:', error);
- showProtocolMappingAlert('Error loading mapping', 'error');
+ console.error('Error loading signal:', error);
+ showSimplifiedAlert('Error loading signal', 'error');
}
}
-async function deleteMapping(mappingId) {
- if (!confirm(`Are you sure you want to delete mapping ${mappingId}?`)) {
+async function deleteSignal(signalId) {
+ if (!confirm('Are you sure you want to delete this signal?')) {
return;
}
try {
- const response = await fetch(`/api/v1/dashboard/protocol-mappings/${mappingId}`, {
+ const response = await fetch(`/api/v1/dashboard/protocol-signals/${signalId}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
- showProtocolMappingAlert('Mapping deleted successfully!', 'success');
- loadProtocolMappings();
+ showSimplifiedAlert('Signal deleted successfully!', 'success');
+ loadAllSignals();
} else {
- showProtocolMappingAlert(`Failed to delete mapping: ${data.detail || 'Unknown error'}`, 'error');
+ showSimplifiedAlert(`Failed to delete signal: ${data.detail || 'Unknown error'}`, 'error');
}
} catch (error) {
- console.error('Error deleting mapping:', error);
- showProtocolMappingAlert('Error deleting mapping', 'error');
+ console.error('Error deleting signal:', error);
+ showSimplifiedAlert('Error deleting signal', 'error');
}
}
-function showProtocolMappingAlert(message, type) {
- const alertsDiv = document.getElementById('protocol-mapping-alerts');
+// Discovery Integration
+function autoPopulateSignalForm(discoveryData) {
+ console.log('Auto-populating signal form with:', discoveryData);
+
+ // First, open the "Add New Signal" modal
+ showAddSignalModal();
+
+ // Wait for modal to be fully loaded and visible
+ const waitForModal = setInterval(() => {
+ const modal = document.getElementById('signal-modal');
+ const isModalVisible = modal && modal.style.display !== 'none';
+
+ if (isModalVisible) {
+ clearInterval(waitForModal);
+ populateModalFields(discoveryData);
+ }
+ }, 50);
+
+ // Timeout after 2 seconds
+ setTimeout(() => {
+ clearInterval(waitForModal);
+ const modal = document.getElementById('signal-modal');
+ if (modal && modal.style.display !== 'none') {
+ populateModalFields(discoveryData);
+ } else {
+ console.error('Modal did not open within timeout period');
+ showSimplifiedAlert('Could not open signal form. Please try opening it manually.', 'error');
+ }
+ }, 2000);
+}
+
+function populateModalFields(discoveryData) {
+ console.log('Populating modal fields with:', discoveryData);
+
+ // Populate signal name
+ const signalNameField = document.getElementById('signal_name');
+ if (signalNameField && discoveryData.signal_name) {
+ signalNameField.value = discoveryData.signal_name;
+ console.log('✓ Set signal_name to:', discoveryData.signal_name);
+ }
+
+ // Populate tags
+ const tagsField = document.getElementById('tags');
+ if (tagsField && discoveryData.tags) {
+ tagsField.value = discoveryData.tags.join(', ');
+ console.log('✓ Set tags to:', discoveryData.tags);
+ }
+
+ // Populate protocol type
+ const protocolTypeField = document.getElementById('protocol_type');
+ if (protocolTypeField && discoveryData.protocol_type) {
+ protocolTypeField.value = discoveryData.protocol_type;
+ console.log('✓ Set protocol_type to:', discoveryData.protocol_type);
+ // Trigger protocol field updates
+ protocolTypeField.dispatchEvent(new Event('change'));
+ }
+
+ // Populate protocol address
+ const protocolAddressField = document.getElementById('protocol_address');
+ if (protocolAddressField && discoveryData.protocol_address) {
+ protocolAddressField.value = discoveryData.protocol_address;
+ console.log('✓ Set protocol_address to:', discoveryData.protocol_address);
+ }
+
+ // Populate database source
+ const dbSourceField = document.getElementById('db_source');
+ if (dbSourceField && discoveryData.db_source) {
+ dbSourceField.value = discoveryData.db_source;
+ console.log('✓ Set db_source to:', discoveryData.db_source);
+ }
+
+ // Show success message
+ showSimplifiedAlert(`Signal form populated with discovery data. Please review and save.`, 'success');
+}
+
+// Utility Functions
+function showSimplifiedAlert(message, type = 'info') {
+ const alertsDiv = document.getElementById('simplified-alerts');
const alertDiv = document.createElement('div');
alertDiv.className = `alert ${type === 'error' ? 'error' : 'success'}`;
alertDiv.textContent = message;
@@ -360,57 +337,21 @@ function showProtocolMappingAlert(message, type) {
alertsDiv.innerHTML = '';
alertsDiv.appendChild(alertDiv);
+ // Auto-remove after 5 seconds
setTimeout(() => {
- alertDiv.remove();
+ if (alertDiv.parentNode) {
+ alertDiv.remove();
+ }
}, 5000);
}
-async function exportProtocolMappings() {
- try {
- const response = await fetch('/api/v1/dashboard/protocol-mappings?protocol_type=all');
- const data = await response.json();
-
- if (data.success) {
- const csvContent = convertToCSV(data.mappings);
- downloadCSV(csvContent, 'protocol_mappings.csv');
- } else {
- showProtocolMappingAlert('Failed to export mappings', 'error');
- }
- } catch (error) {
- console.error('Error exporting mappings:', error);
- showProtocolMappingAlert('Error exporting mappings', 'error');
- }
-}
-
-function convertToCSV(mappings) {
- const headers = ['ID', 'Protocol', 'Station', 'Pump', 'Data Type', 'Protocol Address', 'Database Source'];
- const rows = mappings.map(mapping => [
- mapping.id,
- mapping.protocol_type,
- mapping.station_id || '',
- mapping.pump_id || '',
- mapping.data_type,
- mapping.protocol_address,
- mapping.db_source
- ]);
-
- return [headers, ...rows].map(row => row.map(field => `"${field}"`).join(',')).join('\n');
-}
-
-function downloadCSV(content, filename) {
- const blob = new Blob([content], { type: 'text/csv' });
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = filename;
- a.click();
- window.URL.revokeObjectURL(url);
-}
-
-// Initialize form submission handler
+// Initialize
document.addEventListener('DOMContentLoaded', function() {
- const mappingForm = document.getElementById('mapping-form');
- if (mappingForm) {
- mappingForm.addEventListener('submit', saveMapping);
+ const signalForm = document.getElementById('signal-form');
+ if (signalForm) {
+ signalForm.addEventListener('submit', saveSignal);
}
+
+ // Load initial data
+ loadAllSignals();
});
\ No newline at end of file
diff --git a/static/simplified_discovery.js b/static/simplified_discovery.js
new file mode 100644
index 0000000..43fc20f
--- /dev/null
+++ b/static/simplified_discovery.js
@@ -0,0 +1,352 @@
+// Simplified Discovery Integration
+// Updated for simplified signal names + tags architecture
+
+class SimplifiedProtocolDiscovery {
+ constructor() {
+ this.currentScanId = 'simplified-scan-123';
+ this.isScanning = false;
+ }
+
+ init() {
+ this.bindDiscoveryEvents();
+ }
+
+ bindDiscoveryEvents() {
+ // Auto-fill signal form from discovery
+ document.addEventListener('click', (e) => {
+ if (e.target.classList.contains('use-discovered-endpoint')) {
+ this.useDiscoveredEndpoint(e.target.dataset.endpointId);
+ }
+ });
+ }
+
+ async useDiscoveredEndpoint(endpointId) {
+ console.log('Using discovered endpoint:', endpointId);
+
+ // Mock endpoint data (in real implementation, this would come from discovery service)
+ const endpoints = {
+ 'device_001': {
+ device_id: 'device_001',
+ protocol_type: 'modbus_tcp',
+ device_name: 'Water Pump Controller',
+ address: '192.168.1.100',
+ port: 502,
+ data_point: 'Speed',
+ protocol_address: '40001'
+ },
+ 'device_002': {
+ device_id: 'device_002',
+ protocol_type: 'opcua',
+ device_name: 'Temperature Sensor',
+ address: '192.168.1.101',
+ port: 4840,
+ data_point: 'Temperature',
+ protocol_address: 'ns=2;s=Temperature'
+ },
+ 'device_003': {
+ device_id: 'device_003',
+ protocol_type: 'modbus_tcp',
+ device_name: 'Pressure Transmitter',
+ address: '192.168.1.102',
+ port: 502,
+ data_point: 'Pressure',
+ protocol_address: '30001'
+ }
+ };
+
+ const endpoint = endpoints[endpointId];
+ if (!endpoint) {
+ this.showNotification(`Endpoint ${endpointId} not found`, 'error');
+ return;
+ }
+
+ // Convert to simplified signal format
+ const signalData = this.convertEndpointToSignal(endpoint);
+
+ // Auto-populate the signal form
+ this.autoPopulateSignalForm(signalData);
+
+ this.showNotification(`Endpoint ${endpoint.device_name} selected for signal creation`, 'success');
+ }
+
+ convertEndpointToSignal(endpoint) {
+ // Generate human-readable signal name
+ const signalName = `${endpoint.device_name} ${endpoint.data_point}`;
+
+ // Generate meaningful tags
+ const tags = [
+ `device:${endpoint.device_name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`,
+ `protocol:${endpoint.protocol_type}`,
+ `data_point:${endpoint.data_point.toLowerCase().replace(/[^a-z0-9]/g, '_')}`,
+ 'discovered:true'
+ ];
+
+ // Add device-specific tags
+ if (endpoint.device_name.toLowerCase().includes('pump')) {
+ tags.push('equipment:pump');
+ }
+ if (endpoint.device_name.toLowerCase().includes('sensor')) {
+ tags.push('equipment:sensor');
+ }
+ if (endpoint.device_name.toLowerCase().includes('controller')) {
+ tags.push('equipment:controller');
+ }
+
+ // Add protocol-specific tags
+ if (endpoint.protocol_type === 'modbus_tcp') {
+ tags.push('interface:modbus');
+ } else if (endpoint.protocol_type === 'opcua') {
+ tags.push('interface:opcua');
+ }
+
+ // Generate database source
+ const dbSource = `measurements.${endpoint.device_name.toLowerCase().replace(/[^a-z0-9]/g, '_')}_${endpoint.data_point.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
+
+ return {
+ signal_name: signalName,
+ tags: tags,
+ protocol_type: endpoint.protocol_type,
+ protocol_address: endpoint.protocol_address,
+ db_source: dbSource
+ };
+ }
+
+ autoPopulateSignalForm(signalData) {
+ console.log('Auto-populating signal form with:', signalData);
+
+ // Use the simplified protocol mapping function
+ if (typeof autoPopulateSignalForm === 'function') {
+ autoPopulateSignalForm(signalData);
+ } else {
+ console.error('Simplified protocol mapping functions not loaded');
+ this.showNotification('Protocol mapping system not available', 'error');
+ }
+ }
+
+ // Advanced discovery features
+ async discoverAndSuggestSignals(networkRange = '192.168.1.0/24') {
+ console.log(`Starting discovery scan on ${networkRange}`);
+ this.isScanning = true;
+
+ try {
+ // Mock discovery results
+ const discoveredEndpoints = await this.mockDiscoveryScan(networkRange);
+
+ // Convert to suggested signals
+ const suggestedSignals = discoveredEndpoints.map(endpoint =>
+ this.convertEndpointToSignal(endpoint)
+ );
+
+ this.displayDiscoveryResults(suggestedSignals);
+ this.isScanning = false;
+
+ return suggestedSignals;
+
+ } catch (error) {
+ console.error('Discovery scan failed:', error);
+ this.showNotification('Discovery scan failed', 'error');
+ this.isScanning = false;
+ return [];
+ }
+ }
+
+ async mockDiscoveryScan(networkRange) {
+ // Simulate network discovery delay
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ // Return mock discovered endpoints
+ return [
+ {
+ device_id: 'discovered_001',
+ protocol_type: 'modbus_tcp',
+ device_name: 'Booster Pump',
+ address: '192.168.1.110',
+ port: 502,
+ data_point: 'Flow Rate',
+ protocol_address: '30002'
+ },
+ {
+ device_id: 'discovered_002',
+ protocol_type: 'modbus_tcp',
+ device_name: 'Level Sensor',
+ address: '192.168.1.111',
+ port: 502,
+ data_point: 'Tank Level',
+ protocol_address: '30003'
+ },
+ {
+ device_id: 'discovered_003',
+ protocol_type: 'opcua',
+ device_name: 'PLC Controller',
+ address: '192.168.1.112',
+ port: 4840,
+ data_point: 'System Status',
+ protocol_address: 'ns=2;s=SystemStatus'
+ }
+ ];
+ }
+
+ displayDiscoveryResults(suggestedSignals) {
+ const resultsContainer = document.getElementById('discovery-results');
+ if (!resultsContainer) return;
+
+ resultsContainer.innerHTML = '
Discovery Results
';
+
+ suggestedSignals.forEach((signal, index) => {
+ const signalCard = document.createElement('div');
+ signalCard.className = 'discovery-result-card';
+ signalCard.innerHTML = `
+
+
${signal.signal_name}
+
+ ${signal.tags.map(tag => `${tag}`).join('')}
+
+
+ Protocol: ${signal.protocol_type}
+ Address: ${signal.protocol_address}
+
+
+
+ `;
+
+ resultsContainer.appendChild(signalCard);
+ });
+
+ // Add event listeners for use buttons
+ resultsContainer.addEventListener('click', (e) => {
+ if (e.target.classList.contains('use-signal-btn')) {
+ const signalIndex = parseInt(e.target.dataset.signalIndex);
+ const signal = suggestedSignals[signalIndex];
+ this.autoPopulateSignalForm(signal);
+ }
+ });
+ }
+
+ // Tag-based signal search
+ async searchSignalsByTags(tags) {
+ try {
+ const params = new URLSearchParams();
+ tags.forEach(tag => params.append('tags', tag));
+
+ const response = await fetch(`/api/v1/dashboard/protocol-signals?${params}`);
+ const data = await response.json();
+
+ if (data.success) {
+ return data.signals;
+ } else {
+ console.error('Failed to search signals by tags:', data.detail);
+ return [];
+ }
+ } catch (error) {
+ console.error('Error searching signals by tags:', error);
+ return [];
+ }
+ }
+
+ // Signal name suggestions based on device type
+ generateSignalNameSuggestions(deviceName, dataPoint) {
+ const baseName = `${deviceName} ${dataPoint}`;
+
+ const suggestions = [
+ baseName,
+ `${dataPoint} of ${deviceName}`,
+ `${deviceName} ${dataPoint} Reading`,
+ `${dataPoint} Measurement - ${deviceName}`
+ ];
+
+ // Add context-specific suggestions
+ if (dataPoint.toLowerCase().includes('speed')) {
+ suggestions.push(`${deviceName} Motor Speed`);
+ suggestions.push(`${deviceName} RPM`);
+ }
+
+ if (dataPoint.toLowerCase().includes('temperature')) {
+ suggestions.push(`${deviceName} Temperature`);
+ suggestions.push(`Temperature at ${deviceName}`);
+ }
+
+ if (dataPoint.toLowerCase().includes('pressure')) {
+ suggestions.push(`${deviceName} Pressure`);
+ suggestions.push(`Pressure Reading - ${deviceName}`);
+ }
+
+ return suggestions;
+ }
+
+ // Tag suggestions based on device and protocol
+ generateTagSuggestions(deviceName, protocolType, dataPoint) {
+ const suggestions = new Set();
+
+ // Device type tags
+ if (deviceName.toLowerCase().includes('pump')) {
+ suggestions.add('equipment:pump');
+ suggestions.add('fluid:water');
+ }
+ if (deviceName.toLowerCase().includes('sensor')) {
+ suggestions.add('equipment:sensor');
+ suggestions.add('type:measurement');
+ }
+ if (deviceName.toLowerCase().includes('controller')) {
+ suggestions.add('equipment:controller');
+ suggestions.add('type:control');
+ }
+
+ // Protocol tags
+ suggestions.add(`protocol:${protocolType}`);
+ if (protocolType === 'modbus_tcp' || protocolType === 'modbus_rtu') {
+ suggestions.add('interface:modbus');
+ } else if (protocolType === 'opcua') {
+ suggestions.add('interface:opcua');
+ }
+
+ // Data point tags
+ suggestions.add(`data_point:${dataPoint.toLowerCase().replace(/[^a-z0-9]/g, '_')}`);
+
+ if (dataPoint.toLowerCase().includes('speed')) {
+ suggestions.add('unit:rpm');
+ suggestions.add('type:setpoint');
+ }
+ if (dataPoint.toLowerCase().includes('temperature')) {
+ suggestions.add('unit:celsius');
+ suggestions.add('type:measurement');
+ }
+ if (dataPoint.toLowerCase().includes('pressure')) {
+ suggestions.add('unit:psi');
+ suggestions.add('type:measurement');
+ }
+ if (dataPoint.toLowerCase().includes('status')) {
+ suggestions.add('type:status');
+ suggestions.add('format:boolean');
+ }
+
+ // Discovery tag
+ suggestions.add('discovered:true');
+
+ return Array.from(suggestions);
+ }
+
+ showNotification(message, type = 'info') {
+ const notification = document.createElement('div');
+ notification.className = `discovery-notification ${type}`;
+ notification.textContent = message;
+
+ document.body.appendChild(notification);
+
+ // Auto-remove after 5 seconds
+ setTimeout(() => {
+ if (notification.parentNode) {
+ notification.remove();
+ }
+ }, 5000);
+ }
+}
+
+// Global instance
+const simplifiedDiscovery = new SimplifiedProtocolDiscovery();
+
+// Initialize when DOM is loaded
+document.addEventListener('DOMContentLoaded', function() {
+ simplifiedDiscovery.init();
+});
\ No newline at end of file
diff --git a/static/simplified_protocol_mapping.js b/static/simplified_protocol_mapping.js
new file mode 100644
index 0000000..3016db6
--- /dev/null
+++ b/static/simplified_protocol_mapping.js
@@ -0,0 +1,357 @@
+// Simplified Protocol Mapping Functions
+// Uses human-readable signal names and tags instead of complex IDs
+
+let currentProtocolFilter = 'all';
+let editingSignalId = null;
+let allTags = new Set();
+
+// Simplified Signal Management Functions
+async function loadAllSignals() {
+ try {
+ const response = await fetch('/api/v1/dashboard/protocol-signals');
+ const data = await response.json();
+
+ if (data.success) {
+ displaySignals(data.signals);
+ updateTagCloud(data.signals);
+ } else {
+ showSimplifiedAlert('Failed to load signals', 'error');
+ }
+ } catch (error) {
+ console.error('Error loading signals:', error);
+ showSimplifiedAlert('Error loading signals', 'error');
+ }
+}
+
+function displaySignals(signals) {
+ const tbody = document.getElementById('protocol-signals-body');
+ tbody.innerHTML = '';
+
+ if (signals.length === 0) {
+ tbody.innerHTML = '
| No protocol signals found |
';
+ return;
+ }
+
+ signals.forEach(signal => {
+ const row = document.createElement('tr');
+ row.innerHTML = `
+
${signal.signal_name} |
+
${signal.protocol_type} |
+
+ ${signal.tags.map(tag => `${tag}`).join('')}
+ |
+
${signal.protocol_address} |
+
${signal.db_source} |
+
+
+ ${signal.enabled ? 'Enabled' : 'Disabled'}
+
+ |
+
+
+
+ |
+ `;
+ tbody.appendChild(row);
+ });
+}
+
+function updateTagCloud(signals) {
+ const tagCloud = document.getElementById('tag-cloud');
+ if (!tagCloud) return;
+
+ // Collect all tags
+ const tagCounts = {};
+ signals.forEach(signal => {
+ signal.tags.forEach(tag => {
+ tagCounts[tag] = (tagCounts[tag] || 0) + 1;
+ });
+ });
+
+ // Create tag cloud
+ tagCloud.innerHTML = '';
+ Object.entries(tagCounts).forEach(([tag, count]) => {
+ const tagElement = document.createElement('span');
+ tagElement.className = 'tag-cloud-item';
+ tagElement.textContent = tag;
+ tagElement.title = `${count} signal(s)`;
+ tagElement.onclick = () => filterByTag(tag);
+ tagCloud.appendChild(tagElement);
+ });
+}
+
+function filterByTag(tag) {
+ const filterInput = document.getElementById('tag-filter');
+ if (filterInput) {
+ filterInput.value = tag;
+ applyFilters();
+ }
+}
+
+async function applyFilters() {
+ const tagFilter = document.getElementById('tag-filter')?.value || '';
+ const protocolFilter = document.getElementById('protocol-filter')?.value || 'all';
+ const nameFilter = document.getElementById('name-filter')?.value || '';
+
+ const params = new URLSearchParams();
+ if (tagFilter) params.append('tags', tagFilter);
+ if (protocolFilter !== 'all') params.append('protocol_type', protocolFilter);
+ if (nameFilter) params.append('signal_name_contains', nameFilter);
+
+ try {
+ const response = await fetch(`/api/v1/dashboard/protocol-signals?${params}`);
+ const data = await response.json();
+
+ if (data.success) {
+ displaySignals(data.signals);
+ }
+ } catch (error) {
+ console.error('Error applying filters:', error);
+ }
+}
+
+// Modal Functions
+function showAddSignalModal() {
+ editingSignalId = null;
+ document.getElementById('modal-title').textContent = 'Add Protocol Signal';
+ document.getElementById('signal-form').reset();
+ document.getElementById('protocol-address-help').textContent = '';
+ document.getElementById('signal-modal').style.display = 'block';
+}
+
+function showEditSignalModal(signal) {
+ editingSignalId = signal.signal_id;
+ document.getElementById('modal-title').textContent = 'Edit Protocol Signal';
+
+ // Populate form
+ document.getElementById('signal_name').value = signal.signal_name;
+ document.getElementById('tags').value = signal.tags.join(', ');
+ document.getElementById('protocol_type').value = signal.protocol_type;
+ document.getElementById('protocol_address').value = signal.protocol_address;
+ document.getElementById('db_source').value = signal.db_source;
+ document.getElementById('preprocessing_enabled').checked = signal.preprocessing_enabled || false;
+
+ updateProtocolFields();
+ document.getElementById('signal-modal').style.display = 'block';
+}
+
+function closeSignalModal() {
+ document.getElementById('signal-modal').style.display = 'none';
+ editingSignalId = null;
+}
+
+function updateProtocolFields() {
+ const protocolType = document.getElementById('protocol_type').value;
+ const helpText = document.getElementById('protocol-address-help');
+
+ switch (protocolType) {
+ case 'modbus_tcp':
+ case 'modbus_rtu':
+ helpText.textContent = 'Modbus address format: 40001 (holding register), 30001 (input register), 10001 (coil), 00001 (discrete input)';
+ break;
+ case 'opcua':
+ helpText.textContent = 'OPC UA NodeId format: ns=2;s=MyVariable or ns=2;i=1234';
+ break;
+ case 'rest_api':
+ helpText.textContent = 'REST API endpoint format: /api/v1/data/endpoint';
+ break;
+ default:
+ helpText.textContent = '';
+ }
+}
+
+// Form Submission
+async function saveSignal(event) {
+ event.preventDefault();
+
+ const formData = getSignalFormData();
+
+ try {
+ let response;
+ if (editingSignalId) {
+ response = await fetch(`/api/v1/dashboard/protocol-signals/${editingSignalId}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(formData)
+ });
+ } else {
+ response = await fetch('/api/v1/dashboard/protocol-signals', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(formData)
+ });
+ }
+
+ const data = await response.json();
+
+ if (data.success) {
+ showSimplifiedAlert(`Protocol signal ${editingSignalId ? 'updated' : 'created'} successfully!`, 'success');
+ closeSignalModal();
+ loadAllSignals();
+ } else {
+ showSimplifiedAlert(`Failed to save signal: ${data.detail || 'Unknown error'}`, 'error');
+ }
+ } catch (error) {
+ console.error('Error saving signal:', error);
+ showSimplifiedAlert('Error saving signal', 'error');
+ }
+}
+
+function getSignalFormData() {
+ const tagsInput = document.getElementById('tags').value;
+ const tags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag);
+
+ return {
+ signal_name: document.getElementById('signal_name').value,
+ tags: tags,
+ protocol_type: document.getElementById('protocol_type').value,
+ protocol_address: document.getElementById('protocol_address').value,
+ db_source: document.getElementById('db_source').value,
+ preprocessing_enabled: document.getElementById('preprocessing_enabled').checked
+ };
+}
+
+// Signal Management
+async function editSignal(signalId) {
+ try {
+ const response = await fetch(`/api/v1/dashboard/protocol-signals/${signalId}`);
+ const data = await response.json();
+
+ if (data.success) {
+ showEditSignalModal(data.signal);
+ } else {
+ showSimplifiedAlert('Signal not found', 'error');
+ }
+ } catch (error) {
+ console.error('Error loading signal:', error);
+ showSimplifiedAlert('Error loading signal', 'error');
+ }
+}
+
+async function deleteSignal(signalId) {
+ if (!confirm('Are you sure you want to delete this signal?')) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`/api/v1/dashboard/protocol-signals/${signalId}`, {
+ method: 'DELETE'
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ showSimplifiedAlert('Signal deleted successfully!', 'success');
+ loadAllSignals();
+ } else {
+ showSimplifiedAlert(`Failed to delete signal: ${data.detail || 'Unknown error'}`, 'error');
+ }
+ } catch (error) {
+ console.error('Error deleting signal:', error);
+ showSimplifiedAlert('Error deleting signal', 'error');
+ }
+}
+
+// Discovery Integration
+function autoPopulateSignalForm(discoveryData) {
+ console.log('Auto-populating signal form with:', discoveryData);
+
+ // First, open the "Add New Signal" modal
+ showAddSignalModal();
+
+ // Wait for modal to be fully loaded and visible
+ const waitForModal = setInterval(() => {
+ const modal = document.getElementById('signal-modal');
+ const isModalVisible = modal && modal.style.display !== 'none';
+
+ if (isModalVisible) {
+ clearInterval(waitForModal);
+ populateModalFields(discoveryData);
+ }
+ }, 50);
+
+ // Timeout after 2 seconds
+ setTimeout(() => {
+ clearInterval(waitForModal);
+ const modal = document.getElementById('signal-modal');
+ if (modal && modal.style.display !== 'none') {
+ populateModalFields(discoveryData);
+ } else {
+ console.error('Modal did not open within timeout period');
+ showSimplifiedAlert('Could not open signal form. Please try opening it manually.', 'error');
+ }
+ }, 2000);
+}
+
+function populateModalFields(discoveryData) {
+ console.log('Populating modal fields with:', discoveryData);
+
+ // Populate signal name
+ const signalNameField = document.getElementById('signal_name');
+ if (signalNameField && discoveryData.signal_name) {
+ signalNameField.value = discoveryData.signal_name;
+ console.log('✓ Set signal_name to:', discoveryData.signal_name);
+ }
+
+ // Populate tags
+ const tagsField = document.getElementById('tags');
+ if (tagsField && discoveryData.tags) {
+ tagsField.value = discoveryData.tags.join(', ');
+ console.log('✓ Set tags to:', discoveryData.tags);
+ }
+
+ // Populate protocol type
+ const protocolTypeField = document.getElementById('protocol_type');
+ if (protocolTypeField && discoveryData.protocol_type) {
+ protocolTypeField.value = discoveryData.protocol_type;
+ console.log('✓ Set protocol_type to:', discoveryData.protocol_type);
+ // Trigger protocol field updates
+ protocolTypeField.dispatchEvent(new Event('change'));
+ }
+
+ // Populate protocol address
+ const protocolAddressField = document.getElementById('protocol_address');
+ if (protocolAddressField && discoveryData.protocol_address) {
+ protocolAddressField.value = discoveryData.protocol_address;
+ console.log('✓ Set protocol_address to:', discoveryData.protocol_address);
+ }
+
+ // Populate database source
+ const dbSourceField = document.getElementById('db_source');
+ if (dbSourceField && discoveryData.db_source) {
+ dbSourceField.value = discoveryData.db_source;
+ console.log('✓ Set db_source to:', discoveryData.db_source);
+ }
+
+ // Show success message
+ showSimplifiedAlert(`Signal form populated with discovery data. Please review and save.`, 'success');
+}
+
+// Utility Functions
+function showSimplifiedAlert(message, type = 'info') {
+ const alertsDiv = document.getElementById('simplified-alerts');
+ const alertDiv = document.createElement('div');
+ alertDiv.className = `alert ${type === 'error' ? 'error' : 'success'}`;
+ alertDiv.textContent = message;
+
+ alertsDiv.innerHTML = '';
+ alertsDiv.appendChild(alertDiv);
+
+ // Auto-remove after 5 seconds
+ setTimeout(() => {
+ if (alertDiv.parentNode) {
+ alertDiv.remove();
+ }
+ }, 5000);
+}
+
+// Initialize
+document.addEventListener('DOMContentLoaded', function() {
+ const signalForm = document.getElementById('signal-form');
+ if (signalForm) {
+ signalForm.addEventListener('submit', saveSignal);
+ }
+
+ // Load initial data
+ loadAllSignals();
+});
\ No newline at end of file
diff --git a/static/simplified_styles.css b/static/simplified_styles.css
new file mode 100644
index 0000000..45125de
--- /dev/null
+++ b/static/simplified_styles.css
@@ -0,0 +1,361 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background-color: #f5f7fa;
+ color: #333;
+ line-height: 1.6;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+}
+
+.header {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 30px 0;
+ border-radius: 10px;
+ margin-bottom: 30px;
+ text-align: center;
+}
+
+.header h1 {
+ font-size: 2.5rem;
+ margin-bottom: 10px;
+}
+
+.header p {
+ font-size: 1.1rem;
+ opacity: 0.9;
+}
+
+.controls {
+ background: white;
+ padding: 25px;
+ border-radius: 10px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ margin-bottom: 30px;
+}
+
+.filter-section {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr auto;
+ gap: 15px;
+ align-items: end;
+}
+
+.filter-group {
+ display: flex;
+ flex-direction: column;
+}
+
+.filter-group label {
+ font-weight: 600;
+ margin-bottom: 5px;
+ color: #555;
+}
+
+.filter-group input, .filter-group select {
+ padding: 10px;
+ border: 2px solid #e1e5e9;
+ border-radius: 6px;
+ font-size: 14px;
+ transition: border-color 0.3s;
+}
+
+.filter-group input:focus, .filter-group select:focus {
+ outline: none;
+ border-color: #667eea;
+}
+
+.btn {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 600;
+ transition: all 0.3s;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+}
+
+.btn-primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
+}
+
+.btn-secondary {
+ background: #6c757d;
+ color: white;
+}
+
+.btn-secondary:hover {
+ background: #5a6268;
+}
+
+.tag-cloud {
+ background: white;
+ padding: 20px;
+ border-radius: 10px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ margin-bottom: 30px;
+}
+
+.tag-cloud h3 {
+ margin-bottom: 15px;
+ color: #333;
+}
+
+.tag-cloud-item {
+ display: inline-block;
+ background: #e9ecef;
+ padding: 5px 12px;
+ margin: 5px;
+ border-radius: 20px;
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+.tag-cloud-item:hover {
+ background: #667eea;
+ color: white;
+ transform: scale(1.05);
+}
+
+.signals-table {
+ background: white;
+ border-radius: 10px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+}
+
+.table-header {
+ background: #f8f9fa;
+ padding: 20px;
+ border-bottom: 1px solid #e1e5e9;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.table-header h3 {
+ color: #333;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+th, td {
+ padding: 15px;
+ text-align: left;
+ border-bottom: 1px solid #e1e5e9;
+}
+
+th {
+ background: #f8f9fa;
+ font-weight: 600;
+ color: #555;
+}
+
+tr:hover {
+ background: #f8f9fa;
+}
+
+.tag-badge {
+ display: inline-block;
+ background: #667eea;
+ color: white;
+ padding: 3px 8px;
+ margin: 2px;
+ border-radius: 12px;
+ font-size: 11px;
+}
+
+.status-badge {
+ padding: 5px 10px;
+ border-radius: 15px;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.status-badge.enabled {
+ background: #d4edda;
+ color: #155724;
+}
+
+.status-badge.disabled {
+ background: #f8d7da;
+ color: #721c24;
+}
+
+.btn-edit, .btn-delete {
+ padding: 6px 12px;
+ margin: 0 2px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 12px;
+}
+
+.btn-edit {
+ background: #28a745;
+ color: white;
+}
+
+.btn-delete {
+ background: #dc3545;
+ color: white;
+}
+
+.btn-edit:hover {
+ background: #218838;
+}
+
+.btn-delete:hover {
+ background: #c82333;
+}
+
+.modal {
+ display: none;
+ position: fixed;
+ z-index: 1000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+}
+
+.modal-content {
+ background-color: white;
+ margin: 5% auto;
+ padding: 30px;
+ border-radius: 10px;
+ width: 90%;
+ max-width: 600px;
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid #e1e5e9;
+}
+
+.modal-header h2 {
+ color: #333;
+}
+
+.close {
+ color: #aaa;
+ font-size: 28px;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.close:hover {
+ color: #333;
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: 600;
+ color: #555;
+}
+
+.form-group input, .form-group select, .form-group textarea {
+ width: 100%;
+ padding: 10px;
+ border: 2px solid #e1e5e9;
+ border-radius: 6px;
+ font-size: 14px;
+ transition: border-color 0.3s;
+}
+
+.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
+ outline: none;
+ border-color: #667eea;
+}
+
+.form-help {
+ font-size: 12px;
+ color: #6c757d;
+ margin-top: 5px;
+}
+
+.form-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 25px;
+}
+
+.alert {
+ padding: 15px;
+ margin: 20px 0;
+ border-radius: 6px;
+ font-weight: 500;
+}
+
+.alert.success {
+ background: #d4edda;
+ color: #155724;
+ border: 1px solid #c3e6cb;
+}
+
+.alert.error {
+ background: #f8d7da;
+ color: #721c24;
+ border: 1px solid #f5c6cb;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 50px 20px;
+ color: #6c757d;
+}
+
+.empty-state h3 {
+ margin-bottom: 10px;
+}
+
+@media (max-width: 768px) {
+ .filter-section {
+ grid-template-columns: 1fr;
+ }
+
+ .table-header {
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ table {
+ font-size: 14px;
+ }
+
+ th, td {
+ padding: 10px;
+ }
+}
\ No newline at end of file
diff --git a/templates/simplified_protocol_signals.html b/templates/simplified_protocol_signals.html
new file mode 100644
index 0000000..3b3c217
--- /dev/null
+++ b/templates/simplified_protocol_signals.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+
Calejo Control - Protocol Signals
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Signal Name |
+ Protocol Type |
+ Tags |
+ Protocol Address |
+ Database Source |
+ Status |
+ Actions |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test_api_integration.py b/test_api_integration.py
new file mode 100644
index 0000000..8528086
--- /dev/null
+++ b/test_api_integration.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+"""
+Test API Integration for Simplified Protocol Signals
+"""
+
+import sys
+import os
+import asyncio
+import json
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from src.dashboard.simplified_models import ProtocolSignalCreate, ProtocolType
+from src.dashboard.simplified_configuration_manager import simplified_configuration_manager
+
+async def test_api_endpoints():
+ """Test the API endpoints through the configuration manager"""
+ print("\n=== Testing API Integration ===")
+
+ # Test 1: Create signals
+ print("\n1. Creating test signals:")
+ test_signals = [
+ {
+ "signal_name": "Boiler Temperature Reading",
+ "tags": ["equipment:boiler", "protocol:modbus_tcp", "data_point:temperature", "unit:celsius"],
+ "protocol_type": "modbus_tcp",
+ "protocol_address": "30001",
+ "db_source": "measurements.boiler_temperature"
+ },
+ {
+ "signal_name": "Pump Motor Status",
+ "tags": ["equipment:pump", "protocol:opcua", "data_point:status", "type:boolean"],
+ "protocol_type": "opcua",
+ "protocol_address": "ns=2;s=PumpStatus",
+ "db_source": "measurements.pump_status"
+ },
+ {
+ "signal_name": "System Pressure",
+ "tags": ["equipment:system", "protocol:modbus_tcp", "data_point:pressure", "unit:psi"],
+ "protocol_type": "modbus_tcp",
+ "protocol_address": "30002",
+ "db_source": "measurements.system_pressure"
+ }
+ ]
+
+ created_signals = []
+ for signal_data in test_signals:
+ signal_create = ProtocolSignalCreate(
+ signal_name=signal_data["signal_name"],
+ tags=signal_data["tags"],
+ protocol_type=ProtocolType(signal_data["protocol_type"]),
+ protocol_address=signal_data["protocol_address"],
+ db_source=signal_data["db_source"]
+ )
+
+ success = simplified_configuration_manager.add_protocol_signal(signal_create)
+ if success:
+ # Get the actual signal ID that was used
+ signal_id = signal_create.generate_signal_id()
+ signal = simplified_configuration_manager.get_protocol_signal(signal_id)
+ if signal:
+ created_signals.append(signal)
+ print(f" ✓ Created: {signal.signal_name}")
+ else:
+ print(f" ⚠ Created but cannot retrieve: {signal_data['signal_name']}")
+ else:
+ print(f" ✗ Failed to create: {signal_data['signal_name']}")
+
+ # Test 2: Get all signals
+ print("\n2. Getting all signals:")
+ all_signals = simplified_configuration_manager.get_protocol_signals()
+ print(f" Total signals: {len(all_signals)}")
+ for signal in all_signals:
+ print(f" - {signal.signal_name} ({signal.protocol_type.value})")
+
+ # Test 3: Filter by tags
+ print("\n3. Filtering by tags:")
+ modbus_signals = simplified_configuration_manager.search_signals_by_tags(["protocol:modbus_tcp"])
+ print(f" Modbus signals: {len(modbus_signals)}")
+ for signal in modbus_signals:
+ print(f" - {signal.signal_name}")
+
+ # Test 4: Get all tags
+ print("\n4. Getting all tags:")
+ all_tags = simplified_configuration_manager.get_all_tags()
+ print(f" All tags: {all_tags}")
+
+ # Test 5: Update a signal
+ print("\n5. Updating a signal:")
+ if created_signals:
+ signal_to_update = created_signals[0]
+ print(f" Updating: {signal_to_update.signal_name}")
+
+ from src.dashboard.simplified_models import ProtocolSignalUpdate
+ update_data = ProtocolSignalUpdate(
+ signal_name="Updated Boiler Temperature",
+ tags=["equipment:boiler", "protocol:modbus_tcp", "data_point:temperature", "unit:celsius", "updated:true"]
+ )
+
+ success = simplified_configuration_manager.update_protocol_signal(signal_to_update.signal_id, update_data)
+ if success:
+ updated_signal = simplified_configuration_manager.get_protocol_signal(signal_to_update.signal_id)
+ print(f" ✓ Updated to: {updated_signal.signal_name}")
+ print(f" New tags: {updated_signal.tags}")
+ else:
+ print(f" ✗ Failed to update")
+
+ # Test 6: Delete a signal
+ print("\n6. Deleting a signal:")
+ if len(created_signals) > 1:
+ signal_to_delete = created_signals[1]
+ print(f" Deleting: {signal_to_delete.signal_name}")
+
+ success = simplified_configuration_manager.delete_protocol_signal(signal_to_delete.signal_id)
+ if success:
+ print(f" ✓ Deleted successfully")
+ else:
+ print(f" ✗ Failed to delete")
+
+ # Test 7: Get remaining signals
+ print("\n7. Final signal count:")
+ final_signals = simplified_configuration_manager.get_protocol_signals()
+ print(f" Remaining signals: {len(final_signals)}")
+
+ return len(final_signals) > 0
+
+def test_api_compatibility():
+ """Test that the new API is compatible with discovery results"""
+ print("\n=== Testing Discovery Compatibility ===")
+
+ from src.dashboard.simplified_models import SignalDiscoveryResult
+
+ # Simulate discovery results
+ discovery_results = [
+ {
+ "device_name": "Flow Meter",
+ "protocol_type": "modbus_tcp",
+ "protocol_address": "30003",
+ "data_point": "Flow Rate",
+ "device_address": "192.168.1.105"
+ },
+ {
+ "device_name": "Level Sensor",
+ "protocol_type": "opcua",
+ "protocol_address": "ns=2;s=Level",
+ "data_point": "Tank Level",
+ "device_address": "192.168.1.106"
+ }
+ ]
+
+ for discovery_data in discovery_results:
+ discovery = SignalDiscoveryResult(**discovery_data)
+ signal_create = discovery.to_protocol_signal_create()
+
+ print(f"\nDiscovery: {discovery.device_name}")
+ print(f" Signal Name: {signal_create.signal_name}")
+ print(f" Tags: {signal_create.tags}")
+ print(f" Protocol: {signal_create.protocol_type.value}")
+ print(f" Address: {signal_create.protocol_address}")
+ print(f" DB Source: {signal_create.db_source}")
+
+ # Validate
+ validation = simplified_configuration_manager.validate_signal_configuration(signal_create)
+ print(f" Valid: {validation['valid']}")
+ if validation['warnings']:
+ print(f" Warnings: {validation['warnings']}")
+
+def main():
+ """Run all API integration tests"""
+ print("Calejo Control API Integration Test")
+ print("=" * 50)
+
+ try:
+ # Run async tests
+ success = asyncio.run(test_api_endpoints())
+
+ # Run compatibility tests
+ test_api_compatibility()
+
+ print("\n" + "=" * 50)
+ if success:
+ print("✅ All API integration tests completed successfully!")
+ print("\nAPI Endpoints Available:")
+ print(" • GET /api/v1/dashboard/protocol-signals")
+ print(" • GET /api/v1/dashboard/protocol-signals/{signal_id}")
+ print(" • POST /api/v1/dashboard/protocol-signals")
+ print(" • PUT /api/v1/dashboard/protocol-signals/{signal_id}")
+ print(" • DELETE /api/v1/dashboard/protocol-signals/{signal_id}")
+ print(" • GET /api/v1/dashboard/protocol-signals/tags/all")
+ else:
+ print("❌ Some API integration tests failed")
+ return 1
+
+ except Exception as e:
+ print(f"\n❌ API integration test failed: {e}")
+ import traceback
+ traceback.print_exc()
+ return 1
+
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
\ No newline at end of file
diff --git a/test_discovery.js b/test_discovery.js
new file mode 100644
index 0000000..5b97d58
--- /dev/null
+++ b/test_discovery.js
@@ -0,0 +1,329 @@
+// Test script to verify discovery functionality
+// This simulates the browser environment and tests the discovery system
+
+// Mock browser environment
+let modalDisplay = 'none';
+const mockDocument = {
+ getElementById: function(id) {
+ console.log(`getElementById called with: ${id}`);
+
+ // Mock the modal
+ if (id === 'mapping-modal') {
+ return {
+ style: {
+ display: modalDisplay,
+ set display(value) {
+ modalDisplay = value;
+ console.log(`Modal display set to: ${value}`);
+ },
+ get display() {
+ return modalDisplay;
+ }
+ },
+ innerHTML: 'Mock modal content'
+ };
+ }
+
+ // Mock form fields
+ const mockFields = {
+ 'mapping_id': { value: '' },
+ 'protocol_type': { value: '', dispatchEvent: () => console.log('protocol_type change event') },
+ 'protocol_address': { value: '' },
+ 'station_id': {
+ value: '',
+ options: [{ value: '', textContent: 'Select Station' }, { value: 'station_main', textContent: 'Main Pump Station' }],
+ dispatchEvent: () => console.log('station_id change event')
+ },
+ 'equipment_id': {
+ value: '',
+ options: [{ value: '', textContent: 'Select Equipment' }, { value: 'pump_primary', textContent: 'Primary Pump' }]
+ },
+ 'data_type_id': {
+ value: '',
+ options: [{ value: '', textContent: 'Select Data Type' }, { value: 'speed_pump', textContent: 'Pump Speed' }]
+ },
+ 'db_source': { value: '' }
+ };
+
+ return mockFields[id] || null;
+ },
+ querySelector: function(selector) {
+ console.log(`querySelector called with: ${selector}`);
+ return null;
+ },
+ querySelectorAll: function(selector) {
+ console.log(`querySelectorAll called with: ${selector}`);
+ return [];
+ }
+};
+
+// Mock global document
+const document = mockDocument;
+
+// Mock showAddMappingModal function
+const showAddMappingModal = function() {
+ console.log('showAddMappingModal called');
+ const modal = document.getElementById('mapping-modal');
+ if (modal) {
+ modal.style.display = 'block';
+ console.log('Modal opened successfully');
+ }
+};
+
+// Import the discovery class (simplified version for testing)
+class ProtocolDiscovery {
+ constructor() {
+ this.currentScanId = 'test-scan-123';
+ this.isScanning = false;
+ }
+
+ // Test the populateProtocolForm method
+ populateProtocolForm(endpoint) {
+ console.log('\n=== Testing populateProtocolForm ===');
+
+ // Create a new protocol mapping ID
+ const mappingId = `${endpoint.device_id}_${endpoint.protocol_type}`;
+
+ // Get default metadata IDs
+ const defaultStationId = this.getDefaultStationId();
+ const defaultEquipmentId = this.getDefaultEquipmentId(defaultStationId);
+ const defaultDataTypeId = this.getDefaultDataTypeId();
+
+ // Set form values
+ const formData = {
+ mapping_id: mappingId,
+ protocol_type: endpoint.protocol_type === 'opc_ua' ? 'opcua' : endpoint.protocol_type,
+ protocol_address: this.getDefaultProtocolAddress(endpoint),
+ device_name: endpoint.device_name || endpoint.device_id,
+ device_address: endpoint.address,
+ device_port: endpoint.port || '',
+ station_id: defaultStationId,
+ equipment_id: defaultEquipmentId,
+ data_type_id: defaultDataTypeId
+ };
+
+ console.log('Form data created:', formData);
+
+ // Auto-populate the protocol mapping form
+ this.autoPopulateProtocolForm(formData);
+ }
+
+ autoPopulateProtocolForm(formData) {
+ console.log('\n=== Testing autoPopulateProtocolForm ===');
+ console.log('Auto-populating protocol form with:', formData);
+
+ // First, open the "Add New Mapping" modal
+ this.openAddMappingModal();
+
+ // Wait for modal to be fully loaded and visible
+ const waitForModal = setInterval(() => {
+ const modal = document.getElementById('mapping-modal');
+ const isModalVisible = modal && modal.style.display !== 'none';
+
+ if (isModalVisible) {
+ clearInterval(waitForModal);
+ this.populateModalFields(formData);
+ }
+ }, 50);
+
+ // Timeout after 2 seconds
+ setTimeout(() => {
+ clearInterval(waitForModal);
+ const modal = document.getElementById('mapping-modal');
+ if (modal && modal.style.display !== 'none') {
+ this.populateModalFields(formData);
+ } else {
+ console.error('Modal did not open within timeout period');
+ }
+ }, 2000);
+ }
+
+ populateModalFields(formData) {
+ console.log('\n=== Testing populateModalFields ===');
+ console.log('Populating modal fields with:', formData);
+
+ // Find and populate form fields in the modal
+ const mappingIdField = document.getElementById('mapping_id');
+ const protocolTypeField = document.getElementById('protocol_type');
+ const protocolAddressField = document.getElementById('protocol_address');
+ const stationIdField = document.getElementById('station_id');
+ const equipmentIdField = document.getElementById('equipment_id');
+ const dataTypeIdField = document.getElementById('data_type_id');
+ const dbSourceField = document.getElementById('db_source');
+
+ console.log('Found fields:', {
+ mappingIdField: !!mappingIdField,
+ protocolTypeField: !!protocolTypeField,
+ protocolAddressField: !!protocolAddressField,
+ stationIdField: !!stationIdField,
+ equipmentIdField: !!equipmentIdField,
+ dataTypeIdField: !!dataTypeIdField,
+ dbSourceField: !!dbSourceField
+ });
+
+ // Populate mapping ID
+ if (mappingIdField) {
+ mappingIdField.value = formData.mapping_id;
+ console.log('✓ Set mapping_id to:', formData.mapping_id);
+ }
+
+ // Populate protocol type
+ if (protocolTypeField) {
+ protocolTypeField.value = formData.protocol_type;
+ console.log('✓ Set protocol_type to:', formData.protocol_type);
+ // Trigger protocol field updates
+ protocolTypeField.dispatchEvent(new Event('change'));
+ }
+
+ // Populate protocol address
+ if (protocolAddressField) {
+ protocolAddressField.value = formData.protocol_address;
+ console.log('✓ Set protocol_address to:', formData.protocol_address);
+ }
+
+ // Set station, equipment, and data type
+ if (stationIdField) {
+ this.waitForStationsLoaded(() => {
+ if (this.isValidStationId(formData.station_id)) {
+ stationIdField.value = formData.station_id;
+ console.log('✓ Set station_id to:', formData.station_id);
+ // Trigger equipment dropdown update
+ stationIdField.dispatchEvent(new Event('change'));
+
+ // Wait for equipment to be loaded
+ setTimeout(() => {
+ if (equipmentIdField && this.isValidEquipmentId(formData.equipment_id)) {
+ equipmentIdField.value = formData.equipment_id;
+ console.log('✓ Set equipment_id to:', formData.equipment_id);
+ }
+
+ if (dataTypeIdField && this.isValidDataTypeId(formData.data_type_id)) {
+ dataTypeIdField.value = formData.data_type_id;
+ console.log('✓ Set data_type_id to:', formData.data_type_id);
+ }
+
+ // Set default database source
+ if (dbSourceField && !dbSourceField.value) {
+ dbSourceField.value = 'measurements.' + formData.device_name.toLowerCase().replace(/[^a-z0-9]/g, '_');
+ console.log('✓ Set db_source to:', dbSourceField.value);
+ }
+
+ console.log('\n✅ Protocol form successfully populated!');
+ console.log('All fields should now be filled with discovery data.');
+ }, 100);
+ }
+ });
+ }
+ }
+
+ openAddMappingModal() {
+ console.log('\n=== Testing openAddMappingModal ===');
+ console.log('Attempting to open Add New Mapping modal...');
+
+ // First try to use the global function
+ if (typeof showAddMappingModal === 'function') {
+ console.log('✓ Using showAddMappingModal function');
+ showAddMappingModal();
+ return;
+ }
+
+ console.log('❌ Could not find any way to open the protocol mapping modal');
+ }
+
+ getDefaultProtocolAddress(endpoint) {
+ const protocolType = endpoint.protocol_type;
+ switch (protocolType) {
+ case 'modbus_tcp':
+ return '40001';
+ case 'opc_ua':
+ return 'ns=2;s=MyVariable';
+ case 'modbus_rtu':
+ return '40001';
+ case 'rest_api':
+ return '/api/v1/data/endpoint';
+ default:
+ return 'unknown';
+ }
+ }
+
+ getDefaultStationId() {
+ const stationSelect = document.getElementById('station_id');
+ if (stationSelect && stationSelect.options.length > 1) {
+ return stationSelect.options[1].value;
+ }
+ return 'station_main';
+ }
+
+ getDefaultEquipmentId(stationId) {
+ const equipmentSelect = document.getElementById('equipment_id');
+ if (equipmentSelect && equipmentSelect.options.length > 1) {
+ return equipmentSelect.options[1].value;
+ }
+ if (stationId === 'station_main') return 'pump_primary';
+ if (stationId === 'station_backup') return 'pump_backup';
+ if (stationId === 'station_control') return 'controller_plc';
+ return 'pump_primary';
+ }
+
+ getDefaultDataTypeId() {
+ const dataTypeSelect = document.getElementById('data_type_id');
+ if (dataTypeSelect && dataTypeSelect.options.length > 1) {
+ return dataTypeSelect.options[1].value;
+ }
+ return 'speed_pump';
+ }
+
+ isValidStationId(stationId) {
+ const stationSelect = document.getElementById('station_id');
+ if (!stationSelect) return false;
+ return Array.from(stationSelect.options).some(option => option.value === stationId);
+ }
+
+ isValidEquipmentId(equipmentId) {
+ const equipmentSelect = document.getElementById('equipment_id');
+ if (!equipmentSelect) return false;
+ return Array.from(equipmentSelect.options).some(option => option.value === equipmentId);
+ }
+
+ isValidDataTypeId(dataTypeId) {
+ const dataTypeSelect = document.getElementById('data_type_id');
+ if (!dataTypeSelect) return false;
+ return Array.from(dataTypeSelect.options).some(option => option.value === dataTypeId);
+ }
+
+ waitForStationsLoaded(callback, maxWait = 3000) {
+ const stationSelect = document.getElementById('station_id');
+ if (!stationSelect) {
+ console.error('Station select element not found');
+ callback();
+ return;
+ }
+
+ // Check if stations are already loaded
+ if (stationSelect.options.length > 1) {
+ console.log('✓ Stations already loaded:', stationSelect.options.length);
+ callback();
+ return;
+ }
+
+ console.log('Waiting for stations to load...');
+ callback(); // In test, just call immediately
+ }
+}
+
+// Run the test
+console.log('🚀 Starting Protocol Discovery Test\n');
+
+const discovery = new ProtocolDiscovery();
+
+// Test with a sample discovered endpoint
+const sampleEndpoint = {
+ device_id: 'device_001',
+ protocol_type: 'modbus_tcp',
+ device_name: 'Water Pump Controller',
+ address: '192.168.1.100',
+ port: 502
+};
+
+console.log('Testing with sample endpoint:', sampleEndpoint);
+discovery.populateProtocolForm(sampleEndpoint);
\ No newline at end of file
diff --git a/test_discovery_simple.html b/test_discovery_simple.html
new file mode 100644
index 0000000..b44b537
--- /dev/null
+++ b/test_discovery_simple.html
@@ -0,0 +1,328 @@
+
+
+
+
Protocol Discovery Test
+
+
+
+
Protocol Discovery Test
+
+
+
Test Discovery "Use" Button
+
+
+
+
+
+
+
+
×
+
Add Protocol Mapping
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test_integration_workflow.py b/test_integration_workflow.py
new file mode 100644
index 0000000..7e9f105
--- /dev/null
+++ b/test_integration_workflow.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python3
+"""
+Test the integration between discovery and simplified protocol mapping system
+"""
+
+import sys
+import os
+sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
+
+from src.dashboard.simplified_models import ProtocolSignalCreate, ProtocolType
+from src.dashboard.simplified_configuration_manager import simplified_configuration_manager
+
+def test_discovery_to_signal_workflow():
+ """Test the complete workflow from discovery to signal creation"""
+
+ print("=" * 60)
+ print("Testing Discovery to Protocol Signal Integration")
+ print("=" * 60)
+
+ # Simulate discovery results
+ discovery_results = [
+ {
+ "device_name": "Boiler Temperature Sensor",
+ "protocol_type": "opcua",
+ "protocol_address": "ns=2;s=Temperature",
+ "data_point": "Temperature",
+ "device_address": "192.168.1.100"
+ },
+ {
+ "device_name": "Main Water Pump",
+ "protocol_type": "modbus_tcp",
+ "protocol_address": "40001",
+ "data_point": "Speed",
+ "device_address": "192.168.1.101"
+ },
+ {
+ "device_name": "System Pressure Sensor",
+ "protocol_type": "modbus_tcp",
+ "protocol_address": "40002",
+ "data_point": "Pressure",
+ "device_address": "192.168.1.102"
+ }
+ ]
+
+ print("\n1. Discovery Results:")
+ for i, device in enumerate(discovery_results, 1):
+ print(f" {i}. {device['device_name']} - {device['protocol_type']} - {device['protocol_address']}")
+
+ # Convert discovery results to signal format
+ print("\n2. Converting Discovery to Signal Format:")
+ signals_created = []
+
+ for device in discovery_results:
+ # Generate signal name
+ signal_name = f"{device['device_name']} {device['data_point']}"
+
+ # Generate tags
+ tags = [
+ f"device:{device['device_name'].lower().replace(' ', '_')}",
+ f"protocol:{device['protocol_type']}",
+ f"data_point:{device['data_point'].lower().replace(' ', '_')}",
+ f"address:{device['device_address']}",
+ "discovered:true"
+ ]
+
+ # Generate database source
+ db_source = f"measurements.{device['device_name'].lower().replace(' ', '_')}_{device['data_point'].lower().replace(' ', '_')}"
+
+ # Create signal
+ signal_create = ProtocolSignalCreate(
+ signal_name=signal_name,
+ tags=tags,
+ protocol_type=ProtocolType(device['protocol_type']),
+ protocol_address=device['protocol_address'],
+ db_source=db_source
+ )
+
+ # Add to configuration manager
+ success = simplified_configuration_manager.add_protocol_signal(signal_create)
+
+ if success:
+ signals_created.append(signal_create)
+ print(f" ✓ Created: {signal_name}")
+ print(f" Tags: {', '.join(tags)}")
+ print(f" Protocol: {device['protocol_type']} at {device['protocol_address']}")
+ print(f" DB Source: {db_source}")
+ else:
+ print(f" ✗ Failed to create: {signal_name}")
+
+ # Test filtering and retrieval
+ print("\n3. Testing Signal Management:")
+
+ # Get all signals
+ all_signals = simplified_configuration_manager.get_protocol_signals()
+ print(f" Total signals: {len(all_signals)}")
+
+ # Filter by protocol
+ modbus_signals = [s for s in all_signals if 'protocol:modbus_tcp' in s.tags]
+ print(f" Modbus TCP signals: {len(modbus_signals)}")
+
+ # Filter by device
+ boiler_signals = [s for s in all_signals if 'device:boiler_temperature_sensor' in s.tags]
+ print(f" Boiler signals: {len(boiler_signals)}")
+
+ # Get all tags
+ all_tags = simplified_configuration_manager.get_all_tags()
+ print(f" All tags: {len(all_tags)} unique tags")
+
+ # Test signal updates
+ print("\n4. Testing Signal Updates:")
+ if signals_created:
+ first_signal = signals_created[0]
+ signal_id = first_signal.generate_signal_id()
+
+ # Get the signal
+ signal = simplified_configuration_manager.get_protocol_signal(signal_id)
+ if signal:
+ print(f" Retrieved signal: {signal.signal_name}")
+
+ # Update the signal
+ updated_tags = signal.tags + ["unit:celsius", "alarm:high_temp"]
+ update_success = simplified_configuration_manager.update_protocol_signal(
+ signal_id,
+ tags=updated_tags,
+ preprocessing_enabled=True
+ )
+
+ if update_success:
+ print(f" ✓ Updated signal with new tags and preprocessing")
+ updated_signal = simplified_configuration_manager.get_protocol_signal(signal_id)
+ print(f" New tags: {', '.join(updated_signal.tags)}")
+ print(f" Preprocessing: {updated_signal.preprocessing_enabled}")
+ else:
+ print(f" ✗ Failed to update signal")
+
+ # Test signal deletion
+ print("\n5. Testing Signal Deletion:")
+ if signals_created:
+ last_signal = signals_created[-1]
+ signal_id = last_signal.generate_signal_id()
+
+ delete_success = simplified_configuration_manager.delete_protocol_signal(signal_id)
+
+ if delete_success:
+ print(f" ✓ Deleted signal: {last_signal.signal_name}")
+ remaining_signals = simplified_configuration_manager.get_protocol_signals()
+ print(f" Remaining signals: {len(remaining_signals)}")
+ else:
+ print(f" ✗ Failed to delete signal")
+
+ print("\n" + "=" * 60)
+ print("Integration Test Results:")
+ print(f" - Discovery devices processed: {len(discovery_results)}")
+ print(f" - Signals successfully created: {len(signals_created)}")
+ print(f" - Final signal count: {len(simplified_configuration_manager.get_protocol_signals())}")
+ print(f" - Unique tags available: {len(simplified_configuration_manager.get_all_tags())}")
+
+ if len(signals_created) == len(discovery_results):
+ print("\n✅ SUCCESS: All discovery devices successfully converted to protocol signals!")
+ print(" The simplified system is working correctly with discovery integration.")
+ else:
+ print("\n❌ FAILURE: Some discovery devices failed to convert to signals.")
+
+ print("=" * 60)
+
+if __name__ == "__main__":
+ test_discovery_to_signal_workflow()
\ No newline at end of file
diff --git a/test_migration.py b/test_migration.py
new file mode 100644
index 0000000..46af023
--- /dev/null
+++ b/test_migration.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python3
+"""
+Migration Test Script
+Tests the simplified signal name + tags architecture
+"""
+
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from src.dashboard.simplified_models import (
+ ProtocolSignalCreate, ProtocolType, SignalDiscoveryResult
+)
+from src.dashboard.simplified_configuration_manager import simplified_configuration_manager
+
+def test_simplified_models():
+ """Test the new simplified models"""
+ print("\n=== Testing Simplified Models ===")
+
+ # Test 1: Create from discovery result
+ print("\n1. Testing discovery result conversion:")
+ discovery = SignalDiscoveryResult(
+ device_name="Water Pump Controller",
+ protocol_type=ProtocolType.MODBUS_TCP,
+ protocol_address="40001",
+ data_point="Speed",
+ device_address="192.168.1.100"
+ )
+
+ signal_create = discovery.to_protocol_signal_create()
+ print(f" Signal Name: {signal_create.signal_name}")
+ print(f" Tags: {signal_create.tags}")
+ print(f" Protocol: {signal_create.protocol_type}")
+ print(f" Address: {signal_create.protocol_address}")
+ print(f" DB Source: {signal_create.db_source}")
+
+ # Test 2: Validation
+ print("\n2. Testing validation:")
+ validation = simplified_configuration_manager.validate_signal_configuration(signal_create)
+ print(f" Valid: {validation['valid']}")
+ print(f" Errors: {validation['errors']}")
+ print(f" Warnings: {validation['warnings']}")
+
+ # Test 3: Add signal
+ print("\n3. Testing signal creation:")
+ success = simplified_configuration_manager.add_protocol_signal(signal_create)
+ print(f" Signal created: {success}")
+
+ # Test 4: Retrieve signals
+ print("\n4. Testing signal retrieval:")
+ signals = simplified_configuration_manager.get_protocol_signals()
+ print(f" Number of signals: {len(signals)}")
+ for signal in signals:
+ print(f" - {signal.signal_name} ({signal.signal_id})")
+
+ # Test 5: Tag-based filtering
+ print("\n5. Testing tag-based filtering:")
+ pump_signals = simplified_configuration_manager.search_signals_by_tags(["equipment:pump"])
+ print(f" Pump signals: {len(pump_signals)}")
+
+ # Test 6: All tags
+ print("\n6. Testing tag collection:")
+ all_tags = simplified_configuration_manager.get_all_tags()
+ print(f" All tags: {all_tags}")
+
+def test_migration_scenarios():
+ """Test various migration scenarios"""
+ print("\n=== Testing Migration Scenarios ===")
+
+ scenarios = [
+ {
+ "name": "Modbus Pump Speed",
+ "device_name": "Main Water Pump",
+ "protocol_type": ProtocolType.MODBUS_TCP,
+ "data_point": "Speed",
+ "protocol_address": "40001"
+ },
+ {
+ "name": "OPC UA Temperature",
+ "device_name": "Boiler Temperature Sensor",
+ "protocol_type": ProtocolType.OPCUA,
+ "data_point": "Temperature",
+ "protocol_address": "ns=2;s=Temperature"
+ },
+ {
+ "name": "REST API Status",
+ "device_name": "System Controller",
+ "protocol_type": ProtocolType.REST_API,
+ "data_point": "Status",
+ "protocol_address": "/api/v1/system/status"
+ }
+ ]
+
+ for scenario in scenarios:
+ print(f"\nScenario: {scenario['name']}")
+
+ discovery = SignalDiscoveryResult(
+ device_name=scenario["device_name"],
+ protocol_type=scenario["protocol_type"],
+ protocol_address=scenario["protocol_address"],
+ data_point=scenario["data_point"]
+ )
+
+ signal_create = discovery.to_protocol_signal_create()
+ success = simplified_configuration_manager.add_protocol_signal(signal_create)
+
+ print(f" Created: {success}")
+ print(f" Signal: {signal_create.signal_name}")
+ print(f" Tags: {', '.join(signal_create.tags[:3])}...")
+
+def compare_complexity():
+ """Compare old vs new approach complexity"""
+ print("\n=== Complexity Comparison ===")
+
+ print("\nOLD APPROACH (Complex IDs):")
+ print(" Required fields:")
+ print(" - station_id: 'station_main'")
+ print(" - equipment_id: 'pump_primary'")
+ print(" - data_type_id: 'speed_pump'")
+ print(" - protocol_address: '40001'")
+ print(" - db_source: 'measurements.pump_speed'")
+ print(" Issues: Complex relationships, redundant IDs, confusing UX")
+
+ print("\nNEW APPROACH (Simple Names + Tags):")
+ print(" Required fields:")
+ print(" - signal_name: 'Main Water Pump Speed'")
+ print(" - tags: ['equipment:pump', 'protocol:modbus_tcp', 'data_point:speed']")
+ print(" - protocol_address: '40001'")
+ print(" - db_source: 'measurements.main_water_pump_speed'")
+ print(" Benefits: Intuitive, flexible, simpler relationships")
+
+def main():
+ """Run all tests"""
+ print("Calejo Control Migration Test")
+ print("=" * 50)
+
+ try:
+ test_simplified_models()
+ test_migration_scenarios()
+ compare_complexity()
+
+ print("\n" + "=" * 50)
+ print("✅ All migration tests completed successfully!")
+ print("\nMigration Benefits:")
+ print(" • Simplified user experience")
+ print(" • Flexible tag-based organization")
+ print(" • Intuitive signal names")
+ print(" • Reduced complexity")
+ print(" • Better discovery integration")
+
+ except Exception as e:
+ print(f"\n❌ Migration test failed: {e}")
+ import traceback
+ traceback.print_exc()
+ return 1
+
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
\ No newline at end of file
diff --git a/test_simplified_ui.html b/test_simplified_ui.html
new file mode 100644
index 0000000..3e2eb83
--- /dev/null
+++ b/test_simplified_ui.html
@@ -0,0 +1,273 @@
+
+
+
+
+
+
Test Simplified UI
+
+
+
+
+
Simplified Protocol Signals UI Test
+
This test verifies the simplified UI components work correctly.
+
+
+
+
Test Actions:
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file