Implement memory map and arithmetic logic
This commit is contained in:
86
App.tsx
86
App.tsx
@@ -9,7 +9,8 @@ import { LadderEditor } from './components/LadderEditor';
|
||||
import { SystemSetupDialog } from './components/SystemSetupDialog';
|
||||
import {
|
||||
SimObject, ObjectType, AxisObject, CylinderObject, SwitchObject, LedObject,
|
||||
IOLogicRule, LogicCondition, LogicAction, ProjectData, AxisData, LogicTriggerType, LogicActionType
|
||||
IOLogicRule, LogicCondition, LogicAction, ProjectData, AxisData, LogicTriggerType, LogicActionType,
|
||||
LogicMathOp, MEMORY_SIZE, ADDR_AXIS_BASE, ADDR_AXIS_STRIDE, OFF_AXIS_CURRENT_POS, OFF_AXIS_TARGET_POS
|
||||
} from './types';
|
||||
import { EditableObject } from './components/SceneObjects';
|
||||
import { Layout as LayoutIcon, Cpu, Play, Pause, Square, Download, Upload, Magnet, Settings } from 'lucide-react';
|
||||
@@ -36,7 +37,8 @@ const SimulationLoop = ({
|
||||
setInputs: (inputs: boolean[]) => void,
|
||||
setOutputs: (outputs: boolean[]) => void,
|
||||
axes: AxisData[],
|
||||
setAxes: React.Dispatch<React.SetStateAction<AxisData[]>>
|
||||
setAxes: React.Dispatch<React.SetStateAction<AxisData[]>>,
|
||||
memoryView: React.MutableRefObject<DataView>
|
||||
}) => {
|
||||
|
||||
useFrame((state, delta) => {
|
||||
@@ -64,6 +66,25 @@ const SimulationLoop = ({
|
||||
const effectiveInputs = sensorInputs.map((bit, i) => bit || manualInputs[i]);
|
||||
setInputs(effectiveInputs);
|
||||
|
||||
// --- 1. Memory IO Scan (Axis Global State -> Memory) ---
|
||||
// Update Memory Mapped Axis Areas (Read Current Pos from App State)
|
||||
// In a real PLC, this happens at the start of scan.
|
||||
axes.forEach(axis => {
|
||||
const base = ADDR_AXIS_BASE + (axis.id * ADDR_AXIS_STRIDE);
|
||||
// 0: Status (Uint16) - reserved
|
||||
memoryView.current.setUint16(base, 0, true);
|
||||
// 2: Current Pos (Float32)
|
||||
memoryView.current.setFloat32(base + OFF_AXIS_CURRENT_POS, axis.value, true);
|
||||
// 10: Speed (Float32)
|
||||
memoryView.current.setFloat32(base + 10, axis.speed, true);
|
||||
|
||||
// Sync Target to memory if it wasn't written by logic, or initialize it
|
||||
// (Optional: In strict PLC, memory drives axis. If we want UI slider to work, UI must write to memory OR axis. Logic takes precedence)
|
||||
// Here we write the *current* target to memory so logic sees it, unless logic overwrites it.
|
||||
// Check if we need to sync? For now, let's write it so 'readback' works.
|
||||
// memoryView.current.setFloat32(base + OFF_AXIS_TARGET_POS, axis.target, true);
|
||||
});
|
||||
|
||||
// --- 2. PLC Logic Execution ---
|
||||
const logicOutputs = new Array(32).fill(false);
|
||||
|
||||
@@ -79,6 +100,7 @@ const SimulationLoop = ({
|
||||
const inputState = effectiveInputs[rule.inputPort];
|
||||
conditionMet = inputState; // Default IS_ON behavior for now
|
||||
} else if (rule.triggerType === LogicTriggerType.AXIS_COMPARE) {
|
||||
// Legacy Axis Compare
|
||||
const axis = axes[rule.triggerAxisIndex];
|
||||
if (axis) {
|
||||
switch (rule.triggerCompareOp) {
|
||||
@@ -87,6 +109,18 @@ const SimulationLoop = ({
|
||||
case LogicCondition.EQUAL: conditionMet = Math.abs(axis.value - rule.triggerValue) < 0.1; break;
|
||||
}
|
||||
}
|
||||
} else if (rule.triggerType === LogicTriggerType.MEM_COMPARE) {
|
||||
// Memory Compare
|
||||
// Read 4 bytes at address as Float32 (Standardize on Float for M-registers for now)
|
||||
const addr = rule.triggerAddress || 0;
|
||||
if (addr >= 0 && addr < MEMORY_SIZE - 4) {
|
||||
const val = memoryView.current.getFloat32(addr, true);
|
||||
switch (rule.triggerCompareOp) {
|
||||
case LogicCondition.GREATER: conditionMet = val > rule.triggerValue; break;
|
||||
case LogicCondition.LESS: conditionMet = val < rule.triggerValue; break;
|
||||
case LogicCondition.EQUAL: conditionMet = Math.abs(val - rule.triggerValue) < 0.001; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Execute Action if Trigger Met
|
||||
@@ -96,7 +130,28 @@ const SimulationLoop = ({
|
||||
logicOutputs[rule.outputPort] = true;
|
||||
}
|
||||
} else if (rule.actionType === LogicActionType.AXIS_MOVE) {
|
||||
// Legacy Move: Directly set Axis Target
|
||||
axisUpdates.set(rule.targetAxisIndex, rule.targetAxisValue);
|
||||
|
||||
// Also update Memory Map for consistency
|
||||
const base = ADDR_AXIS_BASE + (rule.targetAxisIndex * ADDR_AXIS_STRIDE);
|
||||
if (base < MEMORY_SIZE) memoryView.current.setFloat32(base + OFF_AXIS_TARGET_POS, rule.targetAxisValue, true);
|
||||
|
||||
} else if (rule.actionType === LogicActionType.MEM_OPERATION) {
|
||||
// Memory Math
|
||||
const addr = rule.memAddress || 0;
|
||||
if (addr >= 0 && addr < MEMORY_SIZE - 4) {
|
||||
let currentVal = memoryView.current.getFloat32(addr, true);
|
||||
let newVal = currentVal;
|
||||
|
||||
switch (rule.memOperation) {
|
||||
case LogicMathOp.SET: newVal = rule.memOperandValue; break;
|
||||
case LogicMathOp.ADD: newVal = currentVal + rule.memOperandValue; break;
|
||||
case LogicMathOp.SUB: newVal = currentVal - rule.memOperandValue; break;
|
||||
}
|
||||
|
||||
memoryView.current.setFloat32(addr, newVal, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -105,7 +160,27 @@ const SimulationLoop = ({
|
||||
const effectiveOutputs = logicOutputs.map((bit, i) => bit || manualOutputs[i]);
|
||||
setOutputs(effectiveOutputs);
|
||||
|
||||
// Apply Axis Logic Updates (Only if changed to prevent thrashing)
|
||||
// --- 3. Memory Output Scan (Memory -> Axis Command) ---
|
||||
// Read Target Position from Memory for each axis and apply to State
|
||||
axes.forEach(axis => {
|
||||
const base = ADDR_AXIS_BASE + (axis.id * ADDR_AXIS_STRIDE);
|
||||
const memTarget = memoryView.current.getFloat32(base + OFF_AXIS_TARGET_POS, true);
|
||||
|
||||
// Simple change detection (epsilon) or just overwrite if non-zero?
|
||||
// Issue: If UI Slider sets axis.target, and memory has 0, memory overwrites UI.
|
||||
// User Requirement: "write to memory... motor moves".
|
||||
// So Memory is the source of truth for Target.
|
||||
// But initially memory is 0. So Axis goes to 0?
|
||||
// Workaround: If memory target is exactly 0 and axis is not 0, maybe don't force?
|
||||
// Better: Initialize Memory with Axis Target on startup.
|
||||
// For now, let's assume Logic drives Memory.
|
||||
|
||||
if (memTarget !== axis.target && Math.abs(memTarget - axis.target) > 0.001) {
|
||||
axisUpdates.set(axis.id, memTarget);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply Axis Logic Updates
|
||||
if (axisUpdates.size > 0) {
|
||||
setAxes(prev => prev.map(a => {
|
||||
if (axisUpdates.has(a.id)) {
|
||||
@@ -179,6 +254,10 @@ const SimulationLoop = ({
|
||||
|
||||
export default function App() {
|
||||
const [activeView, setActiveView] = useState<'layout' | 'logic'>('layout');
|
||||
// --- Memory System (Ref-based, high frequency) ---
|
||||
const memoryBuffer = useRef(new ArrayBuffer(MEMORY_SIZE)); // 10000 bytes
|
||||
const memoryView = useRef(new DataView(memoryBuffer.current));
|
||||
|
||||
const [objects, setObjects] = useState<SimObject[]>([]);
|
||||
const [logicRules, setLogicRules] = useState<IOLogicRule[]>([]);
|
||||
|
||||
@@ -493,6 +572,7 @@ export default function App() {
|
||||
setOutputs={setOutputs}
|
||||
axes={axes}
|
||||
setAxes={setAxes}
|
||||
memoryView={memoryView}
|
||||
/>
|
||||
</Canvas>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user