Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PLC Logic Simulator</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <style> | |
| .ladder-rung { | |
| height: 80px; | |
| border-left: 4px solid #4B5563; | |
| border-right: 4px solid #4B5563; | |
| position: relative; | |
| } | |
| .ladder-rail { | |
| width: 100%; | |
| height: 4px; | |
| background-color: #4B5563; | |
| } | |
| .contact { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .contact.active { | |
| background-color: #10B981; | |
| } | |
| .contact.inactive { | |
| background-color: #EF4444; | |
| } | |
| .coil { | |
| width: 50px; | |
| height: 30px; | |
| border-radius: 4px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .coil.active { | |
| background-color: #10B981; | |
| } | |
| .coil.inactive { | |
| background-color: #EF4444; | |
| } | |
| .wire { | |
| height: 4px; | |
| background-color: #4B5563; | |
| } | |
| .logic-gate { | |
| width: 60px; | |
| height: 60px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .logic-gate.active { | |
| background-color: #10B981; | |
| } | |
| .logic-gate.inactive { | |
| background-color: #EF4444; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="mb-8 text-center"> | |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Industrial Control Simulators</h1> | |
| <p class="text-gray-600">Drag, drop, and simulate industrial control systems</p> | |
| <div class="mt-4 flex justify-center space-x-4"> | |
| <a href="index.html" class="px-4 py-2 bg-blue-500 text-white rounded-lg">PLC Simulator</a> | |
| <a href="singleline.html" class="px-4 py-2 bg-green-500 text-white rounded-lg">Single Line Diagram</a> | |
| </div> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-4 gap-6 mt-8"> | |
| <!-- Toolbox --> | |
| <div class="bg-white rounded-lg shadow-md p-4 lg:col-span-1"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">Components</h2> | |
| <div class="space-y-3"> | |
| <div class="bg-gray-100 p-3 rounded-lg flex items-center cursor-move" draggable="true" data-type="contact-normally-open"> | |
| <i data-feather="circle" class="text-gray-700 mr-2"></i> | |
| <span>NO Contact</span> | |
| </div> | |
| <div class="bg-gray-100 p-3 rounded-lg flex items-center cursor-move" draggable="true" data-type="contact-normally-closed"> | |
| <i data-feather="x" class="text-gray-700 mr-2"></i> | |
| <span>NC Contact</span> | |
| </div> | |
| <div class="bg-gray-100 p-3 rounded-lg flex items-center cursor-move" draggable="true" data-type="coil"> | |
| <i data-feather="square" class="text-gray-700 mr-2"></i> | |
| <span>Coil</span> | |
| </div> | |
| <div class="bg-gray-100 p-3 rounded-lg flex items-center cursor-move" draggable="true" data-type="timer"> | |
| <i data-feather="clock" class="text-gray-700 mr-2"></i> | |
| <span>Timer</span> | |
| </div> | |
| <div class="bg-gray-100 p-3 rounded-lg flex items-center cursor-move" draggable="true" data-type="counter"> | |
| <i data-feather="hash" class="text-gray-700 mr-2"></i> | |
| <span>Counter</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Workspace --> | |
| <div class="bg-white rounded-lg shadow-md p-4 lg:col-span-3" id="workspace"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Ladder Diagram</h2> | |
| <div class="flex space-x-2"> | |
| <button class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center"> | |
| <i data-feather="play" class="mr-2"></i> | |
| Run | |
| </button> | |
| <button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center"> | |
| <i data-feather="save" class="mr-2"></i> | |
| Save | |
| </button> | |
| <button class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg flex items-center"> | |
| <i data-feather="trash-2" class="mr-2"></i> | |
| Clear | |
| </button> | |
| </div> | |
| </div> | |
| <div class="border-2 border-gray-300 rounded-lg p-6 min-h-[500px]" id="ladder-diagram"> | |
| <!-- Vertical rails --> | |
| <div class="flex justify-between mb-4"> | |
| <div class="w-12 h-full border-r-2 border-gray-400"></div> | |
| <div class="w-12 h-full border-l-2 border-gray-400"></div> | |
| </div> | |
| <!-- Rung 1 --> | |
| <div class="relative mb-8"> | |
| <div class="ladder-rail"></div> | |
| <div class="ladder-rung flex items-center justify-center px-12"> | |
| <div class="contact inactive" data-type="contact-normally-open" data-id="1"></div> | |
| <div class="wire w-16"></div> | |
| <div class="coil inactive" data-type="coil" data-id="1"></div> | |
| </div> | |
| <div class="ladder-rail"></div> | |
| </div> | |
| <!-- Rung 2 --> | |
| <div class="relative mb-8"> | |
| <div class="ladder-rail"></div> | |
| <div class="ladder-rung flex items-center justify-center px-12"> | |
| <div class="contact inactive" data-type="contact-normally-closed" data-id="2"></div> | |
| <div class="wire w-16"></div> | |
| <div class="contact inactive" data-type="contact-normally-open" data-id="3"></div> | |
| <div class="wire w-16"></div> | |
| <div class="coil inactive" data-type="coil" data-id="2"></div> | |
| </div> | |
| <div class="ladder-rail"></div> | |
| </div> | |
| <!-- Empty rung template (hidden) --> | |
| <div class="hidden" id="rung-template"> | |
| <div class="relative mb-8"> | |
| <div class="ladder-rail"></div> | |
| <div class="ladder-rung flex items-center justify-center px-12"></div> | |
| <div class="ladder-rail"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-center"> | |
| <button id="add-rung" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg flex items-center"> | |
| <i data-feather="plus" class="mr-2"></i> | |
| Add Rung | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8 bg-white rounded-lg shadow-md p-4"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">I/O Monitor</h2> | |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-4"> | |
| <div class="bg-gray-100 p-4 rounded-lg"> | |
| <div class="flex justify-between items-center"> | |
| <span>Input 1</span> | |
| <div class="w-4 h-4 rounded-full bg-red-500 toggle-input" data-id="1"></div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-100 p-4 rounded-lg"> | |
| <div class="flex justify-between items-center"> | |
| <span>Input 2</span> | |
| <div class="w-4 h-4 rounded-full bg-red-500 toggle-input" data-id="2"></div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-100 p-4 rounded-lg"> | |
| <div class="flex justify-between items-center"> | |
| <span>Output 1</span> | |
| <div class="w-4 h-4 rounded-full bg-red-500" id="output-1"></div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-100 p-4 rounded-lg"> | |
| <div class="flex justify-between items-center"> | |
| <span>Output 2</span> | |
| <div class="w-4 h-4 rounded-full bg-red-500" id="output-2"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| feather.replace(); | |
| // Simple simulation logic | |
| const toggleInputs = document.querySelectorAll('.toggle-input'); | |
| const contacts = document.querySelectorAll('[data-type^="contact"]'); | |
| const coils = document.querySelectorAll('[data-type="coil"]'); | |
| const outputs = [document.getElementById('output-1'), document.getElementById('output-2')]; | |
| toggleInputs.forEach(input => { | |
| input.addEventListener('click', function() { | |
| const isActive = this.classList.contains('bg-green-500'); | |
| this.classList.toggle('bg-green-500', !isActive); | |
| this.classList.toggle('bg-red-500', isActive); | |
| updateSimulation(); | |
| }); | |
| }); | |
| function updateSimulation() { | |
| // Get input states | |
| const input1 = toggleInputs[0].classList.contains('bg-green-500'); | |
| const input2 = toggleInputs[1].classList.contains('bg-green-500'); | |
| // Update contacts based on inputs | |
| contacts.forEach(contact => { | |
| const id = contact.dataset.id; | |
| if (contact.dataset.type === 'contact-normally-open') { | |
| if ((id === '1' && input1) || (id === '3' && input2)) { | |
| contact.classList.add('active'); | |
| contact.classList.remove('inactive'); | |
| } else { | |
| contact.classList.add('inactive'); | |
| contact.classList.remove('active'); | |
| } | |
| } else if (contact.dataset.type === 'contact-normally-closed') { | |
| if ((id === '2' && !input1)) { | |
| contact.classList.add('active'); | |
| contact.classList.remove('inactive'); | |
| } else { | |
| contact.classList.add('inactive'); | |
| contact.classList.remove('active'); | |
| } | |
| } | |
| }); | |
| // Calculate outputs | |
| const output1 = document.querySelector('[data-id="1"].active') !== null; | |
| const output2 = document.querySelector('[data-id="2"].active') !== null && | |
| document.querySelector('[data-id="3"].active') !== null; | |
| // Update coils and outputs | |
| coils.forEach(coil => { | |
| const id = coil.dataset.id; | |
| if ((id === '1' && output1) || (id === '2' && output2)) { | |
| coil.classList.add('active'); | |
| coil.classList.remove('inactive'); | |
| } else { | |
| coil.classList.add('inactive'); | |
| coil.classList.remove('active'); | |
| } | |
| }); | |
| // Update output indicators | |
| outputs[0].classList.toggle('bg-green-500', output1); | |
| outputs[0].classList.toggle('bg-red-500', !output1); | |
| outputs[1].classList.toggle('bg-green-500', output2); | |
| outputs[1].classList.toggle('bg-red-500', !output2); | |
| } | |
| // Add new rung | |
| document.getElementById('add-rung').addEventListener('click', function() { | |
| const template = document.getElementById('rung-template'); | |
| const newRung = template.cloneNode(true); | |
| newRung.classList.remove('hidden'); | |
| document.getElementById('ladder-diagram').appendChild(newRung); | |
| }); | |
| // Simple drag and drop (placeholder functionality) | |
| const draggables = document.querySelectorAll('[draggable="true"]'); | |
| const workspace = document.getElementById('workspace'); | |
| draggables.forEach(draggable => { | |
| draggable.addEventListener('dragstart', function(e) { | |
| e.dataTransfer.setData('text/plain', this.dataset.type); | |
| }); | |
| }); | |
| workspace.addEventListener('dragover', function(e) { | |
| e.preventDefault(); | |
| }); | |
| workspace.addEventListener('drop', function(e) { | |
| e.preventDefault(); | |
| const type = e.dataTransfer.getData('text/plain'); | |
| // In a real implementation, you would create a new component at the drop position | |
| alert(`Dropped ${type} component. In a full implementation, this would create a new ${type} on the ladder diagram.`); | |
| }); | |
| }); | |
| </script> | |
| <script>feather.replace();</script> | |
| </body> | |
| </html> | |