21 KiB
Protocol Mapping - Phase 1 Implementation Plan
Overview
This document outlines the detailed implementation plan for Phase 1 of the Protocol Mapping UI feature, supporting Modbus, OPC UA, and other industrial protocols.
🎯 Phase 1 Goals
- Enable basic configuration of database-to-protocol mappings through unified dashboard interface
- Replace hardcoded protocol mappings with configurable system
- Support multiple protocols (Modbus, OPC UA) through single Protocol Mapping tab
- Provide protocol-specific validation within unified interface
- Implement protocol switching within single dashboard tab
📋 Detailed Task Breakdown
Task 1: Extend Configuration Manager with Protocol Mapping Support
Priority: High Estimated Effort: 3 days
Implementation Details:
# File: src/dashboard/configuration_manager.py
class ProtocolMapping(BaseModel):
"""Protocol mapping configuration for all protocols"""
id: str
protocol_type: str # modbus_tcp, opcua, custom
station_id: str
pump_id: str
data_type: str # setpoint, status, power, etc.
protocol_address: str # register address or OPC UA node
db_source: str
transformation_rules: List[Dict] = []
# Protocol-specific configurations
modbus_config: Optional[Dict] = None
opcua_config: Optional[Dict] = None
class ConfigurationManager:
def __init__(self):
self.protocol_mappings: List[ProtocolMapping] = []
def add_protocol_mapping(self, mapping: ProtocolMapping) -> bool:
"""Add a new protocol mapping with validation"""
def get_protocol_mappings(self,
protocol_type: str = None,
station_id: str = None,
pump_id: str = None) -> List[ProtocolMapping]:
"""Get mappings filtered by protocol/station/pump"""
def validate_protocol_mapping(self, mapping: ProtocolMapping) -> Dict[str, Any]:
"""Validate mapping for conflicts and protocol-specific rules"""
Task 2: Create Protocol Mapping API Endpoints
Priority: High Estimated Effort: 2 days
Implementation Details:
# File: src/dashboard/api.py
@dashboard_router.get("/protocol-mappings")
async def get_protocol_mappings(
protocol_type: Optional[str] = None,
station_id: Optional[str] = None,
pump_id: Optional[str] = None
):
"""Get all protocol mappings"""
@dashboard_router.post("/protocol-mappings")
async def create_protocol_mapping(mapping: ProtocolMapping):
"""Create a new protocol mapping"""
@dashboard_router.put("/protocol-mappings/{mapping_id}")
async def update_protocol_mapping(mapping_id: str, mapping: ProtocolMapping):
"""Update an existing protocol mapping"""
@dashboard_router.delete("/protocol-mappings/{mapping_id}")
async def delete_protocol_mapping(mapping_id: str):
"""Delete a protocol mapping"""
@dashboard_router.post("/protocol-mappings/validate")
async def validate_protocol_mapping(mapping: ProtocolMapping):
"""Validate a protocol mapping without saving"""
# Protocol-specific endpoints
@dashboard_router.get("/protocol-mappings/modbus")
async def get_modbus_mappings():
"""Get all Modbus mappings"""
@dashboard_router.get("/protocol-mappings/opcua")
async def get_opcua_mappings():
"""Get all OPC UA mappings"""
Task 3: Build Multi-Protocol Configuration Form UI
Priority: High Estimated Effort: 3 days
Implementation Details:
<!-- File: static/dashboard.js - Add to existing dashboard -->
// Add Protocol Mapping section to dashboard
function createProtocolMappingSection() {
return `
<div class="protocol-mapping-section">
<h3>Protocol Mapping Configuration</h3>
<div class="protocol-selector">
<button class="protocol-btn active" onclick="selectProtocol('modbus')">Modbus</button>
<button class="protocol-btn" onclick="selectProtocol('opcua')">OPC UA</button>
<button class="protocol-btn" onclick="selectProtocol('custom')">Custom</button>
</div>
<div class="mapping-controls">
<button onclick="showMappingForm()">Add Mapping</button>
<button onclick="exportMappings()">Export</button>
</div>
<div id="mapping-grid"></div>
<div id="mapping-form" class="modal hidden">
<!-- Multi-protocol configuration form implementation -->
</div>
</div>
`;
}
Task 4: Implement Protocol Mapping Grid View
Priority: Medium Estimated Effort: 2 days
Implementation Details:
// File: static/dashboard.js
function renderMappingGrid(mappings) {
const grid = document.getElementById('mapping-grid');
grid.innerHTML = `
<table class="mapping-table">
<thead>
<tr>
<th>Protocol</th>
<th>Station</th>
<th>Pump</th>
<th>Data Type</th>
<th>Address</th>
<th>Database Source</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${mappings.map(mapping => `
<tr class="protocol-${mapping.protocol_type}">
<td><span class="protocol-badge">${mapping.protocol_type}</span></td>
<td>${mapping.station_id}</td>
<td>${mapping.pump_id}</td>
<td>${mapping.data_type}</td>
<td>${mapping.protocol_address}</td>
<td>${mapping.db_source}</td>
<td>
<button onclick="editMapping('${mapping.id}')">Edit</button>
<button onclick="deleteMapping('${mapping.id}')">Delete</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
Task 5: Add Protocol-Specific Validation Logic
Priority: High Estimated Effort: 2 days
Implementation Details:
# File: src/dashboard/configuration_manager.py
class ConfigurationManager:
def validate_protocol_mapping(self, mapping: ProtocolMapping) -> Dict[str, Any]:
"""Validate protocol mapping configuration"""
errors = []
warnings = []
# Protocol-specific validation
if mapping.protocol_type == 'modbus_tcp':
# Modbus validation
try:
address = int(mapping.protocol_address)
if not (0 <= address <= 65535):
errors.append("Modbus register address must be between 0 and 65535")
except ValueError:
errors.append("Modbus address must be a valid integer")
# Check for address conflicts
for existing in self.protocol_mappings:
if (existing.id != mapping.id and
existing.protocol_type == 'modbus_tcp' and
existing.protocol_address == mapping.protocol_address):
errors.append(f"Modbus address {mapping.protocol_address} already used by {existing.station_id}/{existing.pump_id}")
elif mapping.protocol_type == 'opcua':
# OPC UA validation
if not mapping.protocol_address.startswith('ns='):
errors.append("OPC UA Node ID must start with 'ns='")
# Check for node conflicts
for existing in self.protocol_mappings:
if (existing.id != mapping.id and
existing.protocol_type == 'opcua' and
existing.protocol_address == mapping.protocol_address):
errors.append(f"OPC UA node {mapping.protocol_address} already used by {existing.station_id}/{existing.pump_id}")
return {
'valid': len(errors) == 0,
'errors': errors,
'warnings': warnings
}
Task 6: Integrate Configuration Manager with Protocol Servers
Priority: High Estimated Effort: 2 days
Implementation Details:
# File: src/protocols/modbus_server.py
class ModbusServer:
def __init__(self, setpoint_manager, configuration_manager):
self.setpoint_manager = setpoint_manager
self.configuration_manager = configuration_manager
async def _update_registers(self):
"""Update registers using configured mappings"""
modbus_mappings = self.configuration_manager.get_protocol_mappings('modbus_tcp')
for mapping in modbus_mappings:
try:
# Get value from database/setpoint manager
value = await self._get_mapped_value(mapping)
# Apply transformations
transformed_value = self._apply_transformations(value, mapping.transformation_rules)
# Write to register
self._write_register(mapping.protocol_address, transformed_value, mapping.modbus_config['register_type'])
except Exception as e:
logger.error(f"Failed to update mapping {mapping.id}: {str(e)}")
# File: src/protocols/opcua_server.py
class OPCUAServer:
def __init__(self, configuration_manager):
self.configuration_manager = configuration_manager
async def update_nodes(self):
"""Update OPC UA nodes using configured mappings"""
opcua_mappings = self.configuration_manager.get_protocol_mappings('opcua')
for mapping in opcua_mappings:
try:
# Get value from database/setpoint manager
value = await self._get_mapped_value(mapping)
# Apply transformations
transformed_value = self._apply_transformations(value, mapping.transformation_rules)
# Write to node
await self._write_node(mapping.protocol_address, transformed_value)
except Exception as e:
logger.error(f"Failed to update mapping {mapping.id}: {str(e)}")
Task 7: Create Database Schema for Protocol Mappings
Priority: Medium Estimated Effort: 1 day
Implementation Details:
-- File: database/schema.sql
CREATE TABLE IF NOT EXISTS protocol_mappings (
id VARCHAR(50) PRIMARY KEY,
protocol_type VARCHAR(20) NOT NULL, -- modbus_tcp, opcua, custom
station_id VARCHAR(50) NOT NULL,
pump_id VARCHAR(50) NOT NULL,
data_type VARCHAR(50) NOT NULL,
protocol_address VARCHAR(200) NOT NULL, -- register address or OPC UA node
db_source VARCHAR(200) NOT NULL,
transformation_rules JSONB,
-- Protocol-specific configurations
modbus_config JSONB,
opcua_config JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (station_id, pump_id) REFERENCES pumps(station_id, pump_id)
);
CREATE INDEX idx_protocol_mappings_type ON protocol_mappings(protocol_type);
CREATE INDEX idx_protocol_mappings_station_pump ON protocol_mappings(station_id, pump_id);
CREATE INDEX idx_protocol_mappings_address ON protocol_mappings(protocol_address);
Task 8: Add Protocol-Specific Unit Tests
Priority: Medium Estimated Effort: 1.5 days
Implementation Details:
# File: tests/unit/test_protocol_mapping.py
class TestProtocolMapping(unittest.TestCase):
def test_modbus_address_conflict_detection(self):
"""Test that Modbus address conflicts are properly detected"""
config_manager = ConfigurationManager()
mapping1 = ProtocolMapping(
id="test1", protocol_type="modbus_tcp", station_id="STATION_001", pump_id="PUMP_001",
data_type="setpoint", protocol_address="40001", db_source="pump_plans.speed_hz"
)
mapping2 = ProtocolMapping(
id="test2", protocol_type="modbus_tcp", station_id="STATION_001", pump_id="PUMP_002",
data_type="setpoint", protocol_address="40001", db_source="pump_plans.speed_hz"
)
config_manager.add_protocol_mapping(mapping1)
result = config_manager.validate_protocol_mapping(mapping2)
self.assertFalse(result['valid'])
self.assertIn("Modbus address 40001 already used", result['errors'][0])
def test_opcua_node_validation(self):
"""Test OPC UA node validation"""
config_manager = ConfigurationManager()
mapping = ProtocolMapping(
id="test1", protocol_type="opcua", station_id="STATION_001", pump_id="PUMP_001",
data_type="setpoint", protocol_address="invalid_node", db_source="pump_plans.speed_hz"
)
result = config_manager.validate_protocol_mapping(mapping)
self.assertFalse(result['valid'])
self.assertIn("OPC UA Node ID must start with 'ns='", result['errors'][0])
Task 9: Add Single Protocol Mapping Tab to Dashboard
Priority: Low Estimated Effort: 0.5 days
Implementation Details:
// File: static/dashboard.js
// Update tab navigation - Add single Protocol Mapping tab
function updateNavigation() {
const tabButtons = document.querySelector('.tab-buttons');
tabButtons.innerHTML += `
<button class="tab-button" onclick="showTab('protocol-mapping')">Protocol Mapping</button>
`;
}
// Add Protocol Mapping tab content
function addProtocolMappingTab() {
const tabContainer = document.querySelector('.tab-container');
tabContainer.innerHTML += `
<!-- Protocol Mapping Tab -->
<div id="protocol-mapping-tab" class="tab-content">
<h2>Protocol Mapping Configuration</h2>
<div class="protocol-selector">
<button class="protocol-btn active" onclick="selectProtocol('modbus')">Modbus</button>
<button class="protocol-btn" onclick="selectProtocol('opcua')">OPC UA</button>
<button class="protocol-btn" onclick="selectProtocol('all')">All Protocols</button>
</div>
<div id="protocol-mapping-content">
<!-- Unified protocol mapping interface will be loaded here -->
</div>
</div>
`;
}
// Protocol switching within the single tab
function selectProtocol(protocol) {
// Update active protocol button
document.querySelectorAll('.protocol-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
// Load protocol-specific content
loadProtocolMappings(protocol);
}
Task 10: Implement Protocol Discovery Features
Priority: Medium Estimated Effort: 2 days
Implementation Details:
# File: src/dashboard/api.py
@dashboard_router.post("/protocol-mappings/modbus/discover")
async def discover_modbus_registers():
"""Auto-discover available Modbus registers"""
try:
# Scan for available registers
discovered_registers = await modbus_client.scan_registers()
return {"success": True, "registers": discovered_registers}
except Exception as e:
logger.error(f"Failed to discover Modbus registers: {str(e)}")
raise HTTPException(status_code=500, detail=f"Discovery failed: {str(e)}")
@dashboard_router.post("/protocol-mappings/opcua/browse")
async def browse_opcua_nodes():
"""Browse OPC UA server for available nodes"""
try:
# Browse OPC UA server
nodes = await opcua_client.browse_nodes()
return {"success": True, "nodes": nodes}
except Exception as e:
logger.error(f"Failed to browse OPC UA nodes: {str(e)}")
raise HTTPException(status_code=500, detail=f"Browse failed: {str(e)}")
🔄 Integration Points
Existing System Integration
- Configuration Manager: Extend existing class with unified protocol mapping support
- Protocol Servers: Inject configuration manager and use configured mappings (Modbus, OPC UA)
- Dashboard API: Add unified protocol mapping endpoints alongside existing configuration endpoints
- Dashboard UI: Add single Protocol Mapping tab with protocol switching
- Database: Add unified table for persistent storage of all protocol mappings
Data Flow Changes
Current: Database → Setpoint Manager → Hardcoded Mapping → Protocol Servers
New: Database → Setpoint Manager → Unified Configurable Mapping → Protocol Servers
↑
Unified Configuration Manager
Dashboard Integration
┌─────────────────────────────────────────────────────────────────┐
│ DASHBOARD NAVIGATION │
├─────────────────────────────────────────────────────────────────┤
│ [Status] [Config] [SCADA] [Signals] [Protocol Mapping] [Logs] │
└─────────────────────────────────────────────────────────────────┘
Within Protocol Mapping Tab:
┌─────────────────────────────────────────────────────────────────┐
│ PROTOCOL MAPPING │
├─────────────────────────────────────────────────────────────────┤
│ [Modbus] [OPC UA] [All Protocols] ← Protocol Selector │
│ │
│ Unified Mapping Grid & Configuration Forms │
└─────────────────────────────────────────────────────────────────┘
🧪 Testing Strategy
Test Scenarios
- Protocol Configuration Validation: Test address conflicts, data type compatibility across protocols
- Integration Testing: Test that configured mappings are applied correctly to all protocol servers
- Protocol-Specific Testing: Test Modbus register mapping and OPC UA node mapping separately
- Performance Testing: Test impact on protocol server performance
Test Data
- Create test mappings for different protocols and scenarios
- Test edge cases (address boundaries, data type conversions, protocol-specific rules)
- Test cross-protocol conflict scenarios
📊 Success Metrics
Functional Requirements
- ✅ Users can configure database-to-protocol mappings through dashboard
- ✅ System uses configured mappings for all supported protocols
- ✅ Protocol-specific validation prevents configuration conflicts
- ✅ Mappings are persisted across application restarts
- ✅ Support for multiple protocols (Modbus, OPC UA) with unified interface
Performance Requirements
- ⏱️ Mapping configuration response time < 500ms
- ⏱️ Protocol server update performance maintained
- 💾 Memory usage increase < 15MB for typical multi-protocol configurations
🚨 Risk Mitigation
Technical Risks
- Performance Impact: Monitor protocol server update times, optimize if needed
- Configuration Errors: Implement comprehensive protocol-specific validation
- Protocol Compatibility: Ensure consistent behavior across different protocols
Implementation Risks
- Scope Creep: Stick to Phase 1 requirements only
- Integration Issues: Test thoroughly with existing protocol servers
- Data Loss: Implement backup/restore for mapping configurations
📅 Estimated Timeline
Total Phase 1 Effort: 18.5 days
| Week | Tasks | Deliverables |
|---|---|---|
| 1 | Tasks 1-3 | Configuration manager, API endpoints, multi-protocol UI |
| 2 | Tasks 4-6 | Grid view, protocol-specific validation, server integration |
| 3 | Tasks 7-10 | Database schema, tests, navigation, discovery features |
🎯 Next Steps After Phase 1
- User Testing: Gather feedback from operators on multi-protocol interface
- Bug Fixing: Address any issues discovered in production
- Phase 2 Planning: Begin design for enhanced features (drag & drop, templates, bulk operations)
This implementation plan provides a detailed roadmap for delivering Phase 1 of the Protocol Mapping feature, supporting multiple industrial protocols with a unified interface. Each task includes specific implementation details and integration points with the existing system.