Update backend manager status, dataset variables, and configuration schemas
- Updated backend manager status with new timestamps and process IDs. - Modified dataset variables JSON to ensure consistent configuration types and added a new symbolic variable. - Enhanced dataset variables schema to enforce configuration type and prevent additional properties. - Adjusted UI schema for dataset variables to improve layout and visibility of fields. - Refactored config manager to handle expanded dataset variables for PLC communication. - Updated PLC data streamer and core streamer to utilize expanded dataset variables. - Simplified DatasetVariableSymbolWidget and SymbolSelectorWidget components, improving toast notifications and layout. - Removed unnecessary symbol expansion logic from Dashboard component. - Updated system state JSON with new last update timestamp and added PlotJuggler path.
This commit is contained in:
parent
81e5ddec57
commit
a0a65f563d
24338
application_events.json
24338
application_events.json
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"timestamp": "2025-08-22T15:14:03.883875",
|
||||
"timestamp": "2025-08-28T10:48:11.316409",
|
||||
"status": "stopped",
|
||||
"restart_count": 0,
|
||||
"last_restart": 0,
|
||||
"backend_pid": 33676,
|
||||
"manager_pid": 25004,
|
||||
"backend_pid": null,
|
||||
"manager_pid": 13520,
|
||||
"details": {}
|
||||
}
|
|
@ -4,8 +4,8 @@
|
|||
"dataset_id": "DAR",
|
||||
"variables": [
|
||||
{
|
||||
"configType": "manual",
|
||||
"area": "DB",
|
||||
"configType": "manual",
|
||||
"db": 1011,
|
||||
"name": "HMI_Instrument.QTM307.PVFiltered",
|
||||
"offset": 1322,
|
||||
|
@ -13,8 +13,8 @@
|
|||
"type": "real"
|
||||
},
|
||||
{
|
||||
"configType": "manual",
|
||||
"area": "DB",
|
||||
"configType": "manual",
|
||||
"db": 1011,
|
||||
"name": "HMI_Instrument.QTM306.PVFiltered",
|
||||
"offset": 1296,
|
||||
|
@ -22,8 +22,8 @@
|
|||
"type": "real"
|
||||
},
|
||||
{
|
||||
"configType": "manual",
|
||||
"area": "DB",
|
||||
"configType": "manual",
|
||||
"db": 1011,
|
||||
"name": "HMI_Instrument.CTS306.PVFiltered",
|
||||
"offset": 1348,
|
||||
|
@ -31,12 +31,17 @@
|
|||
"type": "real"
|
||||
},
|
||||
{
|
||||
"configType": "manual",
|
||||
"area": "PEW",
|
||||
"configType": "manual",
|
||||
"name": "CTS306_PEW",
|
||||
"offset": 256,
|
||||
"streaming": true,
|
||||
"type": "word"
|
||||
},
|
||||
{
|
||||
"configType": "symbol",
|
||||
"streaming": false,
|
||||
"symbol": "AUX Blink_2.0S"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -44,13 +49,14 @@
|
|||
"dataset_id": "Fast",
|
||||
"variables": [
|
||||
{
|
||||
"name": "AUX Blink_2.0S",
|
||||
"configType": "symbol",
|
||||
"streaming": true,
|
||||
"symbol": "AUX Blink_2.0S"
|
||||
},
|
||||
{
|
||||
"area": "M",
|
||||
"bit": 1,
|
||||
"configType": "manual",
|
||||
"name": "M50.1",
|
||||
"offset": 50,
|
||||
"streaming": false,
|
||||
|
@ -59,6 +65,7 @@
|
|||
{
|
||||
"area": "M",
|
||||
"bit": 2,
|
||||
"configType": "manual",
|
||||
"name": "M50.2",
|
||||
"offset": 50,
|
||||
"streaming": false,
|
||||
|
|
|
@ -157,10 +157,11 @@
|
|||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"configType": {
|
||||
"type": "string",
|
||||
"title": "Variable Name",
|
||||
"description": "Human-readable name for the variable (auto-filled from symbol)"
|
||||
"title": "Configuration Type",
|
||||
"enum": ["manual", "symbol"],
|
||||
"default": "manual"
|
||||
},
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
|
@ -177,7 +178,8 @@
|
|||
"required": [
|
||||
"configType",
|
||||
"symbol"
|
||||
]
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
"items": {
|
||||
"ui:order": [
|
||||
"configType",
|
||||
"name",
|
||||
"symbol",
|
||||
"name",
|
||||
"area",
|
||||
"db",
|
||||
"offset",
|
||||
|
@ -39,17 +39,23 @@
|
|||
[
|
||||
{
|
||||
"name": "configType",
|
||||
"width": 3
|
||||
"width": 6
|
||||
},
|
||||
{
|
||||
"name": "streaming",
|
||||
"width": 6
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "symbol",
|
||||
"width": 12
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "name",
|
||||
"width": 6
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"width": 6
|
||||
"width": 12
|
||||
}
|
||||
],
|
||||
[
|
||||
|
@ -71,11 +77,7 @@
|
|||
},
|
||||
{
|
||||
"name": "type",
|
||||
"width": 2
|
||||
},
|
||||
{
|
||||
"name": "streaming",
|
||||
"width": 2
|
||||
"width": 4
|
||||
}
|
||||
]
|
||||
],
|
||||
|
|
|
@ -263,18 +263,21 @@ class ConfigManager:
|
|||
streaming_variables = []
|
||||
|
||||
for var in variables_list:
|
||||
# Handle symbolic variables by expanding them first
|
||||
if var.get("configType") == "symbol":
|
||||
var = self._expand_symbolic_variable(var)
|
||||
if var is None:
|
||||
# Skip if symbol expansion failed
|
||||
continue
|
||||
|
||||
# Keep symbolic variables as they are for configuration storage
|
||||
# They will be expanded when needed for PLC communication
|
||||
var_name = var.get("name")
|
||||
|
||||
# For symbolic variables, use symbol name if no explicit name
|
||||
if var.get("configType") == "symbol" and not var_name:
|
||||
var_name = var.get("symbol")
|
||||
var = var.copy() # Create copy to avoid modifying original
|
||||
var["name"] = var_name
|
||||
|
||||
if not var_name:
|
||||
if self.logger:
|
||||
self.logger.warning(
|
||||
f"Skipping variable without name in dataset {dataset_id}: {var}"
|
||||
f"Skipping variable without name in dataset "
|
||||
f"{dataset_id}: {var}"
|
||||
)
|
||||
continue
|
||||
|
||||
|
@ -537,8 +540,6 @@ class ConfigManager:
|
|||
self.save_configuration()
|
||||
return {"old_config": old_config, "new_config": self.csv_config}
|
||||
|
||||
|
||||
|
||||
def get_csv_file_directory_path(self) -> str:
|
||||
"""Get the directory path for current day's CSV files"""
|
||||
now = datetime.now()
|
||||
|
@ -612,6 +613,39 @@ class ConfigManager:
|
|||
return self.datasets[self.current_dataset_id]
|
||||
return None
|
||||
|
||||
def get_expanded_dataset_variables(self, dataset_id: str):
|
||||
"""Get variables for a dataset with symbolic variables expanded for PLC communication"""
|
||||
if dataset_id not in self.datasets:
|
||||
return {}
|
||||
|
||||
variables = self.datasets[dataset_id].get("variables", {})
|
||||
expanded_variables = {}
|
||||
|
||||
for var_name, var_config in variables.items():
|
||||
if var_config.get("configType") == "symbol":
|
||||
# Expand symbolic variable for PLC communication
|
||||
expanded_var = self._expand_symbolic_variable(var_config)
|
||||
if expanded_var:
|
||||
expanded_variables[var_name] = expanded_var
|
||||
else:
|
||||
# If expansion fails, keep the original symbolic config for counting
|
||||
# but mark it as non-functional for PLC communication
|
||||
fallback_var = var_config.copy()
|
||||
fallback_var["_expansion_failed"] = True
|
||||
expanded_variables[var_name] = fallback_var
|
||||
|
||||
if self.logger:
|
||||
symbol = var_config.get("symbol", "unknown")
|
||||
self.logger.warning(
|
||||
f"Failed to expand symbol '{symbol}' for "
|
||||
f"variable '{var_name}' in dataset '{dataset_id}'"
|
||||
)
|
||||
else:
|
||||
# Keep manual variables as they are
|
||||
expanded_variables[var_name] = var_config
|
||||
|
||||
return expanded_variables
|
||||
|
||||
def get_dataset_variables(self, dataset_id: str):
|
||||
"""Get variables for a specific dataset"""
|
||||
if dataset_id in self.datasets:
|
||||
|
|
|
@ -274,7 +274,7 @@ class PLCDataStreamer:
|
|||
continue
|
||||
|
||||
# Get expected headers based on current configuration
|
||||
dataset_variables = self.config_manager.get_dataset_variables(
|
||||
dataset_variables = self.config_manager.get_expanded_dataset_variables(
|
||||
dataset_id
|
||||
)
|
||||
expected_headers = ["timestamp"] + list(dataset_variables.keys())
|
||||
|
@ -537,7 +537,7 @@ class PLCDataStreamer:
|
|||
self.config_manager.active_datasets
|
||||
), # Convert set to list for JSON
|
||||
"total_variables": sum(
|
||||
len(self.config_manager.get_dataset_variables(dataset_id))
|
||||
len(self.config_manager.get_expanded_dataset_variables(dataset_id))
|
||||
for dataset_id in self.config_manager.datasets.keys()
|
||||
),
|
||||
"streaming_variables_count": sum(
|
||||
|
@ -774,6 +774,10 @@ class PLCDataStreamer:
|
|||
"""Get variables for a specific dataset"""
|
||||
return self.config_manager.get_dataset_variables(dataset_id)
|
||||
|
||||
def get_expanded_dataset_variables(self, dataset_id: str):
|
||||
"""Get variables for a dataset with symbolic variables expanded for PLC communication"""
|
||||
return self.config_manager.get_expanded_dataset_variables(dataset_id)
|
||||
|
||||
def get_recent_events(self, limit: int = 50):
|
||||
"""Get recent events from the log"""
|
||||
return self.event_logger.get_recent_events(limit)
|
||||
|
|
|
@ -252,7 +252,9 @@ class DataStreamer:
|
|||
csv_path = self.get_dataset_csv_file_path(dataset_id)
|
||||
|
||||
# Get current dataset variables and create expected headers
|
||||
dataset_variables = self.config_manager.get_dataset_variables(dataset_id)
|
||||
dataset_variables = self.config_manager.get_expanded_dataset_variables(
|
||||
dataset_id
|
||||
)
|
||||
expected_headers = ["timestamp"] + list(dataset_variables.keys())
|
||||
|
||||
# Check if file exists and validate headers
|
||||
|
@ -356,7 +358,7 @@ class DataStreamer:
|
|||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
||||
|
||||
# Create row with all variables for this dataset
|
||||
dataset_variables = self.config_manager.get_dataset_variables(
|
||||
dataset_variables = self.config_manager.get_expanded_dataset_variables(
|
||||
dataset_id
|
||||
)
|
||||
row = [timestamp]
|
||||
|
@ -417,8 +419,10 @@ class DataStreamer:
|
|||
self.setup_dataset_csv_file(dataset_id)
|
||||
|
||||
if dataset_id in self.dataset_csv_writers:
|
||||
dataset_variables = self.config_manager.get_dataset_variables(
|
||||
dataset_id
|
||||
dataset_variables = (
|
||||
self.config_manager.get_expanded_dataset_variables(
|
||||
dataset_id
|
||||
)
|
||||
)
|
||||
|
||||
for entry in buffer_data:
|
||||
|
@ -562,7 +566,9 @@ class DataStreamer:
|
|||
self.dataset_csv_hours[dataset_id] = datetime.now().hour
|
||||
|
||||
# Write headers with new variable configuration
|
||||
dataset_variables = self.config_manager.get_dataset_variables(dataset_id)
|
||||
dataset_variables = self.config_manager.get_expanded_dataset_variables(
|
||||
dataset_id
|
||||
)
|
||||
if dataset_variables:
|
||||
headers = ["timestamp"] + list(dataset_variables.keys())
|
||||
self.dataset_csv_writers[dataset_id].writerow(headers)
|
||||
|
@ -629,10 +635,14 @@ class DataStreamer:
|
|||
try:
|
||||
# <20> Get dataset configuration to determine reading method
|
||||
dataset_config = self.config_manager.datasets.get(dataset_id, {})
|
||||
use_optimized_reading = dataset_config.get("use_optimized_reading", True) # Default to True
|
||||
use_optimized_reading = dataset_config.get(
|
||||
"use_optimized_reading", True
|
||||
) # Default to True
|
||||
|
||||
# 🚀 NEW: Use batch reading with dataset-specific optimization setting
|
||||
batch_results = self.plc_client.read_variables_batch(variables, use_optimized_reading)
|
||||
batch_results = self.plc_client.read_variables_batch(
|
||||
variables, use_optimized_reading
|
||||
)
|
||||
|
||||
for var_name, value in batch_results.items():
|
||||
if value is not None:
|
||||
|
@ -842,7 +852,7 @@ class DataStreamer:
|
|||
|
||||
try:
|
||||
# 📋 CRITICAL SECTION: PLC READ with timing and error tracking
|
||||
dataset_variables = self.config_manager.get_dataset_variables(
|
||||
dataset_variables = self.config_manager.get_expanded_dataset_variables(
|
||||
dataset_id
|
||||
)
|
||||
variables_count = len(dataset_variables)
|
||||
|
@ -1138,7 +1148,7 @@ class DataStreamer:
|
|||
{
|
||||
"dataset_id": dataset_id,
|
||||
"variables_count": len(
|
||||
self.config_manager.get_dataset_variables(dataset_id)
|
||||
self.config_manager.get_expanded_dataset_variables(dataset_id)
|
||||
),
|
||||
"streaming_count": len(dataset_info["streaming_variables"]),
|
||||
"prefix": dataset_info["prefix"],
|
||||
|
|
|
@ -1,45 +1,18 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import React from 'react'
|
||||
import {
|
||||
Box,
|
||||
VStack,
|
||||
Text,
|
||||
Badge,
|
||||
useToast
|
||||
} from '@chakra-ui/react'
|
||||
import SymbolSelectorWidget from './SymbolSelectorWidget'
|
||||
|
||||
const DatasetVariableSymbolWidget = ({ value, onChange, label, disabled, readonly, required, placeholder, formContext }) => {
|
||||
const [selectedSymbol, setSelectedSymbol] = useState(null)
|
||||
const toast = useToast()
|
||||
|
||||
// Load symbol details when value changes
|
||||
useEffect(() => {
|
||||
if (value && !selectedSymbol) {
|
||||
loadSymbolDetails(value)
|
||||
}
|
||||
}, [value])
|
||||
|
||||
const loadSymbolDetails = async (symbolName) => {
|
||||
try {
|
||||
const response = await fetch('/api/symbols')
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success && data.symbols) {
|
||||
const symbol = data.symbols.find(s => s.name === symbolName)
|
||||
if (symbol) {
|
||||
setSelectedSymbol(symbol)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading symbol details:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSymbolSelect = (symbolName) => {
|
||||
// Update the symbol field
|
||||
onChange(symbolName)
|
||||
|
||||
// Show success message
|
||||
// Show success message only once
|
||||
toast({
|
||||
title: 'Symbol Selected',
|
||||
description: `Selected: ${symbolName}`,
|
||||
|
@ -50,10 +23,7 @@ const DatasetVariableSymbolWidget = ({ value, onChange, label, disabled, readonl
|
|||
}
|
||||
|
||||
const symbolOptions = {
|
||||
onSymbolSelect: (symbol) => {
|
||||
setSelectedSymbol(symbol)
|
||||
handleSymbolSelect(symbol.name)
|
||||
}
|
||||
skipToast: true, // Prevent duplicate toasts
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -69,30 +39,6 @@ const DatasetVariableSymbolWidget = ({ value, onChange, label, disabled, readonl
|
|||
formContext={formContext}
|
||||
options={symbolOptions}
|
||||
/>
|
||||
|
||||
{selectedSymbol && (
|
||||
<Box mt={2} p={2} border="1px" borderColor="green.200" borderRadius="md" bg="green.50">
|
||||
<Text fontSize="xs" color="green.700" fontWeight="medium">
|
||||
Symbol Information:
|
||||
</Text>
|
||||
<VStack align="start" spacing={1} mt={1}>
|
||||
<Text fontSize="xs" color="green.600">
|
||||
📝 Name: {selectedSymbol.description || selectedSymbol.name}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="green.600" fontFamily="mono">
|
||||
📍 Address: {selectedSymbol.plc_address}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="green.600">
|
||||
🔧 Area: {selectedSymbol.area?.toUpperCase()}, Offset: {selectedSymbol.offset}
|
||||
{selectedSymbol.db && `, DB: ${selectedSymbol.db}`}
|
||||
{selectedSymbol.bit !== null && selectedSymbol.bit !== undefined && `, Bit: ${selectedSymbol.bit}`}
|
||||
</Text>
|
||||
<Badge colorScheme="green" fontSize="xs">
|
||||
{selectedSymbol.data_type}
|
||||
</Badge>
|
||||
</VStack>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ import {
|
|||
ModalFooter,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
useDisclosure
|
||||
useDisclosure,
|
||||
SimpleGrid,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react'
|
||||
import { FiSearch, FiX, FiList, FiInfo } from 'react-icons/fi'
|
||||
|
||||
|
@ -33,6 +35,10 @@ const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, requ
|
|||
const [selectedSymbol, setSelectedSymbol] = useState(null)
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const toast = useToast()
|
||||
|
||||
// Theme values - must be called at component level, not conditionally
|
||||
const nameColor = useColorModeValue("blue.700", "blue.300")
|
||||
const addressColor = useColorModeValue("gray.600", "gray.400")
|
||||
|
||||
// Find the selected symbol from the loaded symbols
|
||||
useEffect(() => {
|
||||
|
@ -100,26 +106,32 @@ const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, requ
|
|||
|
||||
onClose()
|
||||
|
||||
toast({
|
||||
title: 'Symbol Selected',
|
||||
description: `Selected: ${symbol.name}`,
|
||||
status: 'success',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
})
|
||||
// Only show toast if not being called from a parent widget
|
||||
if (!options || !options.skipToast) {
|
||||
toast({
|
||||
title: 'Symbol Selected',
|
||||
description: `Selected: ${symbol.name}`,
|
||||
status: 'success',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleClearSelection = () => {
|
||||
setSelectedSymbol(null)
|
||||
onChange('')
|
||||
|
||||
toast({
|
||||
title: 'Selection Cleared',
|
||||
description: 'Symbol selection has been cleared',
|
||||
status: 'info',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
})
|
||||
// Only show toast if not being called from a parent widget
|
||||
if (!options || !options.skipToast) {
|
||||
toast({
|
||||
title: 'Selection Cleared',
|
||||
description: 'Symbol selection has been cleared',
|
||||
status: 'info',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const SymbolCard = ({ symbol, onClick }) => (
|
||||
|
@ -132,28 +144,31 @@ const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, requ
|
|||
_hover={{ bg: 'gray.50', borderColor: 'blue.300' }}
|
||||
_active={{ bg: 'gray.100' }}
|
||||
onClick={() => onClick(symbol)}
|
||||
minH="140px"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<VStack align="start" spacing={1}>
|
||||
<VStack align="start" spacing={1} flex={1}>
|
||||
<HStack justify="space-between" w="full">
|
||||
<Text fontWeight="semibold" fontSize="sm" color="blue.600">
|
||||
<Text fontWeight="semibold" fontSize="sm" color="blue.600" noOfLines={1}>
|
||||
{symbol.name}
|
||||
</Text>
|
||||
<Badge colorScheme="gray" fontSize="xs">
|
||||
<Badge colorScheme="gray" fontSize="xs" flexShrink={0}>
|
||||
{symbol.data_type}
|
||||
</Badge>
|
||||
</HStack>
|
||||
|
||||
<Text fontSize="xs" color="gray.600" fontFamily="mono">
|
||||
<Text fontSize="xs" color="gray.600" fontFamily="mono" noOfLines={1}>
|
||||
{symbol.plc_address}
|
||||
</Text>
|
||||
|
||||
{symbol.description && (
|
||||
<Text fontSize="xs" color="gray.500" noOfLines={2}>
|
||||
<Text fontSize="xs" color="gray.500" noOfLines={2} flex={1}>
|
||||
{symbol.description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<HStack spacing={2} fontSize="xs">
|
||||
<HStack spacing={1} fontSize="xs" flexWrap="wrap" mt="auto">
|
||||
<Badge colorScheme="blue" variant="subtle">
|
||||
{symbol.area?.toUpperCase()}
|
||||
</Badge>
|
||||
|
@ -219,53 +234,35 @@ const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, requ
|
|||
</HStack>
|
||||
|
||||
{selectedSymbol && (
|
||||
<Box
|
||||
border="1px"
|
||||
borderColor="blue.200"
|
||||
borderRadius="md"
|
||||
p={3}
|
||||
bg="blue.50"
|
||||
>
|
||||
<VStack align="start" spacing={1}>
|
||||
<HStack justify="space-between" w="full">
|
||||
<Text fontWeight="semibold" fontSize="sm" color="blue.700">
|
||||
{selectedSymbol.name}
|
||||
</Text>
|
||||
<Badge colorScheme="blue">
|
||||
{selectedSymbol.data_type}
|
||||
</Badge>
|
||||
</HStack>
|
||||
|
||||
<Text fontSize="xs" color="blue.600" fontFamily="mono">
|
||||
{selectedSymbol.plc_address}
|
||||
<VStack spacing={1} align="start">
|
||||
<HStack justify="space-between" w="full">
|
||||
<Text fontWeight="semibold" fontSize="sm" color={nameColor}>
|
||||
📝 {selectedSymbol.description || selectedSymbol.name}
|
||||
</Text>
|
||||
|
||||
{selectedSymbol.description && (
|
||||
<Text fontSize="xs" color="blue.500">
|
||||
{selectedSymbol.description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<HStack spacing={2} fontSize="xs">
|
||||
<Badge colorScheme="blue">
|
||||
<Badge colorScheme="blue" variant="subtle">
|
||||
{selectedSymbol.data_type}
|
||||
</Badge>
|
||||
</HStack>
|
||||
|
||||
<HStack justify="space-between" w="full">
|
||||
<Text fontSize="xs" color={addressColor} fontFamily="mono">
|
||||
📍 {selectedSymbol.plc_address}
|
||||
</Text>
|
||||
<HStack spacing={1} fontSize="xs">
|
||||
<Badge colorScheme="green" variant="subtle">
|
||||
{selectedSymbol.area?.toUpperCase()}
|
||||
</Badge>
|
||||
{selectedSymbol.db && (
|
||||
<Badge colorScheme="green">
|
||||
DB{selectedSymbol.db}
|
||||
</Badge>
|
||||
)}
|
||||
<Badge colorScheme="purple">
|
||||
<Badge colorScheme="purple" variant="subtle">
|
||||
@{selectedSymbol.offset}
|
||||
</Badge>
|
||||
{selectedSymbol.bit !== null && selectedSymbol.bit !== undefined && (
|
||||
<Badge colorScheme="orange">
|
||||
<Badge colorScheme="orange" variant="subtle">
|
||||
.{selectedSymbol.bit}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{symbols.length === 0 && !isLoading && (
|
||||
|
@ -276,9 +273,9 @@ const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, requ
|
|||
</VStack>
|
||||
|
||||
{/* Symbol Selection Modal */}
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl" scrollBehavior="inside">
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="6xl" scrollBehavior="inside">
|
||||
<ModalOverlay />
|
||||
<ModalContent maxH="80vh">
|
||||
<ModalContent maxH="85vh">
|
||||
<ModalHeader>
|
||||
<VStack align="start" spacing={2}>
|
||||
<Text>Select PLC Symbol</Text>
|
||||
|
@ -322,13 +319,15 @@ const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, requ
|
|||
{searchQuery && ` for "${searchQuery}"`}
|
||||
</Text>
|
||||
|
||||
{filteredSymbols.map((symbol, index) => (
|
||||
<SymbolCard
|
||||
key={`${symbol.name}-${index}`}
|
||||
symbol={symbol}
|
||||
onClick={handleSymbolSelect}
|
||||
/>
|
||||
))}
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={3}>
|
||||
{filteredSymbols.map((symbol, index) => (
|
||||
<SymbolCard
|
||||
key={`${symbol.name}-${index}`}
|
||||
symbol={symbol}
|
||||
onClick={handleSymbolSelect}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
{searchQuery && filteredSymbols.length >= 50 && (
|
||||
<Text fontSize="sm" color="orange.500" textAlign="center" fontStyle="italic">
|
||||
|
|
|
@ -1505,99 +1505,10 @@ function DatasetManager() {
|
|||
)
|
||||
}
|
||||
|
||||
// Function to expand symbol data using backend API
|
||||
const expandSymbolToManualConfig = async (symbolName, currentVariable = {}) => {
|
||||
try {
|
||||
// Create a temporary variable array with just this symbol
|
||||
const tempVariables = [{
|
||||
symbol: symbolName,
|
||||
streaming: currentVariable.streaming || false
|
||||
}]
|
||||
|
||||
// Call backend API to process the symbol
|
||||
const response = await fetch('/api/symbols/process-variables', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
variables: tempVariables
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success && result.processed_variables.length > 0) {
|
||||
const processedVar = result.processed_variables[0]
|
||||
|
||||
// Build the configuration object, only including relevant fields
|
||||
const config = {
|
||||
name: processedVar.name || symbolName,
|
||||
area: processedVar.area || "DB",
|
||||
offset: processedVar.offset !== undefined && processedVar.offset !== null ? processedVar.offset : 0,
|
||||
type: processedVar.type || "real",
|
||||
streaming: currentVariable.streaming || false
|
||||
}
|
||||
|
||||
// Only include db field if it's actually present and area requires it
|
||||
if (processedVar.db !== undefined && processedVar.db !== null) {
|
||||
config.db = processedVar.db
|
||||
} else if (config.area === "DB") {
|
||||
// Default to 1 only for DB area if no DB number was provided
|
||||
config.db = 1
|
||||
}
|
||||
|
||||
// Only include bit field if it's actually present
|
||||
if (processedVar.bit !== undefined && processedVar.bit !== null) {
|
||||
config.bit = processedVar.bit
|
||||
} else {
|
||||
// Default to 0 for bit position when not specified
|
||||
config.bit = 0
|
||||
}
|
||||
|
||||
return config
|
||||
} else {
|
||||
// If backend processing failed, return basic defaults
|
||||
const fallbackConfig = {
|
||||
name: currentVariable.name || symbolName,
|
||||
area: "DB", // Default to DB area
|
||||
offset: 0,
|
||||
type: "real",
|
||||
bit: 0,
|
||||
streaming: currentVariable.streaming || false
|
||||
}
|
||||
|
||||
// Only add db field for DB area
|
||||
if (fallbackConfig.area === "DB") {
|
||||
fallbackConfig.db = 1
|
||||
}
|
||||
|
||||
return fallbackConfig
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error expanding symbol:', error)
|
||||
// Return basic defaults on error
|
||||
const errorConfig = {
|
||||
name: currentVariable.name || symbolName,
|
||||
area: "DB", // Default to DB area
|
||||
offset: 0,
|
||||
type: "real",
|
||||
bit: 0,
|
||||
streaming: currentVariable.streaming || false
|
||||
}
|
||||
|
||||
// Only add db field for DB area
|
||||
if (errorConfig.area === "DB") {
|
||||
errorConfig.db = 1
|
||||
}
|
||||
|
||||
return errorConfig
|
||||
}
|
||||
}
|
||||
|
||||
// Standard form change handler for external schema compatibility
|
||||
const handleFormChange = ({ formData }) => {
|
||||
// Direct update without special processing for external schema compatibility
|
||||
// For symbol-based configuration, don't auto-expand to manual fields
|
||||
// The schema should handle field visibility based on configType
|
||||
updateSelectedDatasetVariables(formData)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,5 +7,6 @@
|
|||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-27T15:19:56.923648"
|
||||
"last_update": "2025-08-28T11:31:29.749311",
|
||||
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
|
||||
}
|
Loading…
Reference in New Issue