Se amplió el soporte para tipos de datos y áreas de memoria en la aplicación, incluyendo MW (Memory Words), PEW (Process Input Words), PAW (Process Output Words) y direccionamiento de bits individuales. Se mejoró la validación de configuraciones de variables y se implementó un sistema de edición de variables con interfaz modal. Además, se integraron nuevas funcionalidades en la API para la gestión de variables, permitiendo una experiencia de usuario más fluida y completa.
This commit is contained in:
parent
5e3b1ae76e
commit
c0d7cbc91a
|
@ -4,6 +4,108 @@
|
|||
|
||||
### Latest Modifications (Current Session)
|
||||
|
||||
#### Expanded Data Types and Memory Areas Support
|
||||
**Decision**: Extended the application to support MW (Memory Words), PEW (Process Input Words), PAW (Process Output Words), and individual bit addressing (E5.1, A3.7, M10.0) plus additional Siemens PLC data types.
|
||||
|
||||
**Rationale**: The original implementation was limited to Data Blocks (DB) only, which restricted access to other important PLC memory areas commonly used in industrial applications. MW (Markers/Memory), PEW (Process Inputs), and PAW (Process Outputs) are essential for monitoring peripheral I/O and internal PLC memory, providing comprehensive visibility into the complete PLC system state.
|
||||
|
||||
**Implementation**:
|
||||
|
||||
**New Memory Areas Supported**:
|
||||
- **MW/M (Memory Words/Markers)**: Internal PLC memory for flags, intermediate calculations, and program logic
|
||||
- **PEW/PE (Process Input Words)**: Direct access to analog and digital input peripherals
|
||||
- **PAW/PA (Process Output Words)**: Direct access to analog and digital output peripherals
|
||||
- **DB (Data Blocks)**: Existing support maintained for backward compatibility
|
||||
|
||||
**Additional Data Types Added**:
|
||||
- **word**: 16-bit unsigned integer (0-65535)
|
||||
- **byte**: 8-bit unsigned integer (0-255)
|
||||
- **uint**: 16-bit unsigned integer (same as word, alternative naming)
|
||||
- **udint**: 32-bit unsigned integer (0-4294967295)
|
||||
- **sint**: 8-bit signed integer (-128 to 127)
|
||||
- **usint**: 8-bit unsigned integer (same as byte, alternative naming)
|
||||
|
||||
**Technical Architecture Changes**:
|
||||
|
||||
**Enhanced Variable Configuration**:
|
||||
- Added `area` field to variable configuration with validation for supported area types
|
||||
- Modified `add_variable()` method signature to include area parameter: `add_variable(name, area, db, offset, var_type)`
|
||||
- Backward compatibility maintained for existing DB-based configurations
|
||||
- DB parameter now optional and only required for DB area type
|
||||
|
||||
**Smart Area Detection**:
|
||||
- Automatic area type validation with descriptive error messages
|
||||
- Support for both short and long area names (e.g., "mw"/"m", "pew"/"pe", "paw"/"pa")
|
||||
- Case-insensitive area specification for user convenience
|
||||
|
||||
**snap7 Library Integration**:
|
||||
- Utilized snap7's dedicated functions for optimal performance:
|
||||
- `mb_read()` for Memory/Markers access
|
||||
- `eb_read()` for Process Input access
|
||||
- `ab_read()` for Process Output access
|
||||
- `db_read()` for Data Block access (existing)
|
||||
- Each area uses appropriate snap7 function rather than generic `read_area()` for better performance
|
||||
|
||||
**Enhanced Variable Display**:
|
||||
- Dynamic area description generation for logging and display
|
||||
- Format examples: "MW100", "PEW256", "PAW64", "DB1.20"
|
||||
- Clear identification of memory area in event logs and status displays
|
||||
|
||||
**Individual Bit Addressing Support**:
|
||||
- Added support for individual bit monitoring using Siemens standard notation
|
||||
- **E (Process Input Bits)**: E5.1 = Input byte 5, bit 1 (sensors, limit switches)
|
||||
- **A (Process Output Bits)**: A3.7 = Output byte 3, bit 7 (actuators, indicator lamps)
|
||||
- **MB (Memory Bits)**: M10.0 = Memory byte 10, bit 0 (internal flags, state variables)
|
||||
- Uses `snap7.util.get_bool()` for reliable bit extraction from byte arrays
|
||||
- Web interface automatically restricts data type to BOOL for bit areas
|
||||
- Dynamic bit position selector (0-7) appears only for bit areas
|
||||
- Format examples: "E5.1", "A3.7", "M10.0"
|
||||
|
||||
**Variable Editing Functionality**:
|
||||
- Added comprehensive variable editing system with modal interface
|
||||
- **Edit Button**: ✏️ Edit button added alongside Remove button in variables table
|
||||
- **Modal Form**: Professional modal dialog with form validation and dynamic field visibility
|
||||
- **API Support**: GET endpoint for fetching variable configuration, PUT endpoint for updates
|
||||
- **Data Preservation**: Maintains streaming state when variables are modified
|
||||
- **Name Changes**: Supports changing variable names with duplicate validation
|
||||
- **Field Validation**: Dynamic UI that adapts to memory area type (DB fields, bit selectors)
|
||||
- **Seamless UX**: Modal closes on successful update with automatic page refresh
|
||||
|
||||
**API Enhancements**:
|
||||
- Updated REST API validation to include all new data types and areas
|
||||
- Comprehensive input validation with descriptive error messages
|
||||
- Support for all area types in variable addition endpoint
|
||||
|
||||
**Error Handling**:
|
||||
- Robust validation for unsupported area types with clear error messages
|
||||
- Data type validation against complete supported type list
|
||||
- Graceful fallback for invalid configurations
|
||||
|
||||
**Industrial Benefits**:
|
||||
- **Comprehensive I/O Monitoring**: Direct access to process inputs and outputs without requiring DB mapping
|
||||
- **Memory Diagnostics**: Ability to monitor internal PLC flags and calculation results
|
||||
- **Flexible Data Types**: Support for various integer sizes optimizes memory usage and precision
|
||||
- **Complete System Visibility**: Monitor entire PLC memory space including peripherals and internal state
|
||||
|
||||
**Configuration Examples**:
|
||||
```
|
||||
# Memory Word (internal flags)
|
||||
Area: MW, Offset: 100, Type: word
|
||||
|
||||
# Process Input (temperature sensor)
|
||||
Area: PEW, Offset: 256, Type: int
|
||||
|
||||
# Process Output (valve control)
|
||||
Area: PAW, Offset: 64, Type: word
|
||||
|
||||
# Data Block (recipe data)
|
||||
Area: DB, DB: 1, Offset: 20, Type: real
|
||||
```
|
||||
|
||||
**Migration Support**: Existing configurations automatically default to "db" area type, ensuring seamless upgrade without configuration loss.
|
||||
|
||||
**Impact**: Technicians and engineers now have complete access to all PLC memory areas, enabling comprehensive monitoring of inputs, outputs, internal memory, and data blocks from a single interface. This eliminates the need for multiple monitoring tools and provides complete system visibility for troubleshooting and process optimization.
|
||||
|
||||
#### Persistent Application Events Log
|
||||
**Decision**: Implemented comprehensive event logging system with persistent storage and real-time web interface.
|
||||
|
||||
|
|
|
@ -251,8 +251,271 @@
|
|||
"slot": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:02:11.949781",
|
||||
"level": "info",
|
||||
"event_type": "Application started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:02:11.964986",
|
||||
"level": "info",
|
||||
"event_type": "plc_connection",
|
||||
"message": "Successfully connected to PLC 10.1.33.11",
|
||||
"details": {
|
||||
"ip": "10.1.33.11",
|
||||
"rack": 0,
|
||||
"slot": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:02:11.966271",
|
||||
"level": "info",
|
||||
"event_type": "csv_started",
|
||||
"message": "CSV recording started for 4 variables",
|
||||
"details": {
|
||||
"variables_count": 4,
|
||||
"output_directory": "records\\17-07-2025"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:02:11.967664",
|
||||
"level": "info",
|
||||
"event_type": "streaming_started",
|
||||
"message": "Streaming started with 4 variables",
|
||||
"details": {
|
||||
"variables_count": 4,
|
||||
"streaming_variables_count": 4,
|
||||
"sampling_interval": 0.1,
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:08:42.495109",
|
||||
"level": "info",
|
||||
"event_type": "Application started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:08:42.524816",
|
||||
"level": "info",
|
||||
"event_type": "plc_connection",
|
||||
"message": "Successfully connected to PLC 10.1.33.11",
|
||||
"details": {
|
||||
"ip": "10.1.33.11",
|
||||
"rack": 0,
|
||||
"slot": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:08:42.527387",
|
||||
"level": "info",
|
||||
"event_type": "csv_started",
|
||||
"message": "CSV recording started for 4 variables",
|
||||
"details": {
|
||||
"variables_count": 4,
|
||||
"output_directory": "records\\17-07-2025"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:08:42.529631",
|
||||
"level": "info",
|
||||
"event_type": "streaming_started",
|
||||
"message": "Streaming started with 4 variables",
|
||||
"details": {
|
||||
"variables_count": 4,
|
||||
"streaming_variables_count": 4,
|
||||
"sampling_interval": 0.1,
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:15:14.523187",
|
||||
"level": "info",
|
||||
"event_type": "Application started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:15:14.534443",
|
||||
"level": "info",
|
||||
"event_type": "plc_connection",
|
||||
"message": "Successfully connected to PLC 10.1.33.11",
|
||||
"details": {
|
||||
"ip": "10.1.33.11",
|
||||
"rack": 0,
|
||||
"slot": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:15:14.536431",
|
||||
"level": "info",
|
||||
"event_type": "csv_started",
|
||||
"message": "CSV recording started for 4 variables",
|
||||
"details": {
|
||||
"variables_count": 4,
|
||||
"output_directory": "records\\17-07-2025"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:15:14.538424",
|
||||
"level": "info",
|
||||
"event_type": "streaming_started",
|
||||
"message": "Streaming started with 4 variables",
|
||||
"details": {
|
||||
"variables_count": 4,
|
||||
"streaming_variables_count": 4,
|
||||
"sampling_interval": 0.1,
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:15:43.961033",
|
||||
"level": "info",
|
||||
"event_type": "variable_added",
|
||||
"message": "Variable added: PEW302 -> PEW302 (real)",
|
||||
"details": {
|
||||
"name": "PEW302",
|
||||
"area": "pew",
|
||||
"db": null,
|
||||
"offset": 302,
|
||||
"bit": null,
|
||||
"type": "real",
|
||||
"total_variables": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:15:43.965019",
|
||||
"level": "info",
|
||||
"event_type": "csv_file_created",
|
||||
"message": "New CSV file created after variable modification: _16_15_43.csv",
|
||||
"details": {
|
||||
"file_path": "records\\17-07-2025\\_16_15_43.csv",
|
||||
"variables_count": 5,
|
||||
"reason": "variable_modification"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:16:05.447969",
|
||||
"level": "info",
|
||||
"event_type": "variable_removed",
|
||||
"message": "Variable removed: PEW302",
|
||||
"details": {
|
||||
"name": "PEW302",
|
||||
"removed_config": {
|
||||
"area": "pew",
|
||||
"offset": 302,
|
||||
"type": "real",
|
||||
"streaming": false
|
||||
},
|
||||
"total_variables": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:16:05.452456",
|
||||
"level": "info",
|
||||
"event_type": "csv_file_created",
|
||||
"message": "New CSV file created after variable modification: _16_16_05.csv",
|
||||
"details": {
|
||||
"file_path": "records\\17-07-2025\\_16_16_05.csv",
|
||||
"variables_count": 4,
|
||||
"reason": "variable_modification"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:16:05.460154",
|
||||
"level": "error",
|
||||
"event_type": "streaming_error",
|
||||
"message": "Error in streaming loop: dictionary changed size during iteration",
|
||||
"details": {
|
||||
"error": "dictionary changed size during iteration",
|
||||
"consecutive_errors": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:16:21.389136",
|
||||
"level": "info",
|
||||
"event_type": "variable_added",
|
||||
"message": "Variable added: PEW302 -> PEW302 (word)",
|
||||
"details": {
|
||||
"name": "PEW302",
|
||||
"area": "pew",
|
||||
"db": null,
|
||||
"offset": 302,
|
||||
"bit": null,
|
||||
"type": "word",
|
||||
"total_variables": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:16:21.395123",
|
||||
"level": "info",
|
||||
"event_type": "csv_file_created",
|
||||
"message": "New CSV file created after variable modification: _16_16_21.csv",
|
||||
"details": {
|
||||
"file_path": "records\\17-07-2025\\_16_16_21.csv",
|
||||
"variables_count": 5,
|
||||
"reason": "variable_modification"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:16:21.400619",
|
||||
"level": "error",
|
||||
"event_type": "streaming_error",
|
||||
"message": "Error in streaming loop: dictionary changed size during iteration",
|
||||
"details": {
|
||||
"error": "dictionary changed size during iteration",
|
||||
"consecutive_errors": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:22:46.797675",
|
||||
"level": "info",
|
||||
"event_type": "Application started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:22:46.830234",
|
||||
"level": "info",
|
||||
"event_type": "plc_connection",
|
||||
"message": "Successfully connected to PLC 10.1.33.11",
|
||||
"details": {
|
||||
"ip": "10.1.33.11",
|
||||
"rack": 0,
|
||||
"slot": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:22:46.832226",
|
||||
"level": "info",
|
||||
"event_type": "csv_started",
|
||||
"message": "CSV recording started for 5 variables",
|
||||
"details": {
|
||||
"variables_count": 5,
|
||||
"output_directory": "records\\17-07-2025"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T16:22:46.834244",
|
||||
"level": "info",
|
||||
"event_type": "streaming_started",
|
||||
"message": "Streaming started with 5 variables",
|
||||
"details": {
|
||||
"variables_count": 5,
|
||||
"streaming_variables_count": 5,
|
||||
"sampling_interval": 0.1,
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870
|
||||
}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-07-17T15:43:38.917840",
|
||||
"total_entries": 22
|
||||
"last_updated": "2025-07-17T16:22:46.834244",
|
||||
"total_entries": 46
|
||||
}
|
437
main.py
437
main.py
|
@ -8,6 +8,7 @@ from flask import (
|
|||
send_from_directory,
|
||||
)
|
||||
import snap7
|
||||
import snap7.util
|
||||
import json
|
||||
import socket
|
||||
import time
|
||||
|
@ -350,27 +351,95 @@ class PLCDataStreamer:
|
|||
config_details,
|
||||
)
|
||||
|
||||
def add_variable(self, name: str, db: int, offset: int, var_type: str):
|
||||
def add_variable(
|
||||
self, name: str, area: str, db: int, offset: int, var_type: str, bit: int = None
|
||||
):
|
||||
"""Add a variable for polling"""
|
||||
self.variables[name] = {
|
||||
"db": db,
|
||||
area = area.lower()
|
||||
|
||||
# Validate area type - ahora incluye áreas de bits individuales
|
||||
if area not in ["db", "mw", "m", "pew", "pe", "paw", "pa", "e", "a", "mb"]:
|
||||
raise ValueError(
|
||||
f"Unsupported area type: {area}. Supported: db, mw, m, pew, pe, paw, pa, e, a, mb"
|
||||
)
|
||||
|
||||
# Validate data type
|
||||
valid_types = [
|
||||
"real",
|
||||
"int",
|
||||
"bool",
|
||||
"dint",
|
||||
"word",
|
||||
"byte",
|
||||
"uint",
|
||||
"udint",
|
||||
"sint",
|
||||
"usint",
|
||||
]
|
||||
if var_type not in valid_types:
|
||||
raise ValueError(
|
||||
f"Invalid data type: {var_type}. Supported: {', '.join(valid_types)}"
|
||||
)
|
||||
|
||||
# Para áreas de bits individuales, el tipo debe ser bool y bit debe estar especificado
|
||||
if area in ["e", "a", "mb"] and var_type != "bool":
|
||||
raise ValueError(f"For bit areas ({area}), data type must be 'bool'")
|
||||
|
||||
if area in ["e", "a", "mb"] and bit is None:
|
||||
raise ValueError(
|
||||
f"For bit areas ({area}), bit position must be specified (0-7)"
|
||||
)
|
||||
|
||||
if bit is not None and (bit < 0 or bit > 7):
|
||||
raise ValueError("Bit position must be between 0 and 7")
|
||||
|
||||
# Create variable configuration
|
||||
var_config = {
|
||||
"area": area,
|
||||
"offset": offset,
|
||||
"type": var_type,
|
||||
"streaming": False,
|
||||
}
|
||||
|
||||
# Add DB number only for DB area
|
||||
if area == "db":
|
||||
var_config["db"] = db
|
||||
|
||||
# Add bit position for bit areas
|
||||
if area in ["e", "a", "mb"]:
|
||||
var_config["bit"] = bit
|
||||
|
||||
self.variables[name] = var_config
|
||||
self.save_variables()
|
||||
|
||||
variable_details = {
|
||||
"name": name,
|
||||
"db": db,
|
||||
"area": area,
|
||||
"db": db if area == "db" else None,
|
||||
"offset": offset,
|
||||
"bit": bit,
|
||||
"type": var_type,
|
||||
"total_variables": len(self.variables),
|
||||
}
|
||||
|
||||
# Updated area description to include bit addresses
|
||||
area_description = {
|
||||
"db": f"DB{db}.{offset}",
|
||||
"mw": f"MW{offset}",
|
||||
"m": f"M{offset}",
|
||||
"pew": f"PEW{offset}",
|
||||
"pe": f"PE{offset}",
|
||||
"paw": f"PAW{offset}",
|
||||
"pa": f"PA{offset}",
|
||||
"e": f"E{offset}.{bit}",
|
||||
"a": f"A{offset}.{bit}",
|
||||
"mb": f"M{offset}.{bit}",
|
||||
}
|
||||
|
||||
self.log_event(
|
||||
"info",
|
||||
"variable_added",
|
||||
f"Variable added: {name} -> DB{db}.{offset} ({var_type})",
|
||||
f"Variable added: {name} -> {area_description[area]} ({var_type})",
|
||||
variable_details,
|
||||
)
|
||||
self.create_new_csv_file_for_variable_modification()
|
||||
|
@ -681,23 +750,181 @@ class PLCDataStreamer:
|
|||
def read_variable(self, var_config: Dict[str, Any]) -> Any:
|
||||
"""Read a specific variable from the PLC"""
|
||||
try:
|
||||
db = var_config["db"]
|
||||
area_type = var_config.get("area", "db").lower()
|
||||
offset = var_config["offset"]
|
||||
var_type = var_config["type"]
|
||||
bit = var_config.get("bit") # Extract bit position for bit areas
|
||||
|
||||
if area_type == "db":
|
||||
# Data Block access (existing functionality)
|
||||
db = var_config["db"]
|
||||
if var_type == "real":
|
||||
raw_data = self.plc.db_read(db, offset, 4)
|
||||
value = struct.unpack(">f", raw_data)[0]
|
||||
elif var_type == "int":
|
||||
raw_data = self.plc.db_read(db, offset, 2)
|
||||
value = struct.unpack(">h", raw_data)[0]
|
||||
elif var_type == "bool":
|
||||
raw_data = self.plc.db_read(db, offset, 1)
|
||||
value = bool(raw_data[0] & 0x01)
|
||||
elif var_type == "dint":
|
||||
raw_data = self.plc.db_read(db, offset, 4)
|
||||
value = struct.unpack(">l", raw_data)[0]
|
||||
elif var_type == "word":
|
||||
raw_data = self.plc.db_read(db, offset, 2)
|
||||
value = struct.unpack(">H", raw_data)[0]
|
||||
elif var_type == "byte":
|
||||
raw_data = self.plc.db_read(db, offset, 1)
|
||||
value = struct.unpack(">B", raw_data)[0]
|
||||
elif var_type == "uint":
|
||||
raw_data = self.plc.db_read(db, offset, 2)
|
||||
value = struct.unpack(">H", raw_data)[0]
|
||||
elif var_type == "udint":
|
||||
raw_data = self.plc.db_read(db, offset, 4)
|
||||
value = struct.unpack(">L", raw_data)[0]
|
||||
elif var_type == "sint":
|
||||
raw_data = self.plc.db_read(db, offset, 1)
|
||||
value = struct.unpack(">b", raw_data)[0]
|
||||
elif var_type == "usint":
|
||||
raw_data = self.plc.db_read(db, offset, 1)
|
||||
value = struct.unpack(">B", raw_data)[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
elif area_type == "mw" or area_type == "m":
|
||||
# Memory Words / Markers access
|
||||
if var_type == "real":
|
||||
raw_data = self.plc.mb_read(offset, 4)
|
||||
value = struct.unpack(">f", raw_data)[0]
|
||||
elif var_type == "int":
|
||||
raw_data = self.plc.mb_read(offset, 2)
|
||||
value = struct.unpack(">h", raw_data)[0]
|
||||
elif var_type == "bool":
|
||||
raw_data = self.plc.mb_read(offset, 1)
|
||||
value = bool(raw_data[0] & 0x01)
|
||||
elif var_type == "dint":
|
||||
raw_data = self.plc.mb_read(offset, 4)
|
||||
value = struct.unpack(">l", raw_data)[0]
|
||||
elif var_type == "word":
|
||||
raw_data = self.plc.mb_read(offset, 2)
|
||||
value = struct.unpack(">H", raw_data)[0]
|
||||
elif var_type == "byte":
|
||||
raw_data = self.plc.mb_read(offset, 1)
|
||||
value = struct.unpack(">B", raw_data)[0]
|
||||
elif var_type == "uint":
|
||||
raw_data = self.plc.mb_read(offset, 2)
|
||||
value = struct.unpack(">H", raw_data)[0]
|
||||
elif var_type == "udint":
|
||||
raw_data = self.plc.mb_read(offset, 4)
|
||||
value = struct.unpack(">L", raw_data)[0]
|
||||
elif var_type == "sint":
|
||||
raw_data = self.plc.mb_read(offset, 1)
|
||||
value = struct.unpack(">b", raw_data)[0]
|
||||
elif var_type == "usint":
|
||||
raw_data = self.plc.mb_read(offset, 1)
|
||||
value = struct.unpack(">B", raw_data)[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
elif area_type == "pew" or area_type == "pe":
|
||||
# Process Input Words access
|
||||
if var_type == "real":
|
||||
raw_data = self.plc.eb_read(offset, 4)
|
||||
value = struct.unpack(">f", raw_data)[0]
|
||||
elif var_type == "int":
|
||||
raw_data = self.plc.eb_read(offset, 2)
|
||||
value = struct.unpack(">h", raw_data)[0]
|
||||
elif var_type == "bool":
|
||||
raw_data = self.plc.eb_read(offset, 1)
|
||||
value = bool(raw_data[0] & 0x01)
|
||||
elif var_type == "dint":
|
||||
raw_data = self.plc.eb_read(offset, 4)
|
||||
value = struct.unpack(">l", raw_data)[0]
|
||||
elif var_type == "word":
|
||||
raw_data = self.plc.eb_read(offset, 2)
|
||||
value = struct.unpack(">H", raw_data)[0]
|
||||
elif var_type == "byte":
|
||||
raw_data = self.plc.eb_read(offset, 1)
|
||||
value = struct.unpack(">B", raw_data)[0]
|
||||
elif var_type == "uint":
|
||||
raw_data = self.plc.eb_read(offset, 2)
|
||||
value = struct.unpack(">H", raw_data)[0]
|
||||
elif var_type == "udint":
|
||||
raw_data = self.plc.eb_read(offset, 4)
|
||||
value = struct.unpack(">L", raw_data)[0]
|
||||
elif var_type == "sint":
|
||||
raw_data = self.plc.eb_read(offset, 1)
|
||||
value = struct.unpack(">b", raw_data)[0]
|
||||
elif var_type == "usint":
|
||||
raw_data = self.plc.eb_read(offset, 1)
|
||||
value = struct.unpack(">B", raw_data)[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
elif area_type == "paw" or area_type == "pa":
|
||||
# Process Output Words access
|
||||
if var_type == "real":
|
||||
raw_data = self.plc.ab_read(offset, 4)
|
||||
value = struct.unpack(">f", raw_data)[0]
|
||||
elif var_type == "int":
|
||||
raw_data = self.plc.ab_read(offset, 2)
|
||||
value = struct.unpack(">h", raw_data)[0]
|
||||
elif var_type == "bool":
|
||||
raw_data = self.plc.ab_read(offset, 1)
|
||||
value = bool(raw_data[0] & 0x01)
|
||||
elif var_type == "dint":
|
||||
raw_data = self.plc.ab_read(offset, 4)
|
||||
value = struct.unpack(">l", raw_data)[0]
|
||||
elif var_type == "word":
|
||||
raw_data = self.plc.ab_read(offset, 2)
|
||||
value = struct.unpack(">H", raw_data)[0]
|
||||
elif var_type == "byte":
|
||||
raw_data = self.plc.ab_read(offset, 1)
|
||||
value = struct.unpack(">B", raw_data)[0]
|
||||
elif var_type == "uint":
|
||||
raw_data = self.plc.ab_read(offset, 2)
|
||||
value = struct.unpack(">H", raw_data)[0]
|
||||
elif var_type == "udint":
|
||||
raw_data = self.plc.ab_read(offset, 4)
|
||||
value = struct.unpack(">L", raw_data)[0]
|
||||
elif var_type == "sint":
|
||||
raw_data = self.plc.ab_read(offset, 1)
|
||||
value = struct.unpack(">b", raw_data)[0]
|
||||
elif var_type == "usint":
|
||||
raw_data = self.plc.ab_read(offset, 1)
|
||||
value = struct.unpack(">B", raw_data)[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
elif area_type == "e":
|
||||
# Process Input Bits access (E5.1 format)
|
||||
if var_type == "bool":
|
||||
raw_data = self.plc.eb_read(offset, 1)
|
||||
# Use snap7.util.get_bool for proper bit extraction
|
||||
value = snap7.util.get_bool(raw_data, 0, bit)
|
||||
else:
|
||||
return None
|
||||
|
||||
elif area_type == "a":
|
||||
# Process Output Bits access (A3.7 format)
|
||||
if var_type == "bool":
|
||||
raw_data = self.plc.ab_read(offset, 1)
|
||||
# Use snap7.util.get_bool for proper bit extraction
|
||||
value = snap7.util.get_bool(raw_data, 0, bit)
|
||||
else:
|
||||
return None
|
||||
|
||||
elif area_type == "mb":
|
||||
# Memory Bits access (M10.0 format)
|
||||
if var_type == "bool":
|
||||
raw_data = self.plc.mb_read(offset, 1)
|
||||
# Use snap7.util.get_bool for proper bit extraction
|
||||
value = snap7.util.get_bool(raw_data, 0, bit)
|
||||
else:
|
||||
return None
|
||||
|
||||
if var_type == "real":
|
||||
raw_data = self.plc.db_read(db, offset, 4)
|
||||
value = struct.unpack(">f", raw_data)[0]
|
||||
elif var_type == "int":
|
||||
raw_data = self.plc.db_read(db, offset, 2)
|
||||
value = struct.unpack(">h", raw_data)[0]
|
||||
elif var_type == "bool":
|
||||
raw_data = self.plc.db_read(db, offset, 1)
|
||||
value = bool(raw_data[0] & 0x01)
|
||||
elif var_type == "dint":
|
||||
raw_data = self.plc.db_read(db, offset, 4)
|
||||
value = struct.unpack(">l", raw_data)[0]
|
||||
else:
|
||||
self.logger.error(f"Unsupported area type: {area_type}")
|
||||
return None
|
||||
|
||||
return value
|
||||
|
@ -1064,14 +1291,57 @@ def add_variable():
|
|||
try:
|
||||
data = request.get_json()
|
||||
name = data.get("name")
|
||||
area = data.get("area")
|
||||
db = int(data.get("db"))
|
||||
offset = int(data.get("offset"))
|
||||
var_type = data.get("type")
|
||||
bit = data.get("bit") # Added bit parameter
|
||||
|
||||
if not name or var_type not in ["real", "int", "bool", "dint"]:
|
||||
valid_types = [
|
||||
"real",
|
||||
"int",
|
||||
"bool",
|
||||
"dint",
|
||||
"word",
|
||||
"byte",
|
||||
"uint",
|
||||
"udint",
|
||||
"sint",
|
||||
"usint",
|
||||
]
|
||||
valid_areas = ["db", "mw", "m", "pew", "pe", "paw", "pa", "e", "a", "mb"]
|
||||
|
||||
if (
|
||||
not name
|
||||
or not area
|
||||
or var_type not in valid_types
|
||||
or area.lower() not in valid_areas
|
||||
):
|
||||
return jsonify({"success": False, "message": "Invalid data"}), 400
|
||||
|
||||
streamer.add_variable(name, db, offset, var_type)
|
||||
if area.lower() in ["e", "a", "mb"] and bit is None:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "Bit position must be specified for bit areas",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
if area.lower() in ["e", "a", "mb"] and (bit < 0 or bit > 7):
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "Bit position must be between 0 and 7",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
streamer.add_variable(name, area, db, offset, var_type, bit)
|
||||
return jsonify({"success": True, "message": f"Variable {name} added"})
|
||||
|
||||
except Exception as e:
|
||||
|
@ -1085,6 +1355,133 @@ def remove_variable(name):
|
|||
return jsonify({"success": True, "message": f"Variable {name} removed"})
|
||||
|
||||
|
||||
@app.route("/api/variables/<name>", methods=["GET"])
|
||||
def get_variable(name):
|
||||
"""Get a specific variable configuration"""
|
||||
try:
|
||||
if name not in streamer.variables:
|
||||
return (
|
||||
jsonify({"success": False, "message": f"Variable {name} not found"}),
|
||||
404,
|
||||
)
|
||||
|
||||
var_config = streamer.variables[name].copy()
|
||||
var_config["name"] = name
|
||||
var_config["streaming"] = name in streamer.streaming_variables
|
||||
|
||||
return jsonify({"success": True, "variable": var_config})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "message": str(e)}), 400
|
||||
|
||||
|
||||
@app.route("/api/variables/<name>", methods=["PUT"])
|
||||
def update_variable(name):
|
||||
"""Update an existing variable"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
new_name = data.get("name", name)
|
||||
area = data.get("area")
|
||||
db = int(data.get("db", 1))
|
||||
offset = int(data.get("offset"))
|
||||
var_type = data.get("type")
|
||||
bit = data.get("bit") # Added bit parameter
|
||||
|
||||
valid_types = [
|
||||
"real",
|
||||
"int",
|
||||
"bool",
|
||||
"dint",
|
||||
"word",
|
||||
"byte",
|
||||
"uint",
|
||||
"udint",
|
||||
"sint",
|
||||
"usint",
|
||||
]
|
||||
valid_areas = ["db", "mw", "m", "pew", "pe", "paw", "pa", "e", "a", "mb"]
|
||||
|
||||
if (
|
||||
not new_name
|
||||
or not area
|
||||
or var_type not in valid_types
|
||||
or area.lower() not in valid_areas
|
||||
):
|
||||
return jsonify({"success": False, "message": "Invalid data"}), 400
|
||||
|
||||
if area.lower() in ["e", "a", "mb"] and bit is None:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "Bit position must be specified for bit areas",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
if area.lower() in ["e", "a", "mb"] and (bit < 0 or bit > 7):
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "Bit position must be between 0 and 7",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# Remove old variable if name changed
|
||||
if name != new_name and name in streamer.variables:
|
||||
# Check if new name already exists
|
||||
if new_name in streamer.variables:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": f"Variable {new_name} already exists",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# Preserve streaming state
|
||||
was_streaming = name in streamer.streaming_variables
|
||||
streamer.remove_variable(name)
|
||||
|
||||
# Add updated variable
|
||||
streamer.add_variable(new_name, area, db, offset, var_type, bit)
|
||||
|
||||
# Restore streaming state if it was enabled
|
||||
if was_streaming:
|
||||
streamer.toggle_streaming_variable(new_name, True)
|
||||
else:
|
||||
# Update existing variable
|
||||
if name not in streamer.variables:
|
||||
return (
|
||||
jsonify(
|
||||
{"success": False, "message": f"Variable {name} not found"}
|
||||
),
|
||||
404,
|
||||
)
|
||||
|
||||
# Preserve streaming state
|
||||
was_streaming = name in streamer.streaming_variables
|
||||
|
||||
# Remove and re-add with new configuration
|
||||
streamer.remove_variable(name)
|
||||
streamer.add_variable(new_name, area, db, offset, var_type, bit)
|
||||
|
||||
# Restore streaming state if it was enabled
|
||||
if was_streaming:
|
||||
streamer.toggle_streaming_variable(new_name, True)
|
||||
|
||||
return jsonify({"success": True, "message": f"Variable updated successfully"})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "message": str(e)}), 400
|
||||
|
||||
|
||||
@app.route("/api/variables/<name>/streaming", methods=["POST"])
|
||||
def toggle_variable_streaming(name):
|
||||
"""Toggle streaming for a specific variable"""
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
41384
|
|
@ -22,5 +22,11 @@
|
|||
"offset": 18,
|
||||
"type": "real",
|
||||
"streaming": true
|
||||
},
|
||||
"PEW302": {
|
||||
"area": "pew",
|
||||
"offset": 302,
|
||||
"type": "word",
|
||||
"streaming": true
|
||||
}
|
||||
}
|
|
@ -5,5 +5,5 @@
|
|||
"should_record_csv": true
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-07-17T15:42:38.054690"
|
||||
"last_update": "2025-07-17T16:22:46.833236"
|
||||
}
|
|
@ -360,6 +360,88 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 5% auto;
|
||||
padding: 0;
|
||||
border-radius: 15px;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
animation: modalShow 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes modalShow {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: linear-gradient(135deg, #6b7280, #4b5563);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 15px 15px 0 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 20px 30px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #9ca3af, #6b7280);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: linear-gradient(135deg, #6b7280, #4b5563);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
@ -461,6 +543,18 @@
|
|||
<input type="text" id="var-name" placeholder="temperature" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Memory Area:</label>
|
||||
<select id="var-area" required onchange="toggleFields()">
|
||||
<option value="db">DB (Data Block)</option>
|
||||
<option value="mw">MW (Memory Words)</option>
|
||||
<option value="pew">PEW (Process Input Words)</option>
|
||||
<option value="paw">PAW (Process Output Words)</option>
|
||||
<option value="e">E (Input Bits)</option>
|
||||
<option value="a">A (Output Bits)</option>
|
||||
<option value="mb">MB (Memory Bits)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="db-field">
|
||||
<label>Data Block (DB):</label>
|
||||
<input type="number" id="var-db" min="1" max="9999" value="1" required>
|
||||
</div>
|
||||
|
@ -468,12 +562,31 @@
|
|||
<label>Offset:</label>
|
||||
<input type="number" id="var-offset" min="0" max="8192" value="0" required>
|
||||
</div>
|
||||
<div class="form-group" id="bit-field" style="display: none;">
|
||||
<label>Bit Position:</label>
|
||||
<select id="var-bit">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Data Type:</label>
|
||||
<select id="var-type" required>
|
||||
<option value="real">REAL (Float 32-bit)</option>
|
||||
<option value="int">INT (16-bit)</option>
|
||||
<option value="dint">DINT (32-bit)</option>
|
||||
<option value="int">INT (16-bit Signed)</option>
|
||||
<option value="uint">UINT (16-bit Unsigned)</option>
|
||||
<option value="dint">DINT (32-bit Signed)</option>
|
||||
<option value="udint">UDINT (32-bit Unsigned)</option>
|
||||
<option value="word">WORD (16-bit)</option>
|
||||
<option value="byte">BYTE (8-bit)</option>
|
||||
<option value="sint">SINT (8-bit Signed)</option>
|
||||
<option value="usint">USINT (8-bit Unsigned)</option>
|
||||
<option value="bool">BOOL</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -486,7 +599,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Data Block</th>
|
||||
<th>Memory Area</th>
|
||||
<th>Offset</th>
|
||||
<th>Type</th>
|
||||
<th>Stream to PlotJuggler</th>
|
||||
|
@ -497,7 +610,25 @@
|
|||
{% for name, var in variables.items() %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td>DB{{ var.db }}</td>
|
||||
<td>
|
||||
{% if var.area == 'db' or var.get('db') %}
|
||||
DB{{ var.get('db', 'N/A') }}.{{ var.offset }}
|
||||
{% elif var.area == 'mw' or var.area == 'm' %}
|
||||
MW{{ var.offset }}
|
||||
{% elif var.area == 'pew' or var.area == 'pe' %}
|
||||
PEW{{ var.offset }}
|
||||
{% elif var.area == 'paw' or var.area == 'pa' %}
|
||||
PAW{{ var.offset }}
|
||||
{% elif var.area == 'e' %}
|
||||
E{{ var.offset }}.{{ var.bit }}
|
||||
{% elif var.area == 'a' %}
|
||||
A{{ var.offset }}.{{ var.bit }}
|
||||
{% elif var.area == 'mb' %}
|
||||
M{{ var.offset }}.{{ var.bit }}
|
||||
{% else %}
|
||||
DB{{ var.get('db', 'N/A') }}.{{ var.offset }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ var.offset }}</td>
|
||||
<td>{{ var.type.upper() }}</td>
|
||||
<td>
|
||||
|
@ -506,6 +637,7 @@
|
|||
<label for="stream-{{ name }}">Enable</label>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" onclick="editVariable('{{ name }}')">✏️ Edit</button>
|
||||
<button class="btn btn-danger" onclick="removeVariable('{{ name }}')">🗑️ Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -514,6 +646,78 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Edit Variable Modal -->
|
||||
<div id="edit-modal" class="modal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>✏️ Edit Variable</h3>
|
||||
<span class="close" onclick="closeEditModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="edit-variable-form">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Variable Name:</label>
|
||||
<input type="text" id="edit-var-name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Memory Area:</label>
|
||||
<select id="edit-var-area" required onchange="toggleEditFields()">
|
||||
<option value="db">DB (Data Block)</option>
|
||||
<option value="mw">MW (Memory Words)</option>
|
||||
<option value="pew">PEW (Process Input Words)</option>
|
||||
<option value="paw">PAW (Process Output Words)</option>
|
||||
<option value="e">E (Input Bits)</option>
|
||||
<option value="a">A (Output Bits)</option>
|
||||
<option value="mb">MB (Memory Bits)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="edit-db-field">
|
||||
<label>Data Block (DB):</label>
|
||||
<input type="number" id="edit-var-db" min="1" max="9999" value="1" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Offset:</label>
|
||||
<input type="number" id="edit-var-offset" min="0" max="8192" value="0" required>
|
||||
</div>
|
||||
<div class="form-group" id="edit-bit-field" style="display: none;">
|
||||
<label>Bit Position:</label>
|
||||
<select id="edit-var-bit">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Data Type:</label>
|
||||
<select id="edit-var-type" required>
|
||||
<option value="real">REAL (Float 32-bit)</option>
|
||||
<option value="int">INT (16-bit Signed)</option>
|
||||
<option value="uint">UINT (16-bit Unsigned)</option>
|
||||
<option value="dint">DINT (32-bit Signed)</option>
|
||||
<option value="udint">UDINT (32-bit Unsigned)</option>
|
||||
<option value="word">WORD (16-bit)</option>
|
||||
<option value="byte">BYTE (8-bit)</option>
|
||||
<option value="sint">SINT (8-bit Signed)</option>
|
||||
<option value="usint">USINT (8-bit Unsigned)</option>
|
||||
<option value="bool">BOOL (Boolean)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeEditModal()">❌ Cancel</button>
|
||||
<button type="submit" class="btn btn-success">💾 Update Variable</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CSV Recording Control -->
|
||||
<div class="card">
|
||||
<h2>💾 CSV Recording Control</h2>
|
||||
|
@ -694,16 +898,59 @@
|
|||
});
|
||||
});
|
||||
|
||||
// Toggle DB and Bit field visibility based on memory area selection
|
||||
function toggleFields() {
|
||||
const area = document.getElementById('var-area').value;
|
||||
const dbField = document.getElementById('db-field');
|
||||
const dbInput = document.getElementById('var-db');
|
||||
const bitField = document.getElementById('bit-field');
|
||||
const typeSelect = document.getElementById('var-type');
|
||||
|
||||
// Handle DB field
|
||||
if (area === 'db') {
|
||||
dbField.style.display = 'block';
|
||||
dbInput.required = true;
|
||||
} else {
|
||||
dbField.style.display = 'none';
|
||||
dbInput.required = false;
|
||||
dbInput.value = 1; // Default value for non-DB areas
|
||||
}
|
||||
|
||||
// Handle Bit field and data type restrictions
|
||||
if (area === 'e' || area === 'a' || area === 'mb') {
|
||||
bitField.style.display = 'block';
|
||||
// For bit areas, force data type to bool
|
||||
typeSelect.value = 'bool';
|
||||
// Disable other data types for bit areas
|
||||
Array.from(typeSelect.options).forEach(option => {
|
||||
option.disabled = (option.value !== 'bool');
|
||||
});
|
||||
} else {
|
||||
bitField.style.display = 'none';
|
||||
// Re-enable all data types for non-bit areas
|
||||
Array.from(typeSelect.options).forEach(option => {
|
||||
option.disabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add variable
|
||||
document.getElementById('variable-form').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const area = document.getElementById('var-area').value;
|
||||
const data = {
|
||||
name: document.getElementById('var-name').value,
|
||||
db: parseInt(document.getElementById('var-db').value),
|
||||
area: area,
|
||||
db: area === 'db' ? parseInt(document.getElementById('var-db').value) : 1,
|
||||
offset: parseInt(document.getElementById('var-offset').value),
|
||||
type: document.getElementById('var-type').value
|
||||
};
|
||||
|
||||
// Add bit parameter for bit areas
|
||||
if (area === 'e' || area === 'a' || area === 'mb') {
|
||||
data.bit = parseInt(document.getElementById('var-bit').value);
|
||||
}
|
||||
|
||||
fetch('/api/variables', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
@ -946,6 +1193,138 @@
|
|||
updateStatus();
|
||||
loadStreamingStatus();
|
||||
refreshEventLog();
|
||||
|
||||
// Edit Variable Functions
|
||||
let currentEditingVariable = null;
|
||||
|
||||
function editVariable(name) {
|
||||
currentEditingVariable = name;
|
||||
|
||||
// Fetch current variable data
|
||||
fetch(`/api/variables/${name}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
populateEditForm(data.variable);
|
||||
document.getElementById('edit-modal').style.display = 'block';
|
||||
} else {
|
||||
showMessage(data.message, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showMessage(`Error fetching variable data: ${error}`, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function populateEditForm(variable) {
|
||||
document.getElementById('edit-var-name').value = variable.name;
|
||||
document.getElementById('edit-var-area').value = variable.area;
|
||||
document.getElementById('edit-var-offset').value = variable.offset;
|
||||
document.getElementById('edit-var-type').value = variable.type;
|
||||
|
||||
if (variable.db) {
|
||||
document.getElementById('edit-var-db').value = variable.db;
|
||||
}
|
||||
|
||||
if (variable.bit !== undefined) {
|
||||
document.getElementById('edit-var-bit').value = variable.bit;
|
||||
}
|
||||
|
||||
// Update field visibility based on area
|
||||
toggleEditFields();
|
||||
}
|
||||
|
||||
function toggleEditFields() {
|
||||
const area = document.getElementById('edit-var-area').value;
|
||||
const dbField = document.getElementById('edit-db-field');
|
||||
const dbInput = document.getElementById('edit-var-db');
|
||||
const bitField = document.getElementById('edit-bit-field');
|
||||
const typeSelect = document.getElementById('edit-var-type');
|
||||
|
||||
// Handle DB field
|
||||
if (area === 'db') {
|
||||
dbField.style.display = 'block';
|
||||
dbInput.required = true;
|
||||
} else {
|
||||
dbField.style.display = 'none';
|
||||
dbInput.required = false;
|
||||
dbInput.value = 1; // Default value for non-DB areas
|
||||
}
|
||||
|
||||
// Handle Bit field and data type restrictions
|
||||
if (area === 'e' || area === 'a' || area === 'mb') {
|
||||
bitField.style.display = 'block';
|
||||
// For bit areas, force data type to bool
|
||||
typeSelect.value = 'bool';
|
||||
// Disable other data types for bit areas
|
||||
Array.from(typeSelect.options).forEach(option => {
|
||||
option.disabled = (option.value !== 'bool');
|
||||
});
|
||||
} else {
|
||||
bitField.style.display = 'none';
|
||||
// Re-enable all data types for non-bit areas
|
||||
Array.from(typeSelect.options).forEach(option => {
|
||||
option.disabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
document.getElementById('edit-modal').style.display = 'none';
|
||||
currentEditingVariable = null;
|
||||
}
|
||||
|
||||
// Handle edit form submission
|
||||
document.getElementById('edit-variable-form').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!currentEditingVariable) {
|
||||
showMessage('No variable selected for editing', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const area = document.getElementById('edit-var-area').value;
|
||||
const data = {
|
||||
name: document.getElementById('edit-var-name').value,
|
||||
area: area,
|
||||
db: area === 'db' ? parseInt(document.getElementById('edit-var-db').value) : 1,
|
||||
offset: parseInt(document.getElementById('edit-var-offset').value),
|
||||
type: document.getElementById('edit-var-type').value
|
||||
};
|
||||
|
||||
// Add bit parameter for bit areas
|
||||
if (area === 'e' || area === 'a' || area === 'mb') {
|
||||
data.bit = parseInt(document.getElementById('edit-var-bit').value);
|
||||
}
|
||||
|
||||
fetch(`/api/variables/${currentEditingVariable}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
showMessage(data.message, data.success ? 'success' : 'error');
|
||||
if (data.success) {
|
||||
closeEditModal();
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showMessage(`Error updating variable: ${error}`, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// Close modal when clicking outside of it
|
||||
window.onclick = function (event) {
|
||||
const modal = document.getElementById('edit-modal');
|
||||
if (event.target === modal) {
|
||||
closeEditModal();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize field visibility on page load
|
||||
toggleFields();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
|
Loading…
Reference in New Issue