10 KiB
PLC S7-315 Streamer & Logger - AI Coding Guide
Architecture Overview
This is a dual-stack industrial automation system for Siemens S7-315 PLCs combining Python backend orchestration with React frontend controls:
- Backend: Flask app (
main.py
) orchestrating core modules viaPLCDataStreamer
- Frontend: Vite + React + Chakra UI + RJSF for dynamic configuration forms
- Data Flow: PLC → snap7 → CSV recording + UDP streaming → PlotJuggler visualization
- Configuration: JSON Schema-driven with validation for PLC variables and plot definitions
Core Architecture Principles
1. Orchestrator Pattern
core/plc_data_streamer.py
is the main coordinator that initializes and manages:
ConfigManager
: JSON configuration persistence with schema validationPLCClient
: Siemens snap7 communicationDataStreamer
: Independent CSV recording + UDP streaming threadsEventLogger
: Persistent application event loggingInstanceManager
: Single-instance control with auto-recovery
2. Independent Data Streams
Critical: CSV recording and UDP streaming are separate concerns:
- CSV recording: Always active when PLC connected (automatic)
- UDP streaming: Manual control for PlotJuggler visualization
- Each dataset thread handles both, but UDP transmission is independently controlled
3. Schema-Driven Configuration with RJSF
All configuration uses JSON Schema validation with React JSON Schema Forms (RJSF):
- Frontend-First Validation: RJSF handles all form generation and validation
- Backend API Simplification: Flask provides simple CRUD operations for JSON files
- Array-Based Data Structure: All configurations use array format for RJSF compatibility
- Three Form Types:
- Type 1: Single object forms (PLC config)
- Type 2: Array management forms (dataset definitions, plot definitions)
- Type 3: Filtered array forms with combo selectors (variables linked to datasets/plots)
4. JSON Configuration Structure
CRITICAL: All JSON files use array-based structures for RJSF compatibility:
plc_config.json
: Single object withudp_config
containingsampling_interval
dataset_definitions.json
:{"datasets": [{"id": "DAR", "name": "...", ...}]}
dataset_variables.json
:{"variables": [{"dataset_id": "DAR", "variables": [...]}]}
plot_definitions.json
:{"plots": [{"id": "plot1", "name": "...", ...}]}
plot_variables.json
:{"plot_variables": [{"plot_id": "plot1", "variables": [...]}]}
Development Workflows
Backend Development
# Setup Python environment (REQUIRED before any Python work)
conda create -n plc_streamer python=3.10
conda activate plc_streamer
pip install -r requirements.txt
# Run backend server
python main.py
# Access at http://localhost:5000
Frontend Development
cd frontend
npm install
npm run dev # Development server at http://localhost:5173
npm run build # Production build to dist/
Key External Dependencies
- snap7.dll: Must be in system PATH or project root for PLC communication
- PlotJuggler: Configured to receive UDP JSON at 127.0.0.1:9870
Critical File Patterns
Configuration Management
config/data/*.json
: Runtime configuration filesconfig/schema/*.json
: JSON Schema definitionsConfigSchemaManager
incore/schema_manager.py
: Centralized schema loading and validation
Frontend Components
pages/DashboardNew.jsx
: Main control interface with tabbed layoutcomponents/EditableTable.jsx
: Reusable schema-to-table convertercomponents/PlotManager.jsx
: Real-time Chart.js plotting with streaming- Pattern: RJSF forms for configuration, custom tables for data management
API Endpoints Structure
Flask routes in main.py
follow simplified REST patterns with unified JSON handling:
/api/config/*
: Unified configuration CRUD operations for all JSON files/api/plc/*
: PLC connection and status/api/streaming/*
: Data streaming controls/api/plots/*
: Plot session management- API Philosophy: Backend provides simple file I/O, frontend handles all validation via RJSF
Important Conventions
1. Error Handling & Logging
- All core classes require logger injection:
__init__(self, logger)
- Use
self.event_logger.log_event()
for persistent events - PyInstaller compatibility: Use
resource_path()
for file access
2. React Component Patterns
- FormTable.jsx: Single-row forms per item using RJSF schemas
- DatasetFormManager/PlotFormManager: Master-detail table management
- Chakra UI components with consistent styling via
theme.js
- RJSF Integration: All forms auto-generated from JSON Schema, no hardcoded form fields
3. Thread Safety
- Data streaming uses thread-safe collections and proper cleanup
- Instance management prevents multiple app instances
- Background threads properly handle graceful shutdown
4. Schema Evolution
Follow existing patterns in config/schema/
- all forms are auto-generated from JSON Schema + UI Schema combinations. Never hardcode form fields.
- Array-First Design: All multi-item configurations use array structures for RJSF type 2 forms
- Unified Validation: JSON Schema validation both client-side (RJSF) and server-side (jsonschema library)
- Schema-UI Separation: Data schemas in
/config/schema/
, UI schemas in/config/schema/ui/
5. Development Context
- Use
.doc/MemoriaDeEvolucion.md
for understanding recent changes and decisions - Comments and variables must be in English per project conventions
- No standalone markdown files unless specifically requested
Integration Points
PLC Communication
- Data blocks accessed via
DB{number}.{offset}
addressing - Supported types: REAL, INT, DINT, BOOL
- Connection state managed through
PLCClient
with automatic reconnection
Real-time Visualization
- Chart.js with chartjs-plugin-streaming for real-time plots
- Automatic data ingestion from
/api/plots/{session_id}/data
- Plot session lifecycle managed through backend API
CSV Data Export
- Automatic file rotation by size/time in
records/
directory - Thread-safe CSV writing with proper cleanup
- Configurable retention policies for long-term storage
Notes
Always write software variables and comments in English The development is focused on Windows and after testing must work without CDN completely offline.
RJSF Configuration Management
Form Type Architecture
The system implements three distinct RJSF form patterns:
Type 1: Single Object Forms
- Used for: PLC configuration (
plc_config.json
) - Structure: Single JSON object with nested properties
- RJSF Pattern: Direct object form rendering
- Example: Connection settings, UDP configuration with
sampling_interval
Type 2: Array Management Forms
- Used for: Dataset definitions (
dataset_definitions.json
), Plot definitions (plot_definitions.json
) - Structure:
{"datasets": [...]}
or{"plots": [...]}
- RJSF Pattern: Array form with add/remove/edit capabilities
- Critical: Root must be array wrapper for RJSF compatibility
Type 3: Filtered Array Forms with Combo Selectors
- Used for: Variables linked to datasets/plots (
dataset_variables.json
,plot_variables.json
) - Structure: Array with foreign key references (
dataset_id
,plot_id
) - RJSF Pattern: Filtered forms based on selected dataset/plot
- Workflow: Select parent → Edit associated variables
- Implementation: Combo selector + dynamic schema generation for selected item
- Key Functions:
getSelectedDatasetVariables()
,updateSelectedDatasetVariables()
RJSF Best Practices and Common Pitfalls
Critical Widget Guidelines:
- Arrays: Never specify
"ui:widget": "array"
- arrays use built-in ArrayField component - Valid Widgets: text, textarea, select, checkbox, updown, variableSelector
- Widget Registry: All widgets must be registered in
AllWidgets.jsx
- Custom Widgets: Use specific widget names, avoid generic type names
Schema Structure Rules:
- Array Items: Always include
title
property for array item schemas - UI Layout: Use
"ui:layout"
for grid-based field arrangement - Field Templates: Leverage
LayoutObjectFieldTemplate
for responsive layouts - Error Handling: RJSF errors often indicate missing widgets or malformed schemas
Type 3 Form Implementation Pattern
// Step 1: Parent Selector (Combo)
const [selectedItemId, setSelectedItemId] = useState('')
// Step 2: Filtered Data Helper
const getSelectedItemData = () => {
return allData.find(item => item.parent_id === selectedItemId) || defaultData
}
// Step 3: Update Helper
const updateSelectedItemData = (newData) => {
const updated = allData.map(item =>
item.parent_id === selectedItemId ? { ...item, ...newData } : item
)
setAllData({ ...allData, items: updated })
}
// Step 4: Dynamic Schema Generation
const dynamicSchema = {
type: "object",
properties: { /* fields specific to selected item */ }
}
JSON Schema Migration Notes
- Legacy to Array: All object-based configs converted to array format
- ID Fields: Added explicit
id
fields to all array items for referencing - Validation: Unified validation using
jsonschema
library server-side + RJSF client-side - Backward Compatibility: Migration handled in backend for existing configurations
Development Debugging Guide
RJSF Error Resolution:
No widget 'X' for type 'Y'
: Check widget registration inAllWidgets.jsx
- Array rendering errors: Remove
"ui:widget"
specification from array fields - Schema validation failures: Use
validate_schema.py
to test JSON structure - Form not displaying: Verify schema structure matches expected Type 1/2/3 pattern
Type 3 Form Debugging:
- Combo not showing options: Check parent data loading and
availableItems
array - Form not updating: Verify
selectedItemId
state and helper functions - Data not persisting: Check
updateSelectedItemData()
logic and save operations - Schema errors: Ensure dynamic schema generation matches data structure
Frontend-Backend Integration:
- API endpoint naming: Use consistent
/api/config/{config-name}
pattern - JSON structure validation: Backend uses
jsonschema
, frontend uses RJSF validation - Error handling: Both client and server should handle array format gracefully
- Configuration loading: Always verify API response structure before setting form data