diff --git a/PROJECT_OVERVIEW.html b/PROJECT_OVERVIEW.html
new file mode 100644
index 0000000..d859c7d
--- /dev/null
+++ b/PROJECT_OVERVIEW.html
@@ -0,0 +1,115 @@
+
Mini Factory Automation Simulation ā Project Overview
\ No newline at end of file
diff --git a/PROJECT_OVERVIEW.md b/PROJECT_OVERVIEW.md
new file mode 100644
index 0000000..b4e9196
--- /dev/null
+++ b/PROJECT_OVERVIEW.md
@@ -0,0 +1,162 @@
+---
+marp: true
+theme: default
+size: 16:9
+paginate: true
+class: lead
+style: |
+ section {
+ background: #0b1221;
+ color: #ffffff;
+ font-family: 'Inter', sans-serif;
+ padding: 60px;
+ }
+
+ h1, h2, h3 {
+ font-weight: 900;
+ letter-spacing: -0.5px;
+ }
+
+ h1 {
+ font-size: 3.2rem;
+ color: #7c5dff;
+ }
+
+ h2 {
+ font-size: 2.2rem;
+ color: #c1b3ff;
+ }
+
+ p {
+ font-size: 1.2rem;
+ line-height: 1.6;
+ color: #d6d6e4;
+ }
+
+ .card {
+ background: #0f1b2b;
+ padding: 20px;
+ border-radius: 18px;
+ margin: 20px 0;
+ box-shadow: 0 0 20px rgba(124, 93, 255, 0.2);
+ }
+
+ ul {
+ font-size: 1.2rem;
+ line-height: 1.7;
+ }
+
+---
+
+# š **Deep Dive Presentation**
+### Elegant. Minimal. Professional.
+
+---
+
+# ā Introduction
+Welcome to a deep and structured exploration of your topic.
+
+This presentation is designed to:
+- Give clarity
+- Build strong understanding
+- Present ideas in a polished way
+
+---
+
+# š§ Understanding the Core Concepts
+## A Strong Foundation
+
+
+Your learning begins with breaking each idea into small, digestible parts.
+
+
+We focus on:
+- Definitions
+- Purpose
+- Real-world relevance
+
+---
+
+# š Real-World Application
+### Why These Concepts Matter
+
+
+Real engineering isnāt about memorizing theoryāitās about applying it.
+
+
+Examples:
+- Scalable systems
+- Efficient problem-solving
+- Clean architecture
+
+---
+
+# āļø Technical Insight
+## Key Mechanics & How They Work
+
+This section explores:
+- Internal processes
+- Data flow
+- Performance factors
+
+---
+
+# š Visualization
+### How Everything Connects
+
+
+Use diagrams, mental models, and intuitive analogies.
+
+
+Helps you:
+- See patterns
+- Predict results
+- Build long-term understanding
+
+---
+
+# š§© Problem Breakdown
+## From Complex ā Simple
+
+Your thinking process should:
+1. Define the goal
+2. Identify constraints
+3. Break steps down
+4. Optimize solutions
+
+---
+
+# š Strategy & Best Practices
+### What You Must Always Do
+
+- Think in systems
+- Document your thought process
+- Prioritize clarity
+- Avoid unnecessary complexity
+
+---
+
+# š”ļø Common Mistakes
+## What to Avoid
+
+- Overthinking trivial details
+- Skipping fundamentals
+- Writing unreadable code
+- Forgetting performance impact
+
+---
+
+# š§ Summary
+### The Path to Mastery
+
+
+Build understanding ā Apply ā Reflect ā Improve.
+
+
+That is the cycle that makes an engineer unstoppable.
+
+---
+
+# š Thank You
+### Questions?
+### Ready for the next step?
diff --git a/automation.sqlite b/automation.sqlite
index 335fb02..3c306ec 100644
Binary files a/automation.sqlite and b/automation.sqlite differ
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..f691ac6
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,142 @@
+{
+ "name": "OOP-Mini-Factory-Automation-Simulation",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "prettier": "3.7.4",
+ "prettier-plugin-java": "2.7.7"
+ }
+ },
+ "node_modules/@chevrotain/cst-dts-gen": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
+ "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/gast": "11.0.3",
+ "@chevrotain/types": "11.0.3",
+ "lodash-es": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/gast": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
+ "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/types": "11.0.3",
+ "lodash-es": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/regexp-to-ast": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
+ "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/types": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
+ "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/utils": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
+ "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/chevrotain": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
+ "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@chevrotain/cst-dts-gen": "11.0.3",
+ "@chevrotain/gast": "11.0.3",
+ "@chevrotain/regexp-to-ast": "11.0.3",
+ "@chevrotain/types": "11.0.3",
+ "@chevrotain/utils": "11.0.3",
+ "lodash-es": "4.17.21"
+ }
+ },
+ "node_modules/chevrotain-allstar": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz",
+ "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash-es": "^4.17.21"
+ },
+ "peerDependencies": {
+ "chevrotain": "^11.0.0"
+ }
+ },
+ "node_modules/java-parser": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/java-parser/-/java-parser-3.0.1.tgz",
+ "integrity": "sha512-sDIR7u9b7O2JViNUxiZRhnRz7URII/eE7g2B+BmGxDeS6Ex3OYAcCyz5oh0H4LQ+hL/BS8OJTz8apMy9xtGmrQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "chevrotain": "11.0.3",
+ "chevrotain-allstar": "0.3.1",
+ "lodash": "4.17.21"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prettier": {
+ "version": "3.7.4",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
+ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-java": {
+ "version": "2.7.7",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-java/-/prettier-plugin-java-2.7.7.tgz",
+ "integrity": "sha512-K3N2lrdKzx2FAi67E0UOTLKybX6iitAxYGuiv/emY8v6TzzGzoaKjmhaAyDKIH5iakFqdN+xUwWoauXnE2JZPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "java-parser": "3.0.1"
+ },
+ "peerDependencies": {
+ "prettier": "^3.0.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..301bb72
--- /dev/null
+++ b/package.json
@@ -0,0 +1,6 @@
+{
+ "devDependencies": {
+ "prettier": "3.7.4",
+ "prettier-plugin-java": "2.7.7"
+ }
+}
diff --git a/pom.xml b/pom.xml
index 000a5de..81efdc3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,53 +1,88 @@
- 4.0.0
-
- org.example
- Mini-Factory-Automation-Simulation
- 1.0-SNAPSHOT
-
-
- 22
- 22
- UTF-8
-
-
-
-
- org.xerial
- sqlite-jdbc
- 3.46.0.0
-
-
-
- org.openjfx
- javafx-controls
- 21
-
-
-
-
- org.openjfx
- javafx-fxml
- 21
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.13.0
-
- 25
- 25
- 25
-
-
-
-
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+ org.example
+ Mini-Factory-Automation-Simulation
+ 1.0-SNAPSHOT
+
+
+
+ 22
+ ${java.version}
+ ${java.version}
+ UTF-8
+
+
+ org.automation.TestSensorManager
+
+
+
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.45.2.0
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+ ${project.build.sourceEncoding}
+ ${java.version}
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+ ${main.class}
+
+
+
+
+
+
diff --git a/scripts/test_input.txt b/scripts/test_input.txt
new file mode 100644
index 0000000..69d88cf
--- /dev/null
+++ b/scripts/test_input.txt
@@ -0,0 +1,8 @@
+1
+2
+LineA
+20
+0.5
+25
+1
+0
diff --git a/src/main/java/org/Automation/ConsoleUI/ConsoleApp.java b/src/main/java/org/Automation/ConsoleUI/ConsoleApp.java
deleted file mode 100644
index 5ba5601..0000000
--- a/src/main/java/org/Automation/ConsoleUI/ConsoleApp.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package ConsoleUI;
-import Sim_Engine.SimulationEngine;
-import WorkflowController.WorkflowController;
-import Logger.Logger;
-public class ConsoleApp extends ConsoleUI {
- //Instance Variables
- private SimulationEngine simulationEngine;
- private WorkflowController controller;
- private Logger logger;
- ConsoleApp(){
- simulationEngine= new SimulationEngine();
- }
-
- //Instance Methods
- public void start() {
- simulationEngine.startSimulation();
-
- }
-
- public void runMainMenu() {
-
- }
-
- public void handleUserInput(String input) {
-
- }
-
- public void printWelcomeMessage() {
-
- }
-
- public void exit() {
-
- }
-}
diff --git a/src/main/java/org/Automation/Controllers/ActuatorManager.java b/src/main/java/org/Automation/Controllers/ActuatorManager.java
new file mode 100644
index 0000000..7e91f4d
--- /dev/null
+++ b/src/main/java/org/Automation/Controllers/ActuatorManager.java
@@ -0,0 +1,5 @@
+package org.automation.controllers;
+
+public class ActuatorManager {
+
+}
diff --git a/src/main/java/org/Automation/Controllers/ItemTracker.java b/src/main/java/org/Automation/Controllers/ItemTracker.java
new file mode 100644
index 0000000..18e21ae
--- /dev/null
+++ b/src/main/java/org/Automation/Controllers/ItemTracker.java
@@ -0,0 +1,53 @@
+package org.automation.controllers;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.automation.utils.Logger;
+import org.automation.entities.ProductItem;
+
+public class ItemTracker {
+ private final Map items = new ConcurrentHashMap<>();
+ private int nextId = 1;
+ private final Logger logger;
+
+ public ItemTracker(Logger logger){
+ this.logger = logger;
+ };
+
+ public synchronized ProductItem addItem(ProductItem item) {
+ int id = nextId++;
+ item.setId(id);
+ items.put(id, item);
+ logger.log("ProductItem", id, "created", "Item created: " + item.getName());
+ return item;
+ }
+
+ public ProductItem getItem(int id) {
+ return items.get(id);
+ }
+
+ public Collection getAllItems() {
+ return Collections.unmodifiableCollection(items.values());
+ }
+
+ public boolean updateItemStatus(int id, String status) {
+ ProductItem it = items.get(id);
+ if (it == null) return false;
+ String prev = it.getStatus();
+ it.setStatus(status);
+ logger.log("ProductItem", id, "status_update", "Status changed: " + prev + " -> " + status);
+ return true;
+ }
+
+ public boolean trackMovement(int id, int stationId) {
+ ProductItem it = items.get(id);
+ if (it == null) return false;
+ int prev = it.getCurrentStationId();
+ it.setCurrentStationId(stationId);
+ logger.log("ProductItem", id, "moved", "Moved from station " + prev + " to " + stationId);
+ return true;
+ }
+}
diff --git a/src/main/java/org/Automation/Controllers/ProductionLine.java b/src/main/java/org/Automation/Controllers/ProductionLine.java
new file mode 100644
index 0000000..2b489eb
--- /dev/null
+++ b/src/main/java/org/Automation/Controllers/ProductionLine.java
@@ -0,0 +1,5 @@
+package org.automation.controllers;
+
+public class ProductionLine {
+
+}
diff --git a/src/main/java/org/Automation/Controllers/SensorManager.java b/src/main/java/org/Automation/Controllers/SensorManager.java
new file mode 100644
index 0000000..0e0e45d
--- /dev/null
+++ b/src/main/java/org/Automation/Controllers/SensorManager.java
@@ -0,0 +1,204 @@
+package org.automation.controllers;
+
+import org.automation.database.DatabaseManager;
+import org.automation.entities.Sensor;
+import org.automation.repositories.SensorRepository;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * SensorManager - Central controller for factory sensors
+ * - Add/remove sensors
+ * - Start/stop sensors
+ * - Bulk operations
+ * - Toggle automatic/control modes per sensor and in bulk
+ * - Graceful shutdown
+ */
+public class SensorManager {
+
+ private final Map sensors = new ConcurrentHashMap<>();
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+ private final DatabaseManager dbManager;
+ private final SensorRepository sensorRepo;
+
+ public SensorManager() {
+ System.out.println("SensorManager initialized");
+ // initialize database and repository
+ this.dbManager = new DatabaseManager();
+ SensorRepository repo = null;
+ try {
+ if (this.dbManager.connect()) {
+ repo = new SensorRepository(this.dbManager);
+ repo.ensureTable();
+ // load persisted sensors
+ List persisted = repo.findAll();
+ for (Sensor s : persisted) {
+ sensors.putIfAbsent(s.getSensorId(), s);
+ System.out.println("Loaded sensor from DB: " + s.getSensorInfo());
+ }
+ }
+ } catch (Exception e) {
+ System.err.println("Database init failed:");
+ e.printStackTrace();
+
+ }
+ this.sensorRepo = repo;
+ }
+
+ // -------------------------
+ // Basic sensor management
+ // -------------------------
+ public void addSensor(Sensor sensor) {
+ if (sensor == null) return;
+ sensors.putIfAbsent(sensor.getSensorId(), sensor);
+ System.out.println("Added sensor: " + sensor.getSensorInfo());
+ if (sensorRepo != null) {
+ try {
+ sensorRepo.save(sensor);
+ } catch (Exception e) {
+ System.err.println("Failed to persist sensor: " + e.getMessage());
+ }
+ }
+ }
+
+ public boolean removeSensor(int sensorId) {
+ Sensor removed = sensors.remove(sensorId);
+ if (removed != null) {
+ System.out.println("Removed sensor: " + removed.getSensorInfo());
+ if (sensorRepo != null) {
+ try {
+ sensorRepo.deleteById(sensorId);
+ } catch (Exception e) {
+ System.err.println("Failed to remove sensor from DB: " + e.getMessage());
+ }
+ }
+ return true;
+ } else {
+ System.err.println("Sensor ID " + sensorId + " not found");
+ return false;
+ }
+ }
+
+ public Sensor findSensorById(int sensorId) {
+ return sensors.get(sensorId);
+ }
+
+ // -------------------------
+ // Start / Stop a specific sensor
+ // -------------------------
+ public boolean startSensor(int sensorId) {
+ Sensor s = findSensorById(sensorId);
+ if (s == null) return false;
+ if (s.isActive()) return false;
+ try {
+ s.start();
+ return true;
+ } catch (Exception e) {
+ System.err.println("Failed to start sensor " + sensorId + ": " + e.getMessage());
+ return false;
+ }
+ }
+
+ public boolean stopSensor(int sensorId) {
+ Sensor s = findSensorById(sensorId);
+ if (s == null) return false;
+ if (!s.isActive()) return false;
+ try {
+ s.stop();
+ return true;
+ } catch (Exception e) {
+ System.err.println("Failed to stop sensor " + sensorId + ": " + e.getMessage());
+ return false;
+ }
+ }
+
+ // -------------------------
+ // Bulk operations & queries
+ // -------------------------
+ public void startAll() {
+ new ArrayList<>(sensors.values()).forEach(s -> { if (!s.isActive()) s.start(); });
+ }
+
+ public void stopAll() {
+ new ArrayList<>(sensors.values()).forEach(s -> { if (s.isActive()) s.stop(); });
+ }
+
+ public List listSensorInfo() {
+ List info = new ArrayList<>();
+ for (Sensor s : sensors.values()) info.add(s.getSensorInfo());
+ return info;
+ }
+
+ public String getSensorInfo(int sensorId) {
+ Sensor s = findSensorById(sensorId);
+ return s == null ? null : s.getSensorInfo();
+ }
+
+ // -------------------------
+ // Control / automatic toggles
+ // -------------------------
+ public boolean setSensorAutomaticMode(int sensorId, boolean enabled) {
+ Sensor s = findSensorById(sensorId);
+ if (s == null) return false;
+ if (enabled) s.enableAutomaticMode(); else s.disableAutomaticMode();
+ return true;
+ }
+
+ public boolean setSensorControl(int sensorId, boolean enabled, double target, double tolerance) {
+ Sensor s = findSensorById(sensorId);
+ if (s == null) return false;
+ if (enabled) s.enableControl(target, tolerance); else s.disableControl();
+ return true;
+ }
+
+ public boolean setAllAutomaticMode(boolean enabled) {
+ new ArrayList<>(sensors.values()).forEach(s -> { if (enabled) s.enableAutomaticMode(); else s.disableAutomaticMode(); });
+ return true;
+ }
+
+ public boolean setAllControl(boolean enabled, double target, double tolerance) {
+ new ArrayList<>(sensors.values()).forEach(s -> { if (enabled) s.enableControl(target, tolerance); else s.disableControl(); });
+ return true;
+ }
+
+ // -------------------------
+ // Async helpers
+ // -------------------------
+ public void startSensorAsync(int sensorId) {
+ executor.submit(() -> startSensor(sensorId));
+ }
+
+ public void stopSensorAsync(int sensorId) {
+ executor.submit(() -> stopSensor(sensorId));
+ }
+
+ // -------------------------
+ // Graceful shutdown
+ // -------------------------
+ public void shutdown(long timeoutSeconds) {
+ // stop sensors first
+ stopAll();
+ // shutdown executor
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(timeoutSeconds, TimeUnit.SECONDS)) {
+ executor.shutdownNow();
+ }
+ } catch (InterruptedException e) {
+ executor.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ try {
+ // shutdown the simulation clock to avoid lingering threads
+ try { org.automation.engine.SimulationClock.getInstance().shutdown(); } catch (Exception ignored) {}
+ if (dbManager != null) dbManager.disconnect();
+ } catch (Exception ignored) {}
+ System.out.println("SensorManager shutdown complete");
+ }
+}
diff --git a/src/main/java/org/Automation/Controllers/Simluators/SimulationClock.java b/src/main/java/org/Automation/Controllers/Simluators/SimulationClock.java
deleted file mode 100644
index 80eb42c..0000000
--- a/src/main/java/org/Automation/Controllers/Simluators/SimulationClock.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package Sim_Engine;
-import java.time.LocalDateTime;
-//import java.time.Duration;
-import java.util.ArrayList;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.time.temporal.ChronoUnit;
-public class SimulationClock {
- //Instance Variables
- private static SimulationClock instance;
-
- private LocalDateTime simTime;
- private LocalDateTime lastNotificationDate;
- private int speedFactor=1;
- private boolean ispaused=false;
-
- private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();//for the entire system to work on the same time
- private final int Tick_Per_MS=50;// well we set it into a task per 50 milliseconds, meaning 20 times in a second
-
- private ArrayList observers = new ArrayList();
-
- private SimulationClock(){
- this.simTime= LocalDateTime.now();
- this.lastNotificationDate=this.simTime;
-
- startIntegralTimer();
- }
-
- //Instance Methods
- public static synchronized SimulationClock getInstance() { // used to create a single instance for all tasks
- if(instance != null) {
- instance= new SimulationClock();
- }
- return instance;
- }
-
- private void startIntegralTimer() {
- scheduler.scheduleAtFixedRate(()->{
- if(!ispaused) {
- tick();
- }} , 0, Tick_Per_MS, TimeUnit.MILLISECONDS);
- }
-
- private void tick() {
-
- long MillsToAdd = Tick_Per_MS * speedFactor;
-
- simTime = simTime.plusNanos(MillsToAdd*1_000_000);
-
- long secondsAfterPreviousNotification = ChronoUnit.SECONDS.between(simTime, lastNotificationDate);
-
- if(secondsAfterPreviousNotification >=1) {
- notifyOthers();
-
- lastNotificationDate = lastNotificationDate.plusSeconds(secondsAfterPreviousNotification);
-
- }
-
- }
-
- public synchronized void register(ClockObservers observer) {
- observers.add(observer);
- }
-
- private synchronized void notifyOthers() {
- for(ClockObservers observer : observers) {
- observer.onTick(simTime);
- }
-
- }
-
- public void start() {
- ispaused=false;
- System.out.println("The Simulation has been started at time: \n"+ simTime);
- }
-
- public void stop() {
- ispaused=true;
- System.out.println("The Simulation has been stopped at time: \n"+ simTime);
- }
-
- public void setSpeedFactor(int speed) {
- speedFactor=speed;
- System.out.println("The Simulation speed has been intiated into: \n"+ speedFactor);
- }
-
- public LocalDateTime getCurrentTime() {
- return simTime;
- }
- public interface ClockObservers{
- void onTick(LocalDateTime currentTime);
- }
-}
diff --git a/src/main/java/org/Automation/Controllers/WorkFlowController.java b/src/main/java/org/Automation/Controllers/WorkFlowController.java
index 11b3bc7..56b7746 100644
--- a/src/main/java/org/Automation/Controllers/WorkFlowController.java
+++ b/src/main/java/org/Automation/Controllers/WorkFlowController.java
@@ -1,8 +1,5 @@
-package WorkflowController;
-import ProductionLine.ProductionLine;
-import SensorManager.SensorManager;
-import ActuatorManager.ActuatorManager;
-import ItemTracker.ItemTracker;
+package org.automation.controllers;
+
public class WorkflowController {
//Instance Variables
private ProductionLine productionLine;
diff --git a/src/main/java/org/Automation/DatabaseManager.java b/src/main/java/org/Automation/DatabaseManager.java
deleted file mode 100644
index 37a8727..0000000
--- a/src/main/java/org/Automation/DatabaseManager.java
+++ /dev/null
@@ -1,147 +0,0 @@
-package org.Automation;
-
-import java.sql.*;
-
-interface DatabaseManagerInterface {
-
- boolean connect();
-
- Connection getConnection();
-
- boolean disconnect();
-
- ResultSet find(String tableName, String where, Object[] params);
-
- boolean insert(String tableName, String[] columns, Object[] values);
-
- boolean delete(String tableName, String where, Object[] params);
-
- boolean update(String tableName, String setClause, String where, Object[] params);
-}
-
-public final class DatabaseManager implements DatabaseManagerInterface {
-
- private Connection connection;
- private final String url = "jdbc:sqlite:automation.sqlite";
-
- public DatabaseManager() {
- }
-
- @Override
- public boolean connect() {
- if (connection != null)
- return true;
-
- try {
- connection = DriverManager.getConnection(url);
- System.out.println("Connection to SQLite established.");
- return true;
- } catch (SQLException e) {
- throw new RuntimeException("Failed to connect", e);
- }
- }
-
- @Override
- public Connection getConnection() {
- return this.connection;
- }
-
- @Override
- public boolean disconnect() {
- try {
- if (connection != null) {
- connection.close();
- System.out.println("Connection closed.");
- }
- return true;
- } catch (SQLException e) {
- throw new RuntimeException("Failed to disconnect", e);
- }
- }
-
- // ------------ Helper method for parameter binding -------------
- private void bindParams(PreparedStatement pstmt, Object[] params) throws SQLException {
- if (params == null)
- return;
-
- for (int i = 0; i < params.length; i++) {
- pstmt.setObject(i + 1, params[i]);
- }
- }
-
- // ---------------- Helper method for SELECT queries ----------------
- private ResultSet executeQuery(String sql, Object[] params) {
- try {
- PreparedStatement pstmt = connection.prepareStatement(sql);
- bindParams(pstmt, params);
- return pstmt.executeQuery();
- } catch (SQLException e) {
- throw new RuntimeException("Query execution failed: " + e.getMessage(), e);
- }
- }
-
- // ---------------- Helper method for INSERT, UPDATE, DELETE ----------------
- private boolean executeMutator(String sql, Object[] params) {
- try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
- bindParams(pstmt, params);
- return pstmt.executeUpdate() > 0;
- } catch (SQLException e) {
- throw new RuntimeException("Mutator execution failed: " + e.getMessage(), e);
- }
- }
-
- // ------------ Find (SELECT) -------------
- @Override
- public ResultSet find(String tableName, String where, Object[] params) {
- String sql = "SELECT * FROM " + tableName;
-
- if (where != null && !where.trim().isEmpty()) {
- sql += " WHERE " + where;
- }
-
- return executeQuery(sql, params);
- }
-
- // ------------ INSERT -------------
- @Override
- public boolean insert(String tableName, String[] columns, Object[] values) {
-
- if (columns.length != values.length)
- throw new IllegalArgumentException("Columns and values length mismatch");
-
- String cols = String.join(", ", columns);
-
- // creating placeholders (?) for the statement
- String placeholders = "";
- for (int i = 0; i < values.length; i++) {
- if (i < values.length - 1) {
- placeholders += "?, ";
- } else {
- placeholders += "?";
- }
- }
-
- String sql = "INSERT INTO " + tableName + " (" + cols + ") VALUES (" + placeholders + ")";
-
- return executeMutator(sql, values);
- }
-
- // ------------ DELETE -------------
- @Override
- public boolean delete(String tableName, String where, Object[] params) {
- String sql = "DELETE FROM " + tableName +
- (where != null ? " WHERE " + where : "");
-
- return executeMutator(sql, params);
- }
-
- // ------------ UPDATE -------------
- @Override
- public boolean update(String tableName, String setClause, String where, Object[] params) {
-
- String sql = "UPDATE " + tableName + " SET " + setClause +
- (where != null ? " WHERE " + where : "");
-
- return executeMutator(sql, params);
- }
-}
diff --git a/src/main/java/org/Automation/Main.java b/src/main/java/org/Automation/Main.java
index 8fd6ea3..af30fa1 100644
--- a/src/main/java/org/Automation/Main.java
+++ b/src/main/java/org/Automation/Main.java
@@ -1,8 +1,16 @@
-package org.Automation;
+package org.automation;
+
+import org.automation.controllers.ItemTracker;
+import org.automation.entities.ProductItem;
+import org.automation.database.DatabaseManager;
+import org.automation.utils.Logger;
public class Main {
public static void main(String[] args) {
DatabaseManager db = new DatabaseManager();
db.connect();
+
+ // cleanup: disconnect DB
+ db.disconnect();
}
}
\ No newline at end of file
diff --git a/src/main/java/org/Automation/TestSensorManager.java b/src/main/java/org/Automation/TestSensorManager.java
index 6116df9..2119f02 100644
--- a/src/main/java/org/Automation/TestSensorManager.java
+++ b/src/main/java/org/Automation/TestSensorManager.java
@@ -1,114 +1,83 @@
-package org.Automation;
+package org.automation;
+
+import org.automation.controllers.SensorManager;
+import org.automation.entities.Sensor;
+import org.automation.entities.TemperatureSensor;
+import org.automation.entities.WeightSensor;
-import org.Automation.controllers.SensorManager;
-import org.Automation.entities.*;
import java.util.List;
+import java.util.Scanner;
public class TestSensorManager {
+
+ private static final Scanner scanner = new Scanner(System.in);
+
public static void main(String[] args) {
- System.out.println("=== SensorManager Test Suite ===\n");
-
- // Step 1: Create SensorManager
- System.out.println("Step 1: Creating SensorManager");
+ System.out.println("Initializing Sensor Management System...");
SensorManager manager = new SensorManager();
- System.out.println("Initial sensor count: " + manager.getSensorCount());
-
- // Step 2: Add Temperature Sensors
- System.out.println("\nStep 2: Adding Temperature Sensors");
- TemperatureSensor temp1 = new TemperatureSensor("Temperature", "Factory Floor A", "Active", 20.0, 80.0, "°C");
- TemperatureSensor temp2 = new TemperatureSensor("Temperature", "Factory Floor B", "Active", 15.0, 75.0, "°C");
-
- manager.addSensor(temp1);
- manager.addSensor(temp2);
- System.out.println("Sensor count after adding temperature sensors: " + manager.getSensorCount());
-
- // Step 3: Add Weight Sensors
- System.out.println("\nStep 3: Adding Weight Sensors");
- WeightSensor weight1 = new WeightSensor("Weight", "Conveyor Belt 1", "Active", 50.0, 100.0, "kg");
- WeightSensor weight2 = new WeightSensor("Weight", "Conveyor Belt 2", "Active", 10.0, 50.0, "kg");
-
- manager.addSensor(weight1);
- manager.addSensor(weight2);
- System.out.println("Sensor count after adding weight sensors: " + manager.getSensorCount());
-
- // Step 4: Display all sensors
- manager.displayAllSensors();
-
- // Step 5: Test sensor reading
- System.out.println("\nStep 5: Reading all sensor data (3 times)");
- for (int i = 1; i <= 3; i++) {
- System.out.println("\n--- Reading #" + i + " ---");
- manager.readAllSensorData();
-
- // Check for alerts
- List alerts = manager.getAlertSensors();
- if (!alerts.isEmpty()) {
- System.out.println("\nā ļø ALERTS DETECTED:");
- for (Sensor sensor : alerts) {
- System.out.println(" " + sensor);
- }
- }
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
+
+ boolean running = true;
+ while (running) {
+ printMenu();
+ int choice = readInt("Select option: ");
+ switch (choice) {
+ case 1 -> listSensors(manager);
+ case 2 -> addTemperatureSensor(manager);
+ case 3 -> addWeightSensor(manager);
+ case 4 -> startSensor(manager);
+ case 5 -> stopSensor(manager);
+ case 6 -> readSensorValue(manager);
+ case 7 -> toggleAutomatic(manager);
+ case 8 -> setControl(manager);
+ case 9 -> removeSensor(manager);
+ case 10 -> findById(manager);
+ case 11 -> manager.startAll();
+ case 12 -> manager.stopAll();
+ case 0 -> { manager.shutdown(5); running = false; }
+ default -> System.out.println("Unknown option.");
}
}
-
- // Step 6: Test search by type
- System.out.println("\n\nStep 6: Testing search by type");
- List tempSensors = manager.findSensorsByType("Temperature");
- System.out.println("Temperature sensors found: " + tempSensors.size());
- tempSensors.forEach(s -> System.out.println(" " + s));
-
- List weightSensors = manager.findSensorsByType("Weight");
- System.out.println("Weight sensors found: " + weightSensors.size());
- weightSensors.forEach(s -> System.out.println(" " + s));
-
- // Step 7: Test search by location
- System.out.println("\nStep 7: Testing search by location");
- List floorASensors = manager.findSensorsByLocation("Factory Floor A");
- System.out.println("Sensors at Factory Floor A: " + floorASensors.size());
- floorASensors.forEach(s -> System.out.println(" " + s));
-
- // Step 8: Test find by ID
- System.out.println("\nStep 8: Testing find by ID");
- Sensor found = manager.findSensorById(2);
- if (found != null) {
- System.out.println("Found sensor with ID 2: " + found);
- } else {
- System.out.println("Sensor with ID 2 not found");
- }
-
- // Step 9: Test calibration
- System.out.println("\nStep 9: Testing sensor calibration");
- manager.calibrateSensor(1);
- manager.calibrateAllSensors();
-
- // Step 10: Test activation/deactivation
- System.out.println("\nStep 10: Testing activation/deactivation");
- manager.deactivateAllSensors();
- manager.printSensorStatus();
-
- manager.activateAllSensors();
- manager.printSensorStatus();
-
- // Step 11: Test sensor removal
- System.out.println("\nStep 11: Testing sensor removal");
- System.out.println("Removing sensor with ID 3");
- boolean removed = manager.removeSensor(3);
- System.out.println("Removal successful: " + removed);
- System.out.println("Final sensor count: " + manager.getSensorCount());
-
- manager.displayAllSensors();
-
- // Step 12: Test edge cases
- System.out.println("\nStep 12: Testing edge cases");
- manager.addSensor(null); // Should handle null
- manager.removeSensor(999); // Should handle non-existent ID
- manager.calibrateSensor(999); // Should handle non-existent ID
-
- System.out.println("\n=== Test Complete ===");
}
-}
\ No newline at end of file
+
+ private static void printMenu() {
+ System.out.println("\n1-List 2-Add Temp 3-Add Weight 4-Start 5-Stop 6-Read 7-Auto 8-Control 9-Remove 10-Find 11-StartAll 12-StopAll 0-Exit");
+ }
+
+ private static int readInt(String prompt) {
+ System.out.print(prompt);
+ try { return Integer.parseInt(scanner.nextLine().trim()); } catch (Exception e) { return -1; }
+ }
+
+ private static double readDouble(String prompt) {
+ System.out.print(prompt);
+ try { return Double.parseDouble(scanner.nextLine().trim()); } catch (Exception e) { return Double.NaN; }
+ }
+
+ private static String prompt(String msg) { System.out.print(msg); return scanner.nextLine().trim(); }
+
+ private static void listSensors(SensorManager manager) {
+ List infos = manager.listSensorInfo();
+ infos.forEach(System.out::println);
+ }
+
+ private static void addTemperatureSensor(SensorManager manager) {
+ TemperatureSensor t = new TemperatureSensor("Temperature", prompt("Location: "), "Init", readDouble("Start: "), readDouble("Tol: "), readDouble("Target: "), "°C");
+ manager.addSensor(t);
+ }
+
+ private static void addWeightSensor(SensorManager manager) {
+ WeightSensor w = new WeightSensor("Weight", prompt("Location: "), "Init", readDouble("Initial: "), readDouble("Capacity: "), "kg");
+ manager.addSensor(w);
+ }
+
+ private static void startSensor(SensorManager manager) { System.out.println(manager.startSensor(readInt("ID: ")) ? "Started" : "Failed"); }
+ private static void stopSensor(SensorManager manager) { System.out.println(manager.stopSensor(readInt("ID: ")) ? "Stopped" : "Failed"); }
+ private static void readSensorValue(SensorManager manager) {
+ Sensor s = manager.findSensorById(readInt("ID: "));
+ if (s != null) s.readValue();
+ }
+ private static void toggleAutomatic(SensorManager manager) { manager.setSensorAutomaticMode(readInt("ID: "), readInt("0/1: ") == 1); }
+ private static void setControl(SensorManager manager) { manager.setSensorControl(readInt("ID: "), readInt("0/1: ") == 1, readDouble("Target: "), readDouble("Tol: ")); }
+ private static void removeSensor(SensorManager manager) { manager.removeSensor(readInt("ID: ")); }
+ private static void findById(SensorManager manager) { System.out.println(manager.findSensorById(readInt("ID: ")).getSensorInfo()); }
+}
diff --git a/src/main/java/org/Automation/controllers/SensorManager.java b/src/main/java/org/Automation/controllers/SensorManager.java
index 1254c0a..0e0e45d 100644
--- a/src/main/java/org/Automation/controllers/SensorManager.java
+++ b/src/main/java/org/Automation/controllers/SensorManager.java
@@ -1,261 +1,204 @@
-package org.Automation.controllers;
+package org.automation.controllers;
+
+import org.automation.database.DatabaseManager;
+import org.automation.entities.Sensor;
+import org.automation.repositories.SensorRepository;
-import org.Automation.entities.Sensor;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
/**
- * SensorManager - Manages all sensors in the factory
- * Responsibilities:
+ * SensorManager - Central controller for factory sensors
* - Add/remove sensors
- * - Update all sensor values
- * - Monitor sensor status
- * - Find sensors by ID or type
+ * - Start/stop sensors
+ * - Bulk operations
+ * - Toggle automatic/control modes per sensor and in bulk
+ * - Graceful shutdown
*/
public class SensorManager {
- private Map sensors;
- private int totalSensors;
- private int activeSensors;
-
- // Simulation clock for sensor manager
- private SimulationClock simulationClock;
+
+ private final Map sensors = new ConcurrentHashMap<>();
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+ private final DatabaseManager dbManager;
+ private final SensorRepository sensorRepo;
public SensorManager() {
- this.sensors = new HashMap<>();
-
- // Create simulation clock for sensor manager
- this.simulationClock = new SimulationClock("SensorManager", 2000) {
- @Override
- protected void tick() {
- super.tick();
- readValue();
+ System.out.println("SensorManager initialized");
+ // initialize database and repository
+ this.dbManager = new DatabaseManager();
+ SensorRepository repo = null;
+ try {
+ if (this.dbManager.connect()) {
+ repo = new SensorRepository(this.dbManager);
+ repo.ensureTable();
+ // load persisted sensors
+ List persisted = repo.findAll();
+ for (Sensor s : persisted) {
+ sensors.putIfAbsent(s.getSensorId(), s);
+ System.out.println("Loaded sensor from DB: " + s.getSensorInfo());
+ }
}
- };
-
- System.out.println("SensorManager initialized with empty sensor list");
- }
-
- // SimulationClock delegation
- public void start() { simulationClock.start(); }
- public void stop() { simulationClock.stop(); }
- public boolean isRunning() { return simulationClock.isRunning(); }
- public int getCurrentTime() { return simulationClock.getCurrentTime(); }
- public void setInterval(int intervalMs) { simulationClock.setInterval(intervalMs); }
+ } catch (Exception e) {
+ System.err.println("Database init failed:");
+ e.printStackTrace();
- // Utility methods
- public int getSensorCount() {
- return totalSensors;
- }
-
- public Map getAllSensors() {
- return new HashMap<>(sensors);
- }
-
- private void updateCounts() {
- totalSensors = sensors.size();
- activeSensors = 0;
- for (Sensor sensor : sensors.values()) {
- if (sensor.isActive()) {
- activeSensors++;
- }
}
+ this.sensorRepo = repo;
}
- public void updateValue() {
- readValue();
- }
-
- public Object getValue() {
- return getAllSensors();
- }
-
-
- // Core management methods
+ // -------------------------
+ // Basic sensor management
+ // -------------------------
public void addSensor(Sensor sensor) {
- if (sensor == null) {
- System.out.println("Error: Cannot add null sensor");
- return;
- }
-
- Integer key = sensor.getSensorId();
-
- if (sensors.containsKey(key)) {
- System.out.println("Sensor with ID " + key + " already exists");
- return;
- }
-
- sensors.put(key, sensor);
- totalSensors++;
-
- if (sensor.isActive()) {
- activeSensors++;
+ if (sensor == null) return;
+ sensors.putIfAbsent(sensor.getSensorId(), sensor);
+ System.out.println("Added sensor: " + sensor.getSensorInfo());
+ if (sensorRepo != null) {
+ try {
+ sensorRepo.save(sensor);
+ } catch (Exception e) {
+ System.err.println("Failed to persist sensor: " + e.getMessage());
+ }
}
- System.out.println("Added sensor: " + sensor);
}
public boolean removeSensor(int sensorId) {
- if (!sensors.containsKey(sensorId)) {
- System.out.println("Sensor with ID " + sensorId + " not found");
- return false;
- }
Sensor removed = sensors.remove(sensorId);
- totalSensors--;
- if (removed.isActive()) {
- activeSensors--;
- }
- System.out.println("Removed sensor: " + removed);
- return true;
- }
-
- public void activateAllSensors() {
- System.out.println("\n=== Activating All Sensors ===");
- sensors.values().forEach(sensor -> {
- sensor.activateSensor();
- if (sensor.isActive()) {
- activeSensors++;
- }
- });
- }
-
- public void deactivateAllSensors() {
- System.out.println("\n=== Deactivating All Sensors ===");
- sensors.values().forEach(sensor -> {
- sensor.deactivateSensor();
- if (!sensor.isActive()) {
- activeSensors--;
+ if (removed != null) {
+ System.out.println("Removed sensor: " + removed.getSensorInfo());
+ if (sensorRepo != null) {
+ try {
+ sensorRepo.deleteById(sensorId);
+ } catch (Exception e) {
+ System.err.println("Failed to remove sensor from DB: " + e.getMessage());
+ }
}
- });
+ return true;
+ } else {
+ System.err.println("Sensor ID " + sensorId + " not found");
+ return false;
+ }
}
- // Search methods
public Sensor findSensorById(int sensorId) {
- return sensors.get(sensorId);
+ return sensors.get(sensorId);
+ }
+
+ // -------------------------
+ // Start / Stop a specific sensor
+ // -------------------------
+ public boolean startSensor(int sensorId) {
+ Sensor s = findSensorById(sensorId);
+ if (s == null) return false;
+ if (s.isActive()) return false;
+ try {
+ s.start();
+ return true;
+ } catch (Exception e) {
+ System.err.println("Failed to start sensor " + sensorId + ": " + e.getMessage());
+ return false;
+ }
}
- public List findSensorsByType(String sensorType) {
- return sensors.get(sensorType);
+ public boolean stopSensor(int sensorId) {
+ Sensor s = findSensorById(sensorId);
+ if (s == null) return false;
+ if (!s.isActive()) return false;
+ try {
+ s.stop();
+ return true;
+ } catch (Exception e) {
+ System.err.println("Failed to stop sensor " + sensorId + ": " + e.getMessage());
+ return false;
+ }
}
- // Operations methods
- public void readValue() {
- System.out.println("\n=== Reading All Sensor Data ===");
- sensors.values().forEach(sensor -> {
- sensor.readSensorData();
- System.out.println(" " + sensor);
- });
- updateCounts();
+ // -------------------------
+ // Bulk operations & queries
+ // -------------------------
+ public void startAll() {
+ new ArrayList<>(sensors.values()).forEach(s -> { if (!s.isActive()) s.start(); });
}
- public void updateAllSensors() {
- System.out.println("\n=== Updating All Sensors ===");
- sensors.forEach((id,sensor) -> {
- sensor.updateValue();
- System.out.println(" " + sensor);
- });
- updateCounts();
+ public void stopAll() {
+ new ArrayList<>(sensors.values()).forEach(s -> { if (s.isActive()) s.stop(); });
}
- public void calibrateAllSensors() {
- System.out.println("\n=== Calibrating All Sensors ===");
- sensors.values().forEach(Sensor::calibrateSensor);
+ public List listSensorInfo() {
+ List info = new ArrayList<>();
+ for (Sensor s : sensors.values()) info.add(s.getSensorInfo());
+ return info;
}
- public void calibrateSensor(int sensorId) {
- Sensor sensor = findSensorById(sensorId);
- if (sensor != null) {
- sensor.calibrateSensor();
- } else {
- System.out.println("Sensor with ID " + sensorId + " not found for calibration");
- }
+ public String getSensorInfo(int sensorId) {
+ Sensor s = findSensorById(sensorId);
+ return s == null ? null : s.getSensorInfo();
}
- // Status monitoring
- public List getAlertSensors() {
- return sensors.values().stream()
- .filter(sensor -> sensor.getStatus().contains("Alert"))
- .toList();
+ // -------------------------
+ // Control / automatic toggles
+ // -------------------------
+ public boolean setSensorAutomaticMode(int sensorId, boolean enabled) {
+ Sensor s = findSensorById(sensorId);
+ if (s == null) return false;
+ if (enabled) s.enableAutomaticMode(); else s.disableAutomaticMode();
+ return true;
}
- public List getActiveSensors() {
- return sensors.values().stream()
- .filter(Sensor::isActive)
- .toList();
+ public boolean setSensorControl(int sensorId, boolean enabled, double target, double tolerance) {
+ Sensor s = findSensorById(sensorId);
+ if (s == null) return false;
+ if (enabled) s.enableControl(target, tolerance); else s.disableControl();
+ return true;
}
- public List getInactiveSensors() {
- return sensors.values().stream()
- .filter(sensor -> !sensor.isActive())
- .toList();
+ public boolean setAllAutomaticMode(boolean enabled) {
+ new ArrayList<>(sensors.values()).forEach(s -> { if (enabled) s.enableAutomaticMode(); else s.disableAutomaticMode(); });
+ return true;
}
- // Display methods
- public void displayAllSensors() {
- System.out.println("\n=== All Sensors ===");
- if (sensors.isEmpty()) {
- System.out.println(" No sensors registered");
- } else {
- sensors.values().forEach(sensor -> System.out.println(" " + sensor));
- }
+ public boolean setAllControl(boolean enabled, double target, double tolerance) {
+ new ArrayList<>(sensors.values()).forEach(s -> { if (enabled) s.enableControl(target, tolerance); else s.disableControl(); });
+ return true;
}
- public void printSensorStatus() {
- updateCounts();
- System.out.println("\n=== Sensor Status Summary ===");
- System.out.println("Total sensors: " + totalSensors);
- System.out.println("Active sensors: " + activeSensors);
- System.out.println("Inactive sensors: " + (totalSensors - activeSensors));
- System.out.println("Alert sensors: " + getAlertSensors().size());
+ // -------------------------
+ // Async helpers
+ // -------------------------
+ public void startSensorAsync(int sensorId) {
+ executor.submit(() -> startSensor(sensorId));
}
-
- // Temperature control methods
- public void enableTemperatureControl(int sensorId, double targetTemp, double threshold) {
- Sensor sensor = findSensorById(sensorId);
- if (sensor instanceof TemperatureSensor) {
- ((TemperatureSensor) sensor).enableTemperatureControl(targetTemp, threshold);
- } else {
- System.out.println("Sensor " + sensorId + " is not a temperature sensor");
- }
- }
-
- public void enableAllTemperatureControl(double targetTemp, double threshold) {
- List tempSensors = findSensorsByType("Temperature");
- for (Sensor sensor : tempSensors) {
- ((TemperatureSensor) sensor).enableTemperatureControl(targetTemp, threshold);
- }
+ public void stopSensorAsync(int sensorId) {
+ executor.submit(() -> stopSensor(sensorId));
}
-
- // Weight control methods
- public void enableWeightControl(int sensorId, double capacity) {
- Sensor sensor = findSensorById(sensorId);
- if (sensor instanceof WeightSensor) {
- ((WeightSensor) sensor).enableWeightControl(capacity);
- } else {
- System.out.println("Sensor " + sensorId + " is not a weight sensor");
- }
- }
-
- public boolean addWeightToSensor(int sensorId, double weight) {
- Sensor sensor = findSensorById(sensorId);
- if (sensor instanceof WeightSensor) {
- return ((WeightSensor) sensor).addWeight(weight);
- }
- System.out.println("Sensor " + sensorId + " is not a weight sensor");
- return false;
- }
-
- public void printControlStatus() {
- System.out.println("\n=== Control Status ===");
- for (Sensor sensor : sensors.values()) {
- if (sensor instanceof TemperatureSensor) {
- TemperatureSensor temp = (TemperatureSensor) sensor;
- System.out.println("Temp Sensor " + sensor.getSensorId() + ": " +
- temp.getTemperatureStatus() + " (" + temp.getCurrentValue() + "°C)");
- } else if (sensor instanceof WeightSensor) {
- WeightSensor weight = (WeightSensor) sensor;
- System.out.println("Weight Sensor " + sensor.getSensorId() + ": " +
- weight.getWeightStatus() + " (" + weight.getCurrentValue() + "kg)");
+
+ // -------------------------
+ // Graceful shutdown
+ // -------------------------
+ public void shutdown(long timeoutSeconds) {
+ // stop sensors first
+ stopAll();
+ // shutdown executor
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(timeoutSeconds, TimeUnit.SECONDS)) {
+ executor.shutdownNow();
}
+ } catch (InterruptedException e) {
+ executor.shutdownNow();
+ Thread.currentThread().interrupt();
}
+ try {
+ // shutdown the simulation clock to avoid lingering threads
+ try { org.automation.engine.SimulationClock.getInstance().shutdown(); } catch (Exception ignored) {}
+ if (dbManager != null) dbManager.disconnect();
+ } catch (Exception ignored) {}
+ System.out.println("SensorManager shutdown complete");
}
}
diff --git a/src/main/java/org/Automation/controllers/SimulationClock.observerclass.java b/src/main/java/org/Automation/controllers/SimulationClock.observerclass.java
deleted file mode 100644
index 6ef253a..0000000
--- a/src/main/java/org/Automation/controllers/SimulationClock.observerclass.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.Automation.controllers;
-
-import java.time.LocalDateTime;
-
-/**
- * ClockObserver - Interface for receiving simulation clock notifications
- */
-public interface ClockObserver {
- void onTick(LocalDateTime currentTime);
-}
-
-
diff --git a/src/main/java/org/Automation/database/DatabaseManager.java b/src/main/java/org/Automation/database/DatabaseManager.java
new file mode 100644
index 0000000..7dbe56b
--- /dev/null
+++ b/src/main/java/org/Automation/database/DatabaseManager.java
@@ -0,0 +1,153 @@
+package org.automation.database;
+
+import java.sql.*;
+
+public final class DatabaseManager implements AutoCloseable {
+
+ private Connection connection;
+ private static final String URL = "jdbc:sqlite:automation.sqlite";
+
+ /**
+ * Connect to SQLite database
+ */
+ public boolean connect() {
+ if (connection != null) return true;
+
+ try {
+ // š“ REQUIRED: force SQLite JDBC driver to load
+ Class.forName("org.sqlite.JDBC");
+
+ connection = DriverManager.getConnection(URL);
+ System.out.println("Connection to SQLite established.");
+ return true;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to connect", e);
+ }
+ }
+
+ public Connection getConnection() {
+ if (connection == null) {
+ throw new IllegalStateException("Database not connected");
+ }
+ return connection;
+ }
+
+ /**
+ * Close DB connection
+ */
+ public boolean disconnect() {
+ try {
+ if (connection != null && !connection.isClosed()) {
+ connection.close();
+ System.out.println("Connection closed.");
+ }
+ connection = null;
+ return true;
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to disconnect", e);
+ }
+ }
+
+ /* ---------------- PARAM BINDING ---------------- */
+
+ private void bindParams(PreparedStatement pstmt, Object[] params) throws SQLException {
+ if (params == null) return;
+ for (int i = 0; i < params.length; i++) {
+ pstmt.setObject(i + 1, params[i]);
+ }
+ }
+
+ /* ---------------- SELECT HELPERS ---------------- */
+
+ public java.util.List query(
+ String tableName,
+ String where,
+ Object[] params,
+ RowMapper mapper
+ ) {
+ String sql = "SELECT * FROM " + tableName +
+ (where != null && !where.trim().isEmpty() ? " WHERE " + where : "");
+
+ java.util.List result = new java.util.ArrayList<>();
+
+ try (PreparedStatement pstmt = getConnection().prepareStatement(sql)) {
+ bindParams(pstmt, params);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ while (rs.next()) {
+ result.add(mapper.mapRow(rs));
+ }
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Query execution failed", e);
+ }
+
+ return result;
+ }
+
+ public T queryOne(
+ String tableName,
+ String where,
+ Object[] params,
+ RowMapper mapper
+ ) {
+ java.util.List list = query(tableName, where, params, mapper);
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ /* ---------------- DDL ---------------- */
+
+ public boolean executeDDL(String sql) {
+ try (Statement stmt = getConnection().createStatement()) {
+ stmt.execute(sql);
+ return true;
+ } catch (SQLException e) {
+ throw new RuntimeException("DDL execution failed", e);
+ }
+ }
+
+ /* ---------------- MUTATORS ---------------- */
+
+ private boolean executeMutator(String sql, Object[] params) {
+ if (connection == null) connect();
+ try (PreparedStatement pstmt = getConnection().prepareStatement(sql)) {
+ bindParams(pstmt, params);
+ return pstmt.executeUpdate() > 0;
+ } catch (SQLException e) {
+ throw new RuntimeException("Mutator execution failed", e);
+ }
+ }
+
+
+ public boolean insert(String tableName, String[] columns, Object[] values) {
+ if (columns.length != values.length) {
+ throw new IllegalArgumentException("Columns and values length mismatch");
+ }
+
+ String cols = String.join(", ", columns);
+ String placeholders = String.join(", ", java.util.Collections.nCopies(values.length, "?"));
+
+ String sql = "INSERT INTO " + tableName +
+ " (" + cols + ") VALUES (" + placeholders + ")";
+
+ return executeMutator(sql, values);
+ }
+
+ public boolean update(String tableName, String setClause, String where, Object[] params) {
+ String sql = "UPDATE " + tableName + " SET " + setClause +
+ (where != null ? " WHERE " + where : "");
+ return executeMutator(sql, params);
+ }
+
+ public boolean delete(String tableName, String where, Object[] params) {
+ String sql = "DELETE FROM " + tableName +
+ (where != null ? " WHERE " + where : "");
+ return executeMutator(sql, params);
+ }
+
+ /* ---------------- CLEANUP ---------------- */
+
+ @Override
+ public void close() {
+ disconnect();
+ }
+}
diff --git a/src/main/java/org/Automation/engine/ClockObserver.java b/src/main/java/org/Automation/engine/ClockObserver.java
new file mode 100644
index 0000000..9777b4b
--- /dev/null
+++ b/src/main/java/org/Automation/engine/ClockObserver.java
@@ -0,0 +1,8 @@
+package org.automation.engine;
+
+import java.time.LocalDateTime;
+
+public interface ClockObserver {
+ void onTick(LocalDateTime currentTime);
+}
+
diff --git a/src/main/java/org/Automation/engine/SimulationClock.java b/src/main/java/org/Automation/engine/SimulationClock.java
new file mode 100644
index 0000000..8147d40
--- /dev/null
+++ b/src/main/java/org/Automation/engine/SimulationClock.java
@@ -0,0 +1,143 @@
+package org.automation.engine;
+
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * SimulationClock
+ *
+ * - Singleton clock used by sensors
+ * - Notifies registered ClockObserver instances once per simulated second
+ * - Exposes getTickIntervalMs() for sensors to adapt increments
+ */
+public class SimulationClock {
+
+ // Singleton instance
+ private static SimulationClock instance;
+
+ // Simulation time state
+ private LocalDateTime simTime;
+ private LocalDateTime lastNotificationDate;
+
+ // Speed and pause control
+ private volatile int speedFactor = 1;
+ private volatile boolean isPaused = false;
+
+ // Scheduler and tick configuration
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread t = new Thread(r, "SimulationClock");
+ t.setDaemon(true);
+ return t;
+ });
+ private static final int TICK_PER_MS = 50; // base tick in milliseconds
+
+ // Observers (thread-safe for iteration)
+ private final CopyOnWriteArrayList observers = new CopyOnWriteArrayList<>();
+
+ private SimulationClock() {
+ this.simTime = LocalDateTime.now();
+ this.lastNotificationDate = this.simTime;
+ startIntegralTimer();
+ }
+
+ public static synchronized SimulationClock getInstance() {
+ if (instance == null) {
+ instance = new SimulationClock();
+ }
+ return instance;
+ }
+
+ private void startIntegralTimer() {
+ scheduler.scheduleAtFixedRate(() -> {
+ if (!isPaused) {
+ tick();
+ }
+ }, 0, TICK_PER_MS, TimeUnit.MILLISECONDS);
+ }
+
+ private void tick() {
+ long millisToAdd = (long) TICK_PER_MS * speedFactor;
+ simTime = simTime.plusNanos(millisToAdd * 1_000_000L);
+
+ long secondsAfterPreviousNotification = ChronoUnit.SECONDS.between(lastNotificationDate, simTime);
+ if (secondsAfterPreviousNotification >= 1) {
+ notifyObservers();
+ lastNotificationDate = lastNotificationDate.plusSeconds(secondsAfterPreviousNotification);
+ }
+ }
+
+ public synchronized void register(ClockObserver observer) {
+ if (observer != null && !observers.contains(observer)) {
+ observers.add(observer);
+ }
+ }
+
+ public synchronized void unregister(ClockObserver observer) {
+ observers.remove(observer);
+ }
+
+ private void notifyObservers() {
+ for (ClockObserver observer : observers) {
+ try {
+ observer.onTick(simTime);
+ } catch (Throwable t) {
+ // keep clock running even if an observer throws
+ t.printStackTrace();
+ }
+ }
+ }
+
+ public synchronized void start() {
+ isPaused = false;
+ System.out.println("The Simulation has been started at time:\n" + simTime);
+ }
+
+ public synchronized void stop() {
+ isPaused = true;
+ System.out.println("The Simulation has been stopped at time:\n" + simTime);
+ }
+
+ public void setSpeedFactor(int speed) {
+ if (speed <= 0) throw new IllegalArgumentException("Speed must be > 0");
+ this.speedFactor = speed;
+ System.out.println("Simulation speed set to: " + speedFactor);
+ }
+
+ public LocalDateTime getCurrentTime() {
+ return simTime;
+ }
+
+ /**
+ * Method expected by sensors: returns effective tick interval in milliseconds.
+ */
+ public int getTickIntervalMs() {
+ return TICK_PER_MS * speedFactor;
+ }
+
+ /**
+ * Graceful shutdown for the scheduler (call at application exit).
+ */
+ public synchronized void shutdown() {
+ try {
+ // request orderly shutdown
+ scheduler.shutdown();
+ // wait briefly for tasks to finish
+ if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
+ // force shutdown if not finished
+ scheduler.shutdownNow();
+ if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
+ System.err.println("SimulationClock did not terminate");
+ }
+ }
+ } catch (InterruptedException ie) {
+ scheduler.shutdownNow();
+ Thread.currentThread().interrupt();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/org/Automation/Controllers/Simluators/SimulationEngine.java b/src/main/java/org/Automation/engine/SimulationEngine.java
similarity index 84%
rename from src/main/java/org/Automation/Controllers/Simluators/SimulationEngine.java
rename to src/main/java/org/Automation/engine/SimulationEngine.java
index c2dfc57..026a6b7 100644
--- a/src/main/java/org/Automation/Controllers/Simluators/SimulationEngine.java
+++ b/src/main/java/org/Automation/engine/SimulationEngine.java
@@ -1,8 +1,7 @@
-package Sim_Engine;
-
+package org.automation.engine;
import java.time.LocalDateTime;
-
-import WorkflowController.WorkflowController;
+import org.automation.controllers.WorkflowController;
+import org.automation.utils.Logger;
public class SimulationEngine {
//Instance Variables
diff --git a/src/main/java/org/Automation/entities/ConveyorBelt.java b/src/main/java/org/Automation/entities/ConveyorBelt.java
index ea268c6..91df915 100644
--- a/src/main/java/org/Automation/entities/ConveyorBelt.java
+++ b/src/main/java/org/Automation/entities/ConveyorBelt.java
@@ -1,4 +1,4 @@
-package org.Automation.entities;
+package org.automation.entities;
public class ConveyorBelt {
public int id;
diff --git a/src/main/java/org/Automation/entities/EventLog.java b/src/main/java/org/Automation/entities/EventLog.java
deleted file mode 100644
index 50b6f9e..0000000
--- a/src/main/java/org/Automation/entities/EventLog.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.Automation.entities;
-
-public class EventLog {
- public int id;
- public String timestamp;
- public String componentType;
- public int componentId;
- public String eventType;
- public String message;
-
- public EventLog(int id, String timestamp, String componentType, int componentId, String eventType, String message) {
- this.id = id;
- this.timestamp = timestamp;
- this.componentType = componentType;
- this.componentId = componentId;
- this.eventType = eventType;
- this.message = message;
- }
-
- @Override
- public String toString() {
- return "EventLog{id=" + id + ", timestamp=" + timestamp + ", componentType=" + componentType + ", componentId=" + componentId + ", eventType=" + eventType + ", message=" + message + "}";
- }
-}
diff --git a/src/main/java/org/Automation/entities/Machine.java b/src/main/java/org/Automation/entities/Machine.java
index 9582678..a6e3aaf 100644
--- a/src/main/java/org/Automation/entities/Machine.java
+++ b/src/main/java/org/Automation/entities/Machine.java
@@ -1,4 +1,4 @@
-package org.Automation.entities;
+package org.automation.entities;
public class Machine {
public int id;
diff --git a/src/main/java/org/Automation/entities/Product.java b/src/main/java/org/Automation/entities/Product.java
index d50a76d..6e80551 100644
--- a/src/main/java/org/Automation/entities/Product.java
+++ b/src/main/java/org/Automation/entities/Product.java
@@ -1,18 +1,12 @@
-package org.Automation.entities;
+package org.automation.entities;
-public class Product {
- public int id;
- public String name;
- public String status;
-
- public Product(int id, String name, String status) {
- this.id = id;
- this.name = name;
- this.status = status;
- }
-
- @Override
- public String toString() {
- return "Product{id=" + id + ", name=" + name + ", status=" + status + "}";
- }
+/**
+ * DEPRECATED: Replaced by `ProductItem`.
+ *
+ * This class was kept for compatibility but should not be used anymore.
+ */
+@Deprecated
+public final class Product {
+ private Product() {
+ /* prevent instantiation */ }
}
diff --git a/src/main/java/org/Automation/entities/ProductItem.java b/src/main/java/org/Automation/entities/ProductItem.java
new file mode 100644
index 0000000..4c6d49a
--- /dev/null
+++ b/src/main/java/org/Automation/entities/ProductItem.java
@@ -0,0 +1,74 @@
+package org.automation.entities;
+
+public class ProductItem {
+ public int id;
+ public String name;
+ public double weight;
+ public String status;
+ public int currentStationId;
+ public String createdAt;
+
+ public ProductItem(int id, String name, double weight, String status, String createdAt, int currentStationId) {
+ this.id = id;
+ this.name = name;
+ this.weight = weight;
+ this.status = status;
+ this.createdAt = createdAt;
+ this.currentStationId = currentStationId;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public double getWeight() {
+ return weight;
+ }
+
+ public void setWeight(double weight) {
+ this.weight = weight;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(String createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public int getCurrentStationId() {
+ return currentStationId;
+ }
+
+ public void setCurrentStationId(int currentStationId) {
+ this.currentStationId = currentStationId;
+ }
+
+ @Override
+ public String toString() {
+ return "ProductItem{id=" + id + ", name=" + name + ", weight=" + weight + ", status=" + status + ", createdAt="
+ + createdAt + ", currentStationId=" + currentStationId + "}";
+ }
+}
+
diff --git a/src/main/java/org/Automation/entities/Sensor.java b/src/main/java/org/Automation/entities/Sensor.java
index 5f4aead..de1769b 100644
--- a/src/main/java/org/Automation/entities/Sensor.java
+++ b/src/main/java/org/Automation/entities/Sensor.java
@@ -1,81 +1,198 @@
-package org.Automation.entities;
+package org.automation.entities;
+import java.time.LocalDateTime;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Abstract Sensor base class.
+ * - Centralizes lifecycle bookkeeping and status management
+ * - Final template method for interval -> increments mapping
+ * - Provides lightweight automatic/control flags and APIs used by concrete sensors
+ *
+ * Thread-safety:
+ * - start()/stop() are synchronized to protect lifecycle transitions.
+ * - primaryIncrement and coolingRate are volatile to ensure visibility across threads.
+ */
public abstract class Sensor {
- private static int idCounter = 0;
-
- protected int sensorId;
- protected String sensorType;
+ private static final AtomicInteger ID_GEN = new AtomicInteger(1);
+
+ // identity / metadata
+ protected final int sensorId;
+ protected final String sensorType;
protected String location;
protected String status;
- protected double currentValue;
- protected boolean isActive;
- // Constructor with auto-increment ID
- public Sensor(String sensorType, String location, String status) {
- this.sensorId = ++idCounter;
+ // lifecycle
+ protected volatile boolean isActive;
+ protected LocalDateTime startTime;
+ protected LocalDateTime stopTime;
+
+ // shared, clock-driven increments
+ protected volatile double primaryIncrement;
+ protected volatile double coolingRate;
+
+ // control / automation flags (default: automatic and control enabled)
+ protected volatile boolean automaticMode = true;
+ protected volatile boolean controlEnabled = true;
+ protected volatile double controlTarget = 0.0;
+ protected volatile double controlTolerance = 0.0;
+
+ protected Sensor(String sensorType, String location, String status) {
+ this.sensorId = ID_GEN.getAndIncrement();
this.sensorType = sensorType;
this.location = location;
this.status = status;
this.isActive = false;
- this.currentValue = 0.0;
+ this.primaryIncrement = getBaselineIncrement();
+ this.coolingRate = getBaselineCooling();
}
- // Constructor with manual ID
- public Sensor(int sensorId, String sensorType, String location, String status) {
- this.sensorId = sensorId;
+ /**
+ * Construct sensor with a forced id (used when loading from persistent storage).
+ * Ensures the global ID generator is advanced past the forced id to avoid collisions.
+ */
+ protected Sensor(int forcedId, String sensorType, String location, String status) {
+ this.sensorId = forcedId;
this.sensorType = sensorType;
this.location = location;
this.status = status;
this.isActive = false;
- this.currentValue = 0.0;
- if (sensorId > idCounter) {
- idCounter = sensorId;
+ this.primaryIncrement = getBaselineIncrement();
+ this.coolingRate = getBaselineCooling();
+ // advance ID generator to avoid future collisions
+ int next = forcedId + 1;
+ while (true) {
+ int cur = ID_GEN.get();
+ if (cur >= next) break;
+ if (ID_GEN.compareAndSet(cur, next)) break;
}
}
- // Abstract methods from diagram
- public abstract void readValue();
- public abstract void calibrateSensor();
- public abstract boolean validateReading();
- public abstract void sendAlert();
- public abstract void updateValue();
- public abstract Object getValue();
-
- // Concrete methods
- public void activateSensor() {
- this.isActive = true;
- this.status = "Active";
- System.out.println("Sensor " + sensorId + " activated");
- }
-
- public void deactivateSensor() {
- this.isActive = false;
- this.status = "Inactive";
- System.out.println("Sensor " + sensorId + " deactivated");
+ // -----------------------
+ // Lifecycle (centralized)
+ // -----------------------
+ public synchronized void start() {
+ if (isActive) throw new IllegalStateException("Sensor " + sensorId + " is already active.");
+ isActive = true;
+ startTime = LocalDateTime.now();
+ stopTime = null;
+ setStatus("Active");
+ onStart();
}
-
- public void updateStatus(String newStatus) {
- this.status = newStatus;
+
+ public synchronized void stop() {
+ if (!isActive) return;
+ isActive = false;
+ stopTime = LocalDateTime.now();
+ onStop();
+ setStatus("Stopped");
}
-
- // Getters and Setters
+
+ protected void activateSensor() { isActive = true; setStatus("Active"); }
+ protected void deactivateSensor() { isActive = false; setStatus("Paused"); }
+
+ public boolean isActive() { return isActive; }
public int getSensorId() { return sensorId; }
public String getSensorType() { return sensorType; }
public String getLocation() { return location; }
public String getStatus() { return status; }
- public double getCurrentValue() { return currentValue; }
- public boolean isActive() { return isActive; }
-
- public void setSensorId(int sensorId) { this.sensorId = sensorId; }
- public void setSensorType(String sensorType) { this.sensorType = sensorType; }
- public void setLocation(String location) { this.location = location; }
- public void setStatus(String status) { this.status = status; }
- public void setCurrentValue(double currentValue) { this.currentValue = currentValue; }
- public void setActive(boolean active) { this.isActive = active; }
-
- @Override
- public String toString() {
- return "Sensor{id=" + sensorId + ", type=" + sensorType + ", location=" + location +
- ", status=" + status + ", value=" + currentValue + ", active=" + isActive + "}";
+
+ // -----------------------
+ // Status helpers
+ // -----------------------
+ protected synchronized void setStatus(String newStatus) {
+ this.status = newStatus;
+ }
+
+ /**
+ * Update status after a read. Keeps statuses consistent across sensors.
+ * @param valid whether the last reading validated successfully
+ */
+ protected synchronized void updateStatusAfterRead(boolean valid) {
+ if (!isActive) setStatus("Stopped");
+ else if (!valid) setStatus("Error");
+ else setStatus("OK");
+ }
+
+ // -----------------------
+ // Control / automation API
+ // -----------------------
+ public void enableAutomaticMode() { this.automaticMode = true; setStatus("AutoMode"); }
+ public void disableAutomaticMode() { this.automaticMode = false; setStatus("ManualMode"); }
+ public boolean isAutomaticMode() { return automaticMode; }
+
+ public void enableControl(double target, double tolerance) {
+ this.controlTarget = target;
+ this.controlTolerance = tolerance;
+ this.controlEnabled = true;
+ setStatus("ControlEnabled");
+ }
+
+ public void disableControl() {
+ this.controlEnabled = false;
+ setStatus("ControlDisabled");
+ }
+
+ public boolean isControlEnabled() { return controlEnabled; }
+ public double getControlTarget() { return controlTarget; }
+ public double getControlTolerance() { return controlTolerance; }
+
+ // -----------------------
+ // Interval -> increment mapping (single place)
+ // -----------------------
+ /**
+ * Final template method mapping clock interval -> increments.
+ */
+ public final void setSimulationInterval(int intervalMs) {
+ double baseline = getBaselineIncrement();
+ double baselineCooling = getBaselineCooling();
+
+ if (intervalMs <= 1000) {
+ primaryIncrement = baseline;
+ coolingRate = baselineCooling;
+ } else if (intervalMs <= 2000) {
+ primaryIncrement = baseline * 2.0;
+ coolingRate = baselineCooling * 2.0;
+ } else {
+ primaryIncrement = baseline * 4.0;
+ coolingRate = baselineCooling * 4.0;
+ }
+
+ onIntervalUpdated(intervalMs);
+ }
+
+ // Subclass hooks
+ protected abstract double getBaselineIncrement();
+ protected double getBaselineCooling() { return 0.5; }
+ protected void onIntervalUpdated(int intervalMs) { /* optional override */ }
+ protected void onStart() { /* optional override */ }
+ protected void onStop() { /* optional override */ }
+
+ // -----------------------
+ // Shared helpers
+ // -----------------------
+ protected void setCurrentValue(Object value) { /* telemetry hook */ }
+
+ protected void raiseAlert(String title, String details) {
+ setStatus("Error");
+ System.err.println("ALERT [" + sensorId + "] " + title + " - " + details);
+ }
+
+ // -----------------------
+ // Abstract contract
+ // -----------------------
+ public abstract Object getValue();
+ public abstract void readValue();
+ public abstract void updateValue(double change);
+ public abstract void calibrateSensor();
+ public abstract boolean validateReading();
+ public abstract void sendAlert(double currentValue);
+ public abstract String getSensorInfo();
+ public abstract void performCycle();
+
+ // helper to manually adjust next id (rarely needed)
+ public static void setNextId(int next) {
+ if (next <= 0) return;
+ ID_GEN.set(next);
}
}
diff --git a/src/main/java/org/Automation/entities/TemperatureSensor.java b/src/main/java/org/Automation/entities/TemperatureSensor.java
index e8428f6..c8f63a9 100644
--- a/src/main/java/org/Automation/entities/TemperatureSensor.java
+++ b/src/main/java/org/Automation/entities/TemperatureSensor.java
@@ -1,223 +1,276 @@
-package org.Automation.entities;
+package org.automation.entities;
+
+import org.automation.engine.SimulationClock;
+import org.automation.engine.ClockObserver;
-import org.Automation.controllers.SimulationClock;
import java.time.LocalDateTime;
-import java.util.Timer;
-import java.util.TimerTask;
-public class TemperatureSensor extends Sensor implements SimulationClock.ClockObserver {
-
- // ========== CORE TEMPERATURE FIELDS ==========
+/**
+ * TemperatureSensor
+ * - Implements baseline increment and cooling baseline
+ * - Registers/unregisters with SimulationClock in onStart/onStop hooks
+ * - Uses primaryIncrement and coolingRate set by Sensor.setSimulationInterval(...)
+ * - Uses Sensor's automaticMode and controlEnabled flags; provides a convenience wrapper
+ */
+public class TemperatureSensor extends Sensor implements ClockObserver {
+
private double currentTemperature;
- private double minTemperature;
- private double maxTemperature;
- private String temperatureUnit;
+ private final String temperatureUnit;
private double calibrationOffset;
- private double temperatureIncrement;
-
- // ========== CONTROL SYSTEM FIELDS ==========
+ private boolean calibrated = false;
+
private double targetTemperature;
- private double temperatureThreshold;
+ private double temperatureTolerance;
private boolean temperatureControlEnabled;
-
- // ========== SIMULATION FIELDS ==========
- private double startThreshold;
+
+ private final double startThreshold;
private boolean maxReached;
- private double coolingRate;
private LocalDateTime lastActionTime;
- // ========== CONSTRUCTOR ==========
- public TemperatureSensor(String sensorType, String location, String status,
- double minTemperature, double maxTemperature, String temperatureUnit) {
+ private SimulationClock simulationClock;
+
+ public TemperatureSensor(String sensorType, String location, String status,
+ double startThreshold, double temperatureTolerance,
+ double targetTemperature, String temperatureUnit) {
super(sensorType, location, status);
- this.minTemperature = minTemperature;
- this.maxTemperature = maxTemperature;
+ this.startThreshold = startThreshold;
+ this.temperatureTolerance = temperatureTolerance;
+ this.targetTemperature = targetTemperature;
this.temperatureUnit = temperatureUnit;
- this.currentTemperature = minTemperature;
-
- // Register with global simulation clock
- SimulationClock.getInstance().register(this);
+ this.currentTemperature = startThreshold;
+ this.maxReached = false;
+ // initialize control target/tolerance in base
+ this.controlTarget = targetTemperature;
+ this.controlTolerance = temperatureTolerance;
}
- public TemperatureSensor(String sensorType, int location, String status,
- double minTemperature, double maxTemperature) {
- this(sensorType, String.valueOf(location), status, minTemperature, maxTemperature, "°C");
+ /**
+ * Construct a TemperatureSensor with an explicit sensor id (used when loading from DB).
+ */
+ public TemperatureSensor(int sensorId, String sensorType, String location, String status,
+ double startThreshold, double temperatureTolerance,
+ double targetTemperature, String temperatureUnit) {
+ super(sensorId, sensorType, location, status);
+ this.startThreshold = startThreshold;
+ this.temperatureTolerance = temperatureTolerance;
+ this.targetTemperature = targetTemperature;
+ this.temperatureUnit = temperatureUnit;
+ this.currentTemperature = startThreshold;
+ this.maxReached = false;
+ this.controlTarget = targetTemperature;
+ this.controlTolerance = temperatureTolerance;
}
- @Override
- public void onTick(LocalDateTime currentTime) {
- if (lastActionTime == null || currentTime.minusSeconds(5).isAfter(lastActionTime)) {
- if (isActive() && !maxReached) {
- performTemperatureCycle();
- }
- lastActionTime = currentTime;
- }
+ public TemperatureSensor(String sensorType, int location, String status,
+ double startThreshold, double temperatureTolerance,
+ double targetTemperature) {
+ this(sensorType, String.valueOf(location), status, startThreshold, temperatureTolerance, targetTemperature, "°C");
}
- // ========== GETTERS ==========
- public double getCurrentTemperature() { return currentTemperature; }
- public String getTemperatureUnit() { return temperatureUnit; }
- public double getTargetTemperature() { return targetTemperature; }
- public boolean isTemperatureControlEnabled() { return temperatureControlEnabled; }
+ @Override
+ protected double getBaselineIncrement() { return 0.5; }
- public String getSensorInfo() {
- return String.format("TemperatureSensor[ID=%d, Type=%s, Location=%s, Status=%s, " +
- "Current=%.2f%s, Range=%.1f-%.1f%s, Target=%.1f%s, Control=%s]",
- getSensorId(), getSensorType(), getLocation(), getStatus(),
- currentTemperature, temperatureUnit,
- minTemperature, maxTemperature, temperatureUnit,
- targetTemperature, temperatureUnit,
- temperatureControlEnabled ? "Enabled" : "Disabled");
+ @Override
+ protected double getBaselineCooling() { return 0.25; }
+
+ @Override
+ protected void onStart() {
+ this.simulationClock = SimulationClock.getInstance();
+ this.simulationClock.register(this);
+ // initialize increments based on current clock interval so first cycle is aligned
+ try { setSimulationInterval(this.simulationClock.getTickIntervalMs()); } catch (Exception ignored) {}
+ calibrateSensor();
+ activateSensor();
+ maxReached = false;
+ lastActionTime = null;
+ // sync subclass target with base control fields
+ this.controlTarget = this.targetTemperature;
+ this.controlTolerance = this.temperatureTolerance;
+ enableTemperatureControl(this.targetTemperature, this.temperatureTolerance);
+ updateValue(0);
+ setStatus("Active");
+ System.out.println("š”ļø TemperatureSensor " + getSensorId() + " started at " + currentTemperature + temperatureUnit);
}
- // ========== ABSTRACT METHOD IMPLEMENTATIONS ==========
@Override
- public void readValue() {
- readSensorData();
+ protected void onStop() {
+ try { simulationClock.unregister(this); } catch (Exception ignored) {}
+ deactivateSensor();
+ temperatureControlEnabled = false;
+ lastActionTime = null;
+ calibrated = false;
+ updateValue(0);
+ setStatus("Stopped");
+ System.out.println("š TemperatureSensor " + getSensorId() + " stopped at " + currentTemperature + temperatureUnit);
}
@Override
- public void updateValue() {
- simulateTemperature();
+ public void onTick(LocalDateTime currentTime) {
+ synchronized (this) {
+ if (!isActive()) return;
+ if (!automaticMode) return; // gate automatic behavior
+ if (lastActionTime == null || currentTime.minusSeconds(5).isAfter(lastActionTime)) {
+ try {
+ int intervalMs = SimulationClock.getInstance().getTickIntervalMs();
+ setSimulationInterval(intervalMs);
+ if (controlEnabled && temperatureControlEnabled) {
+ performCycle();
+ } else {
+ // passive behavior: small drift or no-op; here we apply primaryIncrement as passive growth
+ updateValue(primaryIncrement);
+ }
+ } catch (Exception e) {
+ sendAlert(currentTemperature + calibrationOffset);
+ setStatus("Error");
+ }
+ lastActionTime = currentTime;
+ }
+ }
}
+ // -----------------------
+ // Abstract implementations
+ // -----------------------
@Override
- public Object getValue() {
- return currentTemperature;
+ public Object getValue() { return currentTemperature; }
+
+ @Override
+ public void readValue() {
+ calibrateSensor();
+ double calibratedTemp = currentTemperature + calibrationOffset;
+ System.out.println(getSensorInfo() + " | Calibrated Temperature: " + String.format("%.2f%s", calibratedTemp, temperatureUnit));
+ boolean valid = validateReading(calibratedTemp);
+ if (!valid) sendAlert(calibratedTemp);
+ updateStatusAfterRead(valid);
+ System.out.println("ā” Status: " + getTemperatureStatus(calibratedTemp));
+ }
+
+ @Override
+ public void updateValue(double change) {
+ simulateTemperature(change);
+ readValue();
}
@Override
public void calibrateSensor() {
- System.out.println("š§ Calibrating temperature sensor " + getSensorId());
- this.calibrationOffset = 0.5;
+ if (!calibrated) {
+ setStatus("Calibrating");
+ System.out.println("š§ Calibrating temperature sensor " + getSensorId());
+ this.calibrationOffset = 0.05 + Math.random() * 0.1;
+ calibrated = true;
+ setStatus("OK");
+ }
}
@Override
public boolean validateReading() {
- return currentTemperature >= minTemperature && currentTemperature <= maxTemperature;
+ return validateReading(currentTemperature + calibrationOffset);
+ }
+
+ public boolean validateReading(double calibratedTemp) {
+ return Math.abs(calibratedTemp - targetTemperature) <= temperatureTolerance;
}
@Override
- public void sendAlert() {
- System.out.println("šØ TEMPERATURE ALERT - Sensor " + getSensorId() +
- ": " + currentTemperature + temperatureUnit);
- updateStatus("Alert");
+ public void sendAlert(double currentTemp) {
+ double calibratedTemp = currentTemp + calibrationOffset;
+ raiseAlert("Temperature out of range", temperatureUnit + " (Calibrated: " + String.format("%.2f", calibratedTemp) + ")");
}
- // ========== CORE TEMPERATURE METHODS ==========
- public void readSensorData() {
- double temp = simulateTemperature();
- this.currentTemperature = temp + calibrationOffset;
- setCurrentValue(currentTemperature);
-
- System.out.println(getSensorInfo());
-
- if (!validateReading()) {
- sendAlert();
- }
+ @Override
+ public String getSensorInfo() {
+ return String.format(
+ "TemperatureSensor[ID=%d, Type=%s, Location=%s, Status=%s, Current=%.2f%s, Start=%.2f%s, Target=%.2f%s, Tolerance=%.2f%s, Control=%s, Auto=%s]",
+ getSensorId(), getSensorType(), getLocation(), getStatus(),
+ currentTemperature, temperatureUnit,
+ startThreshold, temperatureUnit,
+ targetTemperature, temperatureUnit,
+ temperatureTolerance, temperatureUnit,
+ temperatureControlEnabled ? "Enabled" : "Disabled",
+ automaticMode ? "On" : "Off"
+ );
}
- public double simulateTemperature() {
- this.currentTemperature += temperatureIncrement;
-
- if (currentTemperature > maxTemperature) {
- currentTemperature = maxTemperature;
- } else if (currentTemperature < minTemperature) {
- currentTemperature = minTemperature;
- }
-
- this.currentTemperature = Math.round(currentTemperature);
+ // -----------------------
+ // Simulation logic
+ // -----------------------
+ public double simulateTemperature(double change) {
+ currentTemperature += change;
+ if (currentTemperature > targetTemperature) currentTemperature = targetTemperature;
+ if (currentTemperature < startThreshold) currentTemperature = startThreshold;
setCurrentValue(currentTemperature);
return currentTemperature;
}
- // ========== CONTROL SYSTEM METHODS ==========
- public void enableTemperatureControl(double targetTemp, double threshold) {
+ public void enableTemperatureControl(double targetTemp, double tolerance) {
this.targetTemperature = targetTemp;
- this.temperatureThreshold = threshold;
+ this.temperatureTolerance = tolerance;
this.temperatureControlEnabled = true;
+ // sync base control fields
+ enableControl(targetTemp, tolerance);
}
- public String getTemperatureStatus() {
- if (!temperatureControlEnabled) {
- return "Control Disabled";
- }
-
- double difference = Math.abs(currentTemperature - targetTemperature);
-
- if (difference <= temperatureThreshold) {
- return "Within Target Range";
- } else if (currentTemperature > targetTemperature + temperatureThreshold) {
- return "Too Hot";
- } else {
- return "Too Cold";
- }
+ public void disableTemperatureControl() {
+ this.temperatureControlEnabled = false;
+ disableControl();
}
- // ========== SIMULATION CONTROL ==========
- public void start() {
- this.currentTemperature = startThreshold;
- setCurrentValue(currentTemperature);
- maxReached = false;
- activateSensor();
- System.out.println("š”ļø Starting temperature sensor " + getSensorId() + " simulation");
- System.out.println("Initial temperature: " + getCurrentTemperature() + temperatureUnit);
+ public String getTemperatureStatus(double tempToCheck) {
+ if (!temperatureControlEnabled) return "Control Disabled";
+ double difference = Math.abs(tempToCheck - targetTemperature);
+ if (difference <= temperatureTolerance) return "Within Target Range";
+ else if (tempToCheck > targetTemperature + temperatureTolerance) return "Too Hot";
+ else return "Too Cold";
}
- public void stop() {
- deactivateSensor();
- }
-
- public boolean isRunning() {
- return isActive();
+ // -----------------------
+ // Heating / Cooling cycle (uses primaryIncrement and coolingRate)
+ // -----------------------
+ private void startHeating() {
+ updateValue(primaryIncrement);
+ if (currentTemperature >= targetTemperature + temperatureTolerance) {
+ throw new IllegalStateException("Too Hot: " + currentTemperature + temperatureUnit);
+ }
+ if (currentTemperature >= targetTemperature) {
+ maxReached = true;
+ deactivateSensor();
+ setStatus("Cooling");
+ }
}
- public void configureHeatingParameters(double startThreshold, double increment, double coolingRate) {
- this.startThreshold = startThreshold;
- this.coolingRate = coolingRate;
- this.temperatureIncrement = increment;
- }
-
- // ========== PRIVATE SIMULATION HELPERS ==========
- private void performTemperatureCycle() {
- if (isActive() && !maxReached) {
- this.currentTemperature += temperatureIncrement;
- setCurrentValue(currentTemperature);
- System.out.println("š”ļø Sensor " + getSensorId() + " heating: " + getCurrentTemperature() + temperatureUnit);
-
- if (currentTemperature >= maxTemperature) {
- this.currentTemperature = maxTemperature;
- setCurrentValue(currentTemperature);
- maxReached = true;
- deactivateSensor();
- System.out.println("š”ļø Sensor " + getSensorId() + " reached max: " + maxTemperature + "°C - DEACTIVATED");
- System.out.println(getSensorInfo());
- startCoolingMonitor();
- }
+ private void startCooling() {
+ updateValue(-coolingRate);
+ if (currentTemperature <= startThreshold - temperatureTolerance) {
+ throw new IllegalStateException("Too Cold: " + currentTemperature + temperatureUnit);
+ }
+ if (currentTemperature <= startThreshold) {
+ maxReached = false;
+ activateSensor();
+ setStatus("Active");
+ System.out.println("š Sensor " + getSensorId() + " cooled to start threshold - RESTARTING");
}
}
-
- private void startCoolingMonitor() {
- Timer coolingTimer = new Timer("Cooling-" + getSensorId(), true);
- coolingTimer.scheduleAtFixedRate(new TimerTask() {
- @Override
- public void run() {
- double cooledTemp = currentTemperature - coolingRate;
- currentTemperature = Math.max(cooledTemp, startThreshold);
- setCurrentValue(currentTemperature);
-
- System.out.println("āļø Sensor " + getSensorId() + " cooling: " + getCurrentTemperature() + temperatureUnit);
-
- if (currentTemperature <= startThreshold + 5.0) {
- coolingTimer.cancel();
- System.out.println("š Sensor " + getSensorId() + " temperature dropped - RESTARTING");
- maxReached = false;
- activateSensor();
- start();
- }
- }
- }, 2000, 2000);
- }
-}
+ @Override
+ public void performCycle() {
+ if (!temperatureControlEnabled) return;
+ // hysteresis: only heat if below target - tolerance, only cool if above target + tolerance
+ double calibrated = currentTemperature + calibrationOffset;
+ if (calibrated < targetTemperature - temperatureTolerance) {
+ startHeating();
+ } else if (calibrated > targetTemperature + temperatureTolerance) {
+ startCooling();
+ } else {
+ // within deadband: no action
+ setStatus("WithinDeadband");
+ }
+ }
+ // getters
+ public double getCurrentTemperature() { return currentTemperature; }
+ public String getTemperatureUnit() { return temperatureUnit; }
+ public double getTargetTemperature() { return targetTemperature; }
+ public boolean isTemperatureControlEnabled() { return temperatureControlEnabled; }
+ public double getStartThreshold() { return startThreshold; }
+ public double getTemperatureTolerance() { return temperatureTolerance; }
+}
diff --git a/src/main/java/org/Automation/entities/WeightSensor.java b/src/main/java/org/Automation/entities/WeightSensor.java
index 0eda8b4..5ae5190 100644
--- a/src/main/java/org/Automation/entities/WeightSensor.java
+++ b/src/main/java/org/Automation/entities/WeightSensor.java
@@ -1,204 +1,254 @@
-package org.Automation.entities;
+package org.automation.entities;
-import org.Automation.controllers.SimulationClock;
+import org.automation.engine.SimulationClock;
+import org.automation.engine.ClockObserver;
+
+import java.time.LocalDateTime;
+
+/**
+ * WeightSensor
+ * - Calibration offset is applied only when reporting/validating (Option 1)
+ * - Internal simulated value (currentWeight) remains the raw truth
+ * - Uses Sensor.primaryIncrement (clock-driven) for automatic per-tick weight changes
+ * - Uses Sensor's automaticMode and controlEnabled flags; provides convenience wrappers
+ */
+public class WeightSensor extends Sensor implements ClockObserver {
-public class WeightSensor extends Sensor {
- // ========== CORE WEIGHT FIELDS ==========
- private double minWeight;
- private double maxWeight;
private double currentWeight;
- private String weightUnit;
- private double calibrationFactor;
- private double weightIncrement;
-
- // ========== CONTROL SYSTEM FIELDS ==========
+ private final String weightUnit;
private double machineCapacity;
private boolean weightControlEnabled;
-
- // ========== SIMULATION FIELDS ==========
+ private double lastWeight;
+
+ private double calibrationOffset;
+ private boolean calibrated = false;
+
+ private volatile LocalDateTime lastActionTime;
private SimulationClock simulationClock;
- private double startWeight;
- private boolean maxReached;
- // ========== CONSTRUCTOR ==========
- public WeightSensor(String sensorType, String location, String status,
- double minWeight, double maxWeight, String weightUnit) {
+ public WeightSensor(String sensorType, String location, String status,
+ double initialWeight, double capacity, String weightUnit) {
super(sensorType, location, status);
- this.minWeight = minWeight;
- this.maxWeight = maxWeight;
+ this.currentWeight = initialWeight;
+ this.lastWeight = initialWeight;
+ this.machineCapacity = capacity;
this.weightUnit = weightUnit;
- this.currentWeight = minWeight;
- this.startWeight = minWeight;
-
- this.simulationClock = new SimulationClock("WeightSensor-" + getSensorId(), 1000) {
- @Override
- protected void tick() {
- super.tick();
- performWeightCycle();
- }
- };
+ this.weightControlEnabled = true;
+ this.simulationClock = SimulationClock.getInstance();
+ // initialize base control fields
+ this.controlTarget = initialWeight;
+ this.controlTolerance = 0.0;
+ }
+
+ /**
+ * Construct a WeightSensor with an explicit sensor id (used when loading from DB).
+ */
+ public WeightSensor(int sensorId, String sensorType, String location, String status,
+ double initialWeight, double capacity, String weightUnit) {
+ super(sensorId, sensorType, location, status);
+ this.currentWeight = initialWeight;
+ this.lastWeight = initialWeight;
+ this.machineCapacity = capacity;
+ this.weightUnit = weightUnit;
+ this.weightControlEnabled = true;
+ this.simulationClock = SimulationClock.getInstance();
+ this.controlTarget = initialWeight;
+ this.controlTolerance = 0.0;
}
- // ========== GETTERS ==========
- public double getCurrentWeight() { return currentWeight; }
- public String getWeightUnit() { return weightUnit; }
- public double getMachineCapacity() { return machineCapacity; }
- public boolean isWeightControlEnabled() { return weightControlEnabled; }
+ @Override
+ protected double getBaselineIncrement() { return 1.0; }
- public String getSensorInfo() {
- return String.format("WeightSensor[ID=%d, Type=%s, Location=%s, Status=%s, " +
- "Current=%.2f%s, Range=%.1f-%.1f%s, Capacity=%.1f%s, Control=%s]",
- getSensorId(), getSensorType(), getLocation(), getStatus(),
- currentWeight, weightUnit,
- minWeight, maxWeight, weightUnit,
- machineCapacity, weightUnit,
- weightControlEnabled ? "Enabled" : "Disabled");
+ @Override
+ protected double getBaselineCooling() { return 0.5; }
+
+ @Override
+ protected void onStart() {
+ simulationClock.register(this);
+ // initialize increments based on current clock interval so first cycle is aligned
+ try { setSimulationInterval(simulationClock.getTickIntervalMs()); } catch (Exception ignored) {}
+ calibrateSensor();
+ updateValue(0);
+ setStatus("Active");
+ System.out.println("āļø WeightSensor " + getSensorId() + " started at " + currentWeight + weightUnit);
}
- // ========== ABSTRACT METHOD IMPLEMENTATIONS ==========
@Override
- public void readValue() {
- readSensorData();
+ protected void onStop() {
+ try { simulationClock.unregister(this); } catch (Exception ignored) {}
+ setStatus("Stopped");
+ System.out.println("š WeightSensor " + getSensorId() + " stopped at " + currentWeight + weightUnit);
}
@Override
- public void updateValue() {
- simulateWeight();
+ public void onTick(LocalDateTime currentTime) {
+ synchronized (this) {
+ if (!isActive()) return;
+ if (!automaticMode) return; // gate automatic behavior
+ if (lastActionTime == null || currentTime.minusSeconds(1).isAfter(lastActionTime)) {
+ try {
+ int intervalMs = SimulationClock.getInstance().getTickIntervalMs();
+ setSimulationInterval(intervalMs);
+ if (controlEnabled && weightControlEnabled) {
+ performCycle();
+ } else {
+ // passive automatic addition if desired
+ updateValue(primaryIncrement);
+ }
+ } catch (IllegalArgumentException e) {
+ sendAlert(getCalibratedWeight());
+ setStatus("Error");
+ }
+ lastActionTime = currentTime;
+ }
+ }
}
+ // -----------------------
+ // Abstract implementations
+ // -----------------------
@Override
- public Object getValue() {
- return currentWeight;
+ public Object getValue() { return currentWeight; }
+
+ @Override
+ public void readValue() {
+ calibrateSensor();
+ double calibratedWeight = getCalibratedWeight();
+ System.out.println(getSensorInfo() + " | Calibrated Weight: " + String.format("%.2f%s", calibratedWeight, weightUnit));
+ boolean valid = validateReading();
+ if (!valid) sendAlert(calibratedWeight);
+ updateStatusAfterRead(valid);
+ System.out.println("ā” Status: " + (valid ? "Within Capacity" : "Out of Range"));
+ }
+
+ @Override
+ public void updateValue(double change) {
+ try {
+ simulateWeight(change);
+ } catch (IllegalArgumentException e) {
+ sendAlert(getCalibratedWeight());
+ setStatus("Error");
+ return;
+ }
+ readValue();
}
@Override
public void calibrateSensor() {
- System.out.println("š§ Calibrating weight sensor " + getSensorId());
- this.calibrationFactor = 0.1;
+ if (!calibrated) {
+ setStatus("Calibrating");
+ System.out.println("š§ Calibrating weight sensor " + getSensorId());
+ this.calibrationOffset = 0.1 + Math.random() * 0.2;
+ calibrated = true;
+ setStatus("OK");
+ }
+ }
+
+ public double getCalibratedWeight() {
+ return currentWeight + calibrationOffset;
}
@Override
public boolean validateReading() {
- return currentWeight >= minWeight && currentWeight <= maxWeight;
+ double calibrated = getCalibratedWeight();
+ return calibrated > 0 && calibrated <= machineCapacity;
}
@Override
- public void sendAlert() {
- System.out.println("šØ WEIGHT ALERT - Sensor " + getSensorId() +
- ": " + currentWeight + weightUnit);
- updateStatus("Alert");
+ public void sendAlert(double currentValue) {
+ raiseAlert("Weight out of range", String.format("%.2f%s", currentValue, weightUnit));
}
- // ========== CORE WEIGHT METHODS ==========
- public void readSensorData() {
- double weight = simulateWeight();
- this.currentWeight = weight + calibrationFactor;
- setCurrentValue(currentWeight);
-
- System.out.println(getSensorInfo());
-
- if (!validateReading()) {
- sendAlert();
- }
+ @Override
+ public String getSensorInfo() {
+ return String.format("WeightSensor[ID=%d, Type=%s, Location=%s, Status=%s, Current=%.2f%s, Capacity=%.2f%s, Auto=%s]",
+ getSensorId(), getSensorType(), getLocation(), getStatus(),
+ currentWeight, weightUnit, machineCapacity, weightUnit, automaticMode ? "On" : "Off");
}
- public double simulateWeight() {
- this.currentWeight += weightIncrement;
-
- if (currentWeight > maxWeight) {
- currentWeight = maxWeight;
- } else if (currentWeight < minWeight) {
- currentWeight = minWeight;
+ // -----------------------
+ // Simulation logic (raw value)
+ // -----------------------
+ private synchronized void simulateWeight(double change) {
+ double newWeight = currentWeight + change;
+
+ if (newWeight <= 0) {
+ throw new IllegalArgumentException("Load cannot be ⤠0. Attempted: " + newWeight + weightUnit);
+ }
+ if (newWeight > machineCapacity) {
+ throw new IllegalArgumentException("Overload detected. Attempted: " + newWeight + weightUnit);
+ }
+ if (Math.abs(newWeight - lastWeight) > machineCapacity * 0.3) {
+ throw new IllegalArgumentException("Shock load detected. Change: " + (newWeight - lastWeight) + weightUnit);
}
-
- this.currentWeight = Math.round(currentWeight);
- setCurrentValue(currentWeight);
- return currentWeight;
- }
- // ========== CONTROL SYSTEM METHODS ==========
- public void enableWeightControl(double capacity) {
- this.machineCapacity = capacity;
- this.weightControlEnabled = true;
+ currentWeight = newWeight;
+ lastWeight = currentWeight;
+ setCurrentValue(currentWeight);
}
+ // Manual control
public boolean addWeight(double weight) {
- double currentWeightValue = getCurrentWeight();
- if (currentWeightValue + weight <= machineCapacity) {
- this.currentWeight += weight;
- setCurrentValue(currentWeight);
- System.out.println("āļø Added " + weight + weightUnit + " to sensor " + getSensorId() +
- " (Total: " + getCurrentWeight() + weightUnit + ")");
- System.out.println(getSensorInfo());
+ try {
+ updateValue(weight);
+ System.out.println("āļø Added " + weight + weightUnit + " -> Current: " + currentWeight + weightUnit);
return true;
- } else {
- System.out.println("šØ Cannot add " + weight + weightUnit + " - would exceed capacity!");
- System.out.println("Current: " + getCurrentWeight() + weightUnit + ", Capacity: " + machineCapacity + weightUnit);
+ } catch (IllegalArgumentException e) {
+ sendAlert(getCalibratedWeight());
+ setStatus("Error");
return false;
}
}
- // ========== SIMULATION CONTROL ==========
- public void start() {
- this.currentWeight = startWeight;
- setCurrentValue(currentWeight);
- maxReached = false;
- activateSensor();
- simulationClock.start();
- System.out.println("āļø Starting weight sensor " + getSensorId() + " simulation");
- System.out.println("Initial weight: " + getCurrentWeight() + weightUnit);
- }
-
- public void stop() {
- simulationClock.stop();
- }
-
- public boolean isRunning() {
- return simulationClock.isRunning();
+ public boolean removeWeight(double weight) {
+ try {
+ updateValue(-weight);
+ System.out.println("āļø Removed " + weight + weightUnit + " -> Current: " + currentWeight + weightUnit);
+ return true;
+ } catch (IllegalArgumentException e) {
+ sendAlert(getCalibratedWeight());
+ setStatus("Error");
+ return false;
+ }
}
- public void setSimulationInterval(int intervalMs) {
- calculateIncrementForInterval(intervalMs);
- simulationClock.setInterval(intervalMs);
+ // Control wrappers specific to weight
+ public void enableWeightControl(double capacity) {
+ this.machineCapacity = capacity;
+ this.weightControlEnabled = true;
+ enableControl(currentWeight, 0.0);
}
- public void configureWeightParameters(double startWeight, double increment, int intervalMs) {
- this.startWeight = startWeight;
- setSimulationInterval(intervalMs);
- this.weightIncrement = increment;
+ public void disableWeightControl() {
+ this.weightControlEnabled = false;
+ disableControl();
}
- // ========== PRIVATE SIMULATION HELPERS ==========
- private void performWeightCycle() {
- if (isActive() && !maxReached) {
- double currentWeightValue = getCurrentWeight();
-
- while (currentWeightValue < machineCapacity && !maxReached) {
- this.currentWeight += weightIncrement;
- setCurrentValue(currentWeight);
- currentWeightValue = getCurrentWeight();
- System.out.println("āļø Sensor " + getSensorId() + " loading: " + currentWeightValue + weightUnit);
+ @Override
+ public void performCycle() {
+ if (!weightControlEnabled) return;
+ // simple control: try to maintain controlTarget (base) within controlTolerance
+ double calibrated = getCalibratedWeight();
+ if (calibrated < controlTarget - controlTolerance) {
+ // add small increment
+ updateValue(primaryIncrement);
+ } else if (calibrated > controlTarget + controlTolerance) {
+ // remove small amount (if safe)
+ try {
+ updateValue(-primaryIncrement);
+ } catch (Exception e) {
+ // if removal would violate safety, raise alert and disable automatic control
+ raiseAlert("Auto-remove failed", e.getMessage());
+ disableAutomaticMode();
+ disableControl();
}
-
- this.currentWeight = machineCapacity;
- setCurrentValue(currentWeight);
- maxReached = true;
- deactivateSensor();
- simulationClock.stop();
- System.out.println("āļø Sensor " + getSensorId() + " reached capacity: " + machineCapacity +
- "kg - DEACTIVATED & STOPPED");
- System.out.println(getSensorInfo());
+ } else {
+ setStatus("WithinDeadband");
}
}
- private void calculateIncrementForInterval(int intervalMs) {
- if (intervalMs <= 1000) this.weightIncrement = 5.0;
- else if (intervalMs <= 2000) this.weightIncrement = 10.0;
- else this.weightIncrement = 15.0;
- }
+ // getters
+ public double getCurrentWeight() { return currentWeight; }
+ public String getWeightUnit() { return weightUnit; }
+ public double getMachineCapacity() { return machineCapacity; }
}
-
-
-
-
-
diff --git a/src/main/java/org/Automation/entities/Worker.java b/src/main/java/org/Automation/entities/Worker.java
index 1b122ba..30a1555 100644
--- a/src/main/java/org/Automation/entities/Worker.java
+++ b/src/main/java/org/Automation/entities/Worker.java
@@ -1,4 +1,4 @@
-package org.Automation.entities;
+package org.automation.entities;
public class Worker {
public int id;
diff --git a/src/main/java/org/Automation/repositories/ConveyorRepository.java b/src/main/java/org/Automation/repositories/ConveyorRepository.java
index 93856b6..676563f 100644
--- a/src/main/java/org/Automation/repositories/ConveyorRepository.java
+++ b/src/main/java/org/Automation/repositories/ConveyorRepository.java
@@ -1,10 +1,10 @@
-package org.Automation.repositories;
+package org.automation.repositories;
import java.sql.ResultSet;
import java.sql.SQLException;
-import org.Automation.DatabaseManager;
-import org.Automation.entities.ConveyorBelt;
+import org.automation.database.DatabaseManager;
+import org.automation.entities.ConveyorBelt;
public class ConveyorRepository extends Repository {
public ConveyorRepository(DatabaseManager db) {
diff --git a/src/main/java/org/Automation/repositories/EventLogRepository.java b/src/main/java/org/Automation/repositories/EventLogRepository.java
deleted file mode 100644
index 925b7df..0000000
--- a/src/main/java/org/Automation/repositories/EventLogRepository.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.Automation.repositories;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-import org.Automation.DatabaseManager;
-import org.Automation.entities.EventLog;
-
-public class EventLogRepository extends Repository {
- public EventLogRepository(DatabaseManager db) {
- super("EventLog", db);
- }
-
-
- @Override
- public EventLog mapRow(ResultSet rs) throws SQLException {
- return new EventLog(
- rs.getInt("id"),
- rs.getString("timestamp"),
- rs.getString("componentType"),
- rs.getInt("componentId"),
- rs.getString("eventType"),
- rs.getString("message"));
- }
-
- @Override
- public String createTableQuery() {
- return """
- CREATE TABLE IF NOT EXISTS EventLog (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL,
- componentType TEXT,
- componentId INTEGER,
- eventType TEXT,
- message TEXT
- );
- """;
- }
-}
diff --git a/src/main/java/org/Automation/repositories/MachineRepository.java b/src/main/java/org/Automation/repositories/MachineRepository.java
index d442f47..94b0166 100644
--- a/src/main/java/org/Automation/repositories/MachineRepository.java
+++ b/src/main/java/org/Automation/repositories/MachineRepository.java
@@ -1,6 +1,6 @@
-package org.Automation.repositories;
-import org.Automation.entities.Machine;
-import org.Automation.DatabaseManager;
+package org.automation.repositories;
+import org.automation.entities.Machine;
+import org.automation.database.DatabaseManager;
import java.sql.ResultSet;
import java.sql.SQLException;
diff --git a/src/main/java/org/Automation/repositories/ProductItemRepository.java b/src/main/java/org/Automation/repositories/ProductItemRepository.java
new file mode 100644
index 0000000..fe3d082
--- /dev/null
+++ b/src/main/java/org/Automation/repositories/ProductItemRepository.java
@@ -0,0 +1,39 @@
+package org.automation.repositories;
+
+import org.automation.entities.ProductItem;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.automation.database.DatabaseManager;
+
+public class ProductItemRepository extends Repository {
+ public ProductItemRepository(DatabaseManager db) {
+ super("ProductItem", db);
+ }
+
+ @Override
+ public String createTableQuery() {
+ return """
+ CREATE TABLE IF NOT EXISTS ProductItem (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ weight REAL,
+ status TEXT,
+ createdAt TEXT,
+ currentStationId INTEGER
+ );
+ """;
+ }
+
+ @Override
+ protected ProductItem mapRow(ResultSet rs) throws SQLException {
+ return new ProductItem(
+ rs.getInt("id"),
+ rs.getString("name"),
+ rs.getDouble("weight"),
+ rs.getString("status"),
+ rs.getString("createdAt"),
+ rs.getInt("currentStationId"));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/Automation/repositories/ProductRepository.java b/src/main/java/org/Automation/repositories/ProductRepository.java
deleted file mode 100644
index 899bf5e..0000000
--- a/src/main/java/org/Automation/repositories/ProductRepository.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.Automation.repositories;
-import org.Automation.entities.Product;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-import org.Automation.DatabaseManager;
-
-public class ProductRepository extends Repository {
- public ProductRepository(DatabaseManager db) {
- super("Product", db);
- }
-
- @Override
- public String createTableQuery() {
- return """
- CREATE TABLE IF NOT EXISTS Product (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- status TEXT
- );
- """;
- }
-
- @Override
- protected Product mapRow(ResultSet rs) throws SQLException {
- return new Product(
- rs.getInt("id"),
- rs.getString("name"),
- rs.getString("status"));
- }
-}
\ No newline at end of file
diff --git a/src/main/java/org/Automation/repositories/Repository.java b/src/main/java/org/Automation/repositories/Repository.java
index ca0dab9..00e574c 100644
--- a/src/main/java/org/Automation/repositories/Repository.java
+++ b/src/main/java/org/Automation/repositories/Repository.java
@@ -1,5 +1,5 @@
-package org.Automation.repositories;
-import org.Automation.DatabaseManager;
+package org.automation.repositories;
+import org.automation.database.DatabaseManager;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -30,38 +30,27 @@ public boolean delete(String where, Object[] params) {
}
public T findOne(String where, Object[] params) {
- try (ResultSet rs = db.find(tableName, where, params)) {
- if (rs.next()) {
- return mapRow(rs);
- }
- } catch (SQLException e) {
+ try {
+ return db.queryOne(tableName, where, params, rs -> mapRow(rs));
+ } catch (RuntimeException e) {
throw new RuntimeException("findOne failed: " + e.getMessage(), e);
}
- return null;
}
public List findAllWhere(String where, Object[] params) {
- List result = new ArrayList<>();
- try (ResultSet rs = db.find(tableName, where, params)) {
- while (rs.next()) {
- result.add(mapRow(rs));
- }
- } catch (SQLException e) {
+ try {
+ return db.query(tableName, where, params, rs -> mapRow(rs));
+ } catch (RuntimeException e) {
throw new RuntimeException("findAllWhere failed: " + e.getMessage(), e);
}
- return result;
}
public List findAll() {
- List result = new ArrayList<>();
- try (ResultSet rs = db.find(tableName, null, null)) {
- while (rs.next()) {
- result.add(mapRow(rs));
- }
- } catch (SQLException e) {
+ try {
+ return db.query(tableName, null, null, rs -> mapRow(rs));
+ } catch (RuntimeException e) {
throw new RuntimeException("findAll failed: " + e.getMessage(), e);
}
- return result;
}
// ---------- Maps a row from ResultSet into an entity ----------
diff --git a/src/main/java/org/Automation/repositories/SensorRepository.java b/src/main/java/org/Automation/repositories/SensorRepository.java
index 85b861b..5776f4d 100644
--- a/src/main/java/org/Automation/repositories/SensorRepository.java
+++ b/src/main/java/org/Automation/repositories/SensorRepository.java
@@ -1,36 +1,157 @@
-package org.Automation.repositories;
+package org.automation.repositories;
import java.sql.ResultSet;
import java.sql.SQLException;
-import org.Automation.DatabaseManager;
-import org.Automation.entities.Sensor;
+import org.automation.database.DatabaseManager;
+import org.automation.entities.Sensor;
+import org.automation.entities.TemperatureSensor;
+import org.automation.entities.WeightSensor;
public class SensorRepository extends Repository {
+
public SensorRepository(DatabaseManager db) {
super("Sensor", db);
}
-
+
+ public boolean ensureTable() {
+ boolean created = db.executeDDL(createTableQuery());
+ if (created) System.out.println("Sensor table ensured/created.");
+ return created;
+ }
+
+ public boolean save(Sensor sensor) {
+ if (sensor == null) return false;
+
+ boolean exists = findOne("id = ?", new Object[]{sensor.getSensorId()}) != null;
+
+ if (sensor instanceof TemperatureSensor t) {
+ return exists ? updateTemperature(t) : insertTemperature(t);
+ } else if (sensor instanceof WeightSensor w) {
+ return exists ? updateWeight(w) : insertWeight(w);
+ } else {
+ return exists ? updateBase(sensor) : insertBase(sensor);
+ }
+ }
+
+ /* ---------------- INSERT ---------------- */
+
+ private boolean insertTemperature(TemperatureSensor t) {
+ return insert(
+ new String[]{"type", "location", "status", "startThreshold", "tolerance", "targetTemperature", "temperatureUnit"},
+ new Object[]{t.getSensorType(), t.getLocation(), t.getStatus(), t.getStartThreshold(), t.getTemperatureTolerance(), t.getTargetTemperature(), t.getTemperatureUnit()}
+ );
+ }
+
+ private boolean insertWeight(WeightSensor w) {
+ return insert(
+ new String[]{"type", "location", "status", "initialWeight", "capacity", "weightUnit"},
+ new Object[]{w.getSensorType(), w.getLocation(), w.getStatus(), w.getCurrentWeight(), w.getMachineCapacity(), w.getWeightUnit()}
+ );
+ }
+
+ private boolean insertBase(Sensor s) {
+ return insert(
+ new String[]{"type", "location", "status"},
+ new Object[]{s.getSensorType(), s.getLocation(), s.getStatus()}
+ );
+ }
+
+ /* ---------------- UPDATE ---------------- */
+
+ private boolean updateTemperature(TemperatureSensor t) {
+ return update(
+ "type = ?, location = ?, status = ?, startThreshold = ?, tolerance = ?, targetTemperature = ?, temperatureUnit = ?",
+ "id = ?",
+ new Object[]{t.getSensorType(), t.getLocation(), t.getStatus(), t.getStartThreshold(), t.getTemperatureTolerance(), t.getTargetTemperature(), t.getTemperatureUnit(), t.getSensorId()}
+ );
+ }
+
+ private boolean updateWeight(WeightSensor w) {
+ return update(
+ "type = ?, location = ?, status = ?, initialWeight = ?, capacity = ?, weightUnit = ?",
+ "id = ?",
+ new Object[]{w.getSensorType(), w.getLocation(), w.getStatus(), w.getCurrentWeight(), w.getMachineCapacity(), w.getWeightUnit(), w.getSensorId()}
+ );
+ }
+
+ private boolean updateBase(Sensor s) {
+ return update(
+ "type = ?, location = ?, status = ?",
+ "id = ?",
+ new Object[]{s.getSensorType(), s.getLocation(), s.getStatus(), s.getSensorId()}
+ );
+ }
+
+ public boolean deleteById(int id) {
+ return delete("id = ?", new Object[]{id});
+ }
+
+ /* ---------------- ROW MAPPING ---------------- */
@Override
public Sensor mapRow(ResultSet rs) throws SQLException {
- return new Sensor(
- rs.getInt("id"),
- rs.getString("type"),
- rs.getInt("machineId"),
- rs.getString("status"));
+ int sensorId = rs.getInt("id");
+ String type = safeGetString(rs, "type", "");
+ String location = safeGetString(rs, "location", "Unknown");
+ String status = safeGetString(rs, "status", "inactive");
+
+ return switch (type.toLowerCase()) {
+ case "temperature" -> new TemperatureSensor(
+ sensorId, type, location, status,
+ safeGetDouble(rs, "startThreshold", 0),
+ safeGetDouble(rs, "tolerance", 1),
+ safeGetDouble(rs, "targetTemperature", 25),
+ safeGetString(rs, "temperatureUnit", "°C")
+ );
+ case "weight" -> new WeightSensor(
+ sensorId, type, location, status,
+ safeGetDouble(rs, "initialWeight", 0),
+ safeGetDouble(rs, "capacity", 100),
+ safeGetString(rs, "weightUnit", "kg")
+ );
+ default -> throw new IllegalStateException("Unknown sensor type: " + type);
+ };
}
+ /* ---------------- SCHEMA ---------------- */
+
@Override
public String createTableQuery() {
return """
- CREATE TABLE IF NOT EXISTS Sensor (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- type TEXT NOT NULL,
- machineId INTEGER,
- status TEXT DEFAULT 'inactive',
- FOREIGN KEY(machineId) REFERENCES Machine(id)
- );
- """;
+ CREATE TABLE IF NOT EXISTS Sensor (
+ id INTEGER PRIMARY KEY,
+ type TEXT NOT NULL,
+ location TEXT,
+ status TEXT DEFAULT 'inactive',
+ startThreshold REAL,
+ tolerance REAL,
+ targetTemperature REAL,
+ temperatureUnit TEXT,
+ initialWeight REAL,
+ capacity REAL,
+ weightUnit TEXT
+ );
+ """;
+ }
+
+ /* ---------------- HELPERS ---------------- */
+
+ private String safeGetString(ResultSet rs, String column, String def) {
+ try {
+ String val = rs.getString(column);
+ return val != null ? val : def;
+ } catch (SQLException e) {
+ return def;
+ }
+ }
+
+ private double safeGetDouble(ResultSet rs, String column, double def) {
+ try {
+ double val = rs.getDouble(column);
+ return rs.wasNull() ? def : val;
+ } catch (SQLException e) {
+ return def;
+ }
}
}
diff --git a/src/main/java/org/Automation/repositories/WorkerRepository.java b/src/main/java/org/Automation/repositories/WorkerRepository.java
index 3c73d5a..5dfa9c8 100644
--- a/src/main/java/org/Automation/repositories/WorkerRepository.java
+++ b/src/main/java/org/Automation/repositories/WorkerRepository.java
@@ -1,16 +1,15 @@
-package org.Automation.repositories;
+package org.automation.repositories;
import java.sql.ResultSet;
import java.sql.SQLException;
-import org.Automation.DatabaseManager;
-import org.Automation.entities.Worker;
+import org.automation.database.DatabaseManager;
+import org.automation.entities.Worker;
public class WorkerRepository extends Repository {
public WorkerRepository(DatabaseManager db) {
super("Worker", db);
}
-
@Override
public Worker mapRow(ResultSet rs) throws SQLException {
diff --git a/src/main/java/org/Automation/ui/ConsoleApp.java b/src/main/java/org/Automation/ui/ConsoleApp.java
new file mode 100644
index 0000000..313b460
--- /dev/null
+++ b/src/main/java/org/Automation/ui/ConsoleApp.java
@@ -0,0 +1,343 @@
+// package org.automation.ui;
+
+// import org.automation.engine.SimulationEngine;
+// import org.automation.controllers.WorkflowController;
+// import org.automation.controllers.SensorManager;
+// import org.automation.entities.Sensor;
+// import org.automation.entities.TemperatureSensor;
+// import org.automation.entities.WeightSensor;
+// import org.automation.utils.Logger;
+
+// import java.util.List;
+// import java.util.Scanner;
+
+// public class ConsoleApp extends ConsoleUI {
+// //Instance Variables
+// private SimulationEngine simulationEngine;
+// private WorkflowController controller;
+// private SensorManager sensorManager;
+// private Logger logger;
+// private Scanner scanner;
+// private boolean running;
+
+// public ConsoleApp(){
+// simulationEngine = new SimulationEngine();
+// sensorManager = new SensorManager();
+// logger = new Logger();
+// scanner = new Scanner(System.in);
+// running = false;
+// }
+
+// //Instance Methods
+// public void start() {
+// running = true;
+// printWelcomeMessage();
+// runMainMenu();
+// }
+
+// public void runMainMenu() {
+// while (running) {
+// printMainMenu();
+// String input = scanner.nextLine().trim();
+// handleUserInput(input);
+// }
+// }
+
+// private void printMainMenu() {
+// System.out.println("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
+// System.out.println("ā FACTORY AUTOMATION - SENSOR MANAGEMENT SYSTEM ā");
+// System.out.println("ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£");
+// System.out.println("ā [1] Add New Sensor ā");
+// System.out.println("ā [2] Remove Sensor ā");
+// System.out.println("ā [3] List All Sensors ā");
+// System.out.println("ā [4] View Sensor Details ā");
+// System.out.println("ā [5] Start Sensor ā");
+// System.out.println("ā [6] Stop Sensor ā");
+// System.out.println("ā [7] Start All Sensors ā");
+// System.out.println("ā [8] Stop All Sensors ā");
+// System.out.println("ā [9] Enable Automatic Mode (Single Sensor) ā");
+// System.out.println("ā [10] Disable Automatic Mode (Single Sensor) ā");
+// System.out.println("ā [11] Enable Control Mode (Single Sensor) ā");
+// System.out.println("ā [12] Disable Control Mode (Single Sensor) ā");
+// System.out.println("ā [13] Enable Automatic Mode (All Sensors) ā");
+// System.out.println("ā [14] Disable Automatic Mode (All Sensors) ā");
+// System.out.println("ā [0] Exit ā");
+// System.out.println("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
+// System.out.print("Select an option: ");
+// }
+
+// public void handleUserInput(String input) {
+// switch (input) {
+// case "1" -> addSensorMenu();
+// case "2" -> removeSensorMenu();
+// case "3" -> listAllSensors();
+// case "4" -> viewSensorDetails();
+// case "5" -> startSensorMenu();
+// case "6" -> stopSensorMenu();
+// case "7" -> startAllSensors();
+// case "8" -> stopAllSensors();
+// case "9" -> enableAutomaticModeMenu();
+// case "10" -> disableAutomaticModeMenu();
+// case "11" -> enableControlModeMenu();
+// case "12" -> disableControlModeMenu();
+// case "13" -> enableAutomaticModeAll();
+// case "14" -> disableAutomaticModeAll();
+// case "0" -> exit();
+// default -> System.out.println("ā Invalid option. Please try again.");
+// }
+// }
+
+// // ===========================
+// // Menu Options Implementation
+// // ===========================
+
+// private void addSensorMenu() {
+// System.out.println("\n--- Add New Sensor ---");
+// System.out.println("Select sensor type:");
+// System.out.println("[1] Temperature Sensor");
+// System.out.println("[2] Weight Sensor");
+// System.out.print("Choice: ");
+// String choice = scanner.nextLine().trim();
+
+// try {
+// System.out.print("Enter Sensor ID: ");
+// int id = Integer.parseInt(scanner.nextLine().trim());
+
+// System.out.print("Enter Location: ");
+// String location = scanner.nextLine().trim();
+
+// Sensor sensor = null;
+
+// if (choice.equals("1")) {
+// // Temperature Sensor
+// System.out.print("Enter Start Threshold: ");
+// double startThreshold = Double.parseDouble(scanner.nextLine().trim());
+
+// System.out.print("Enter Tolerance: ");
+// double tolerance = Double.parseDouble(scanner.nextLine().trim());
+
+// System.out.print("Enter Target Temperature: ");
+// double targetTemp = Double.parseDouble(scanner.nextLine().trim());
+
+// System.out.print("Enter Temperature Unit (°C/°F): ");
+// String unit = scanner.nextLine().trim();
+
+// sensor = new TemperatureSensor(id, "Temperature", location, "inactive",
+// startThreshold, tolerance, targetTemp, unit);
+// } else if (choice.equals("2")) {
+// // Weight Sensor
+// System.out.print("Enter Initial Weight: ");
+// double initialWeight = Double.parseDouble(scanner.nextLine().trim());
+
+// System.out.print("Enter Capacity: ");
+// double capacity = Double.parseDouble(scanner.nextLine().trim());
+
+// System.out.print("Enter Weight Unit (kg/lb): ");
+// String unit = scanner.nextLine().trim();
+
+// sensor = new WeightSensor(id, "Weight", location, "inactive",
+// initialWeight, capacity, unit);
+// } else {
+// System.out.println("ā Invalid sensor type.");
+// return;
+// }
+
+// sensorManager.addSensor(sensor);
+// System.out.println("ā
Sensor added successfully and saved to database!");
+
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid input. Please enter valid numbers.");
+// } catch (Exception e) {
+// System.out.println("ā Error adding sensor: " + e.getMessage());
+// }
+// }
+
+// private void removeSensorMenu() {
+// System.out.println("\n--- Remove Sensor ---");
+// System.out.print("Enter Sensor ID to remove: ");
+// try {
+// int id = Integer.parseInt(scanner.nextLine().trim());
+// if (sensorManager.removeSensor(id)) {
+// System.out.println("ā
Sensor removed successfully from database!");
+// } else {
+// System.out.println("ā Sensor not found.");
+// }
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid ID format.");
+// }
+// }
+
+// private void listAllSensors() {
+// System.out.println("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
+// System.out.println("ā ALL SENSORS LIST ā");
+// System.out.println("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
+
+// List sensorInfo = sensorManager.listSensorInfo();
+// if (sensorInfo.isEmpty()) {
+// System.out.println("š No sensors found in the system.");
+// } else {
+// for (int i = 0; i < sensorInfo.size(); i++) {
+// System.out.println((i + 1) + ". " + sensorInfo.get(i));
+// }
+// }
+// }
+
+// private void viewSensorDetails() {
+// System.out.println("\n--- View Sensor Details ---");
+// System.out.print("Enter Sensor ID: ");
+// try {
+// int id = Integer.parseInt(scanner.nextLine().trim());
+// String info = sensorManager.getSensorInfo(id);
+// if (info != null) {
+// System.out.println("\nš Sensor Details:");
+// System.out.println(info);
+// } else {
+// System.out.println("ā Sensor not found.");
+// }
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid ID format.");
+// }
+// }
+
+// private void startSensorMenu() {
+// System.out.println("\n--- Start Sensor ---");
+// System.out.print("Enter Sensor ID: ");
+// try {
+// int id = Integer.parseInt(scanner.nextLine().trim());
+// if (sensorManager.startSensor(id)) {
+// System.out.println("ā
Sensor started successfully!");
+// } else {
+// System.out.println("ā Failed to start sensor. It may already be running or not exist.");
+// }
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid ID format.");
+// }
+// }
+
+// private void stopSensorMenu() {
+// System.out.println("\n--- Stop Sensor ---");
+// System.out.print("Enter Sensor ID: ");
+// try {
+// int id = Integer.parseInt(scanner.nextLine().trim());
+// if (sensorManager.stopSensor(id)) {
+// System.out.println("ā
Sensor stopped successfully!");
+// } else {
+// System.out.println("ā Failed to stop sensor. It may already be stopped or not exist.");
+// }
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid ID format.");
+// }
+// }
+
+// private void startAllSensors() {
+// System.out.println("\nāļø Starting all sensors...");
+// sensorManager.startAll();
+// System.out.println("ā
All sensors have been started!");
+// }
+
+// private void stopAllSensors() {
+// System.out.println("\nāļø Stopping all sensors...");
+// sensorManager.stopAll();
+// System.out.println("ā
All sensors have been stopped!");
+// }
+
+// private void enableAutomaticModeMenu() {
+// System.out.println("\n--- Enable Automatic Mode ---");
+// System.out.print("Enter Sensor ID: ");
+// try {
+// int id = Integer.parseInt(scanner.nextLine().trim());
+// if (sensorManager.setSensorAutomaticMode(id, true)) {
+// System.out.println("ā
Automatic mode enabled for sensor!");
+// } else {
+// System.out.println("ā Sensor not found.");
+// }
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid ID format.");
+// }
+// }
+
+// private void disableAutomaticModeMenu() {
+// System.out.println("\n--- Disable Automatic Mode ---");
+// System.out.print("Enter Sensor ID: ");
+// try {
+// int id = Integer.parseInt(scanner.nextLine().trim());
+// if (sensorManager.setSensorAutomaticMode(id, false)) {
+// System.out.println("ā
Automatic mode disabled for sensor!");
+// } else {
+// System.out.println("ā Sensor not found.");
+// }
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid ID format.");
+// }
+// }
+
+// private void enableControlModeMenu() {
+// System.out.println("\n--- Enable Control Mode ---");
+// System.out.print("Enter Sensor ID: ");
+// try {
+// int id = Integer.parseInt(scanner.nextLine().trim());
+// System.out.print("Enter Target Value: ");
+// double target = Double.parseDouble(scanner.nextLine().trim());
+// System.out.print("Enter Tolerance: ");
+// double tolerance = Double.parseDouble(scanner.nextLine().trim());
+
+// if (sensorManager.setSensorControl(id, true, target, tolerance)) {
+// System.out.println("ā
Control mode enabled for sensor!");
+// } else {
+// System.out.println("ā Sensor not found.");
+// }
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid input format.");
+// }
+// }
+
+// private void disableControlModeMenu() {
+// System.out.println("\n--- Disable Control Mode ---");
+// System.out.print("Enter Sensor ID: ");
+// try {
+// int id = Integer.parseInt(scanner.nextLine().trim());
+// if (sensorManager.setSensorControl(id, false, 0, 0)) {
+// System.out.println("ā
Control mode disabled for sensor!");
+// } else {
+// System.out.println("ā Sensor not found.");
+// }
+// } catch (NumberFormatException e) {
+// System.out.println("ā Invalid ID format.");
+// }
+// }
+
+// private void enableAutomaticModeAll() {
+// System.out.println("\nāļø Enabling automatic mode for all sensors...");
+// sensorManager.setAllAutomaticMode(true);
+// System.out.println("ā
Automatic mode enabled for all sensors!");
+// }
+
+// private void disableAutomaticModeAll() {
+// System.out.println("\nāļø Disabling automatic mode for all sensors...");
+// sensorManager.setAllAutomaticMode(false);
+// System.out.println("ā
Automatic mode disabled for all sensors!");
+// }
+
+// public void printWelcomeMessage() {
+// System.out.println("\n");
+// System.out.println("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
+// System.out.println("ā ā");
+// System.out.println("ā š FACTORY AUTOMATION - SENSOR MANAGEMENT SYSTEM š ā");
+// System.out.println("ā ā");
+// System.out.println("ā Integrated Database-Driven Sensor Control Interface ā");
+// System.out.println("ā ā");
+// System.out.println("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
+// System.out.println("\n⨠Welcome! All sensors are automatically loaded from the database.");
+// System.out.println("š¾ All changes are persisted to the database in real-time.\n");
+// }
+
+// public void exit() {
+// System.out.println("\nš Shutting down system...");
+// System.out.println("ā³ Stopping all sensors...");
+// sensorManager.shutdown(5);
+// System.out.println("š¾ Database disconnected.");
+// System.out.println("ā
System shutdown complete. Goodbye! š\n");
+// scanner.close();
+// running = false;
+// }
+// }
diff --git a/src/main/java/org/Automation/ConsoleUI/ConsoleUI.java b/src/main/java/org/Automation/ui/ConsoleUI.java
similarity index 92%
rename from src/main/java/org/Automation/ConsoleUI/ConsoleUI.java
rename to src/main/java/org/Automation/ui/ConsoleUI.java
index 7af809e..0028227 100644
--- a/src/main/java/org/Automation/ConsoleUI/ConsoleUI.java
+++ b/src/main/java/org/Automation/ui/ConsoleUI.java
@@ -1,11 +1,10 @@
-package ConsoleUI;
+package org.automation.ui;
import java.util.*;
public class ConsoleUI {
// Low-Level Menu print lines
public void printHeader(String title) {
}
-
public void printMenu(ArrayList options) {
}
diff --git a/src/main/java/org/Automation/utils/Logger.java b/src/main/java/org/Automation/utils/Logger.java
new file mode 100644
index 0000000..640946d
--- /dev/null
+++ b/src/main/java/org/Automation/utils/Logger.java
@@ -0,0 +1,55 @@
+package org.automation.utils;
+
+import org.automation.database.DatabaseManager;
+import org.automation.entities.ProductItem;
+
+class Log {
+ protected int id;
+ protected String timestamp;
+ protected String componentType;
+ protected int componentId;
+ protected String eventType;
+ protected String message;
+
+ public Log(int id, String timestamp, String componentType, int componentId, String eventType, String message) {
+ this.id = id;
+ this.timestamp = timestamp;
+ this.componentType = componentType;
+ this.componentId = componentId;
+ this.eventType = eventType;
+ this.message = message;
+ }
+}
+
+public class Logger {
+ private DatabaseManager db;
+
+ public Logger(DatabaseManager db) {
+ this.db = db;
+ }
+
+ public void logProduct(ProductItem item) {
+
+ }
+
+ public void log(String componentType, int componentId, String eventType, String message) {
+ System.out.println(componentType + " [" + componentId + "] " + eventType + ": " + message);
+ }
+
+ public void logError(String error) {
+
+ }
+
+ public void clear() {
+
+ }
+
+ public void print() {
+
+ }
+
+ @Override
+ public String toString() {
+ return "Logger";
+ }
+}
diff --git a/src/main/java/org/automation/SensorDemo.java b/src/main/java/org/automation/SensorDemo.java
new file mode 100644
index 0000000..ceaacc7
--- /dev/null
+++ b/src/main/java/org/automation/SensorDemo.java
@@ -0,0 +1,40 @@
+// package org.automation;
+
+// import org.automation.entities.*;
+
+// public class SensorDemo {
+// public static void main(String[] args) {
+// System.out.println("=== Factory Sensor Monitoring System ===\n");
+
+// // Create Temperature and Weight sensors
+// TemperatureSensor tempSensor = new TemperatureSensor("Temperature","Factory Floor A", "Active", 20.0,5, 80.0, "°C");
+// WeightSensor weightSensor = new WeightSensor("Weight", "Conveyor Belt 1", "Active", 50.0, 100.0, "kg");
+
+// System.out.println("Initial State:");
+// System.out.println(tempSensor);
+// System.out.println(weightSensor);
+
+// System.out.println("\n=== Updating Sensor Values ===\n");
+
+// // Simulate 5 readings
+// for (int i = 1; i <= 5; i++) {
+// System.out.println("Reading #" + i + ":");
+
+// tempSensor.updateValue(0.5);
+// weightSensor.updateValue(1.0);
+
+// System.out.println(" " + tempSensor);
+// System.out.println(" " + weightSensor);
+// System.out.println();
+
+// try {
+// Thread.sleep(1000); // Wait 1 second between readings
+// } catch (InterruptedException e) {
+// e.printStackTrace();
+// }
+// }
+
+// System.out.println("=== Monitoring Complete ===");
+// }
+// }
+
diff --git a/src/main/java/org/automation/SensorManagerDemo.java b/src/main/java/org/automation/SensorManagerDemo.java
new file mode 100644
index 0000000..c29afaa
--- /dev/null
+++ b/src/main/java/org/automation/SensorManagerDemo.java
@@ -0,0 +1,93 @@
+// package org.automation;
+
+// import org.automation.controllers.SensorManager;
+// import org.automation.entities.*;
+// import java.util.List;
+
+// /**
+// * Demo showing SensorManager initialization and usage
+// */
+// public class SensorManagerDemo {
+// public static void main(String[] args) {
+// System.out.println("=== SensorManager Initialization Demo ===\n");
+
+// // Step 1: Create SensorManager (initializes empty list)
+// System.out.println("Step 1: Creating SensorManager");
+// SensorManager manager = new SensorManager();
+// // System.out.println(" Sensor count: " + manager.getSensorCount());
+
+// // Step 2: Add Temperature Sensors
+// System.out.println("\nStep 2: Adding Temperature Sensors");
+// TemperatureSensor temp1 = new TemperatureSensor("Temperature", "Factory Floor A", "Active", 20.0, 5, 80.0, "°C");
+// TemperatureSensor temp2 = new TemperatureSensor("Temperature", "Factory Floor B", "Active", 15.0,6, 75.0, "°C");
+
+// manager.addSensor(temp1);
+// manager.addSensor(temp2);
+// // System.out.println(" Sensor count: " + manager.getSensorCount());
+
+// // Step 3: Add Weight Sensors
+// System.out.println("\nStep 3: Adding Weight Sensors");
+// WeightSensor weight1 = new WeightSensor("Weight", "Conveyor Belt 1", "Active", 50.0, 100.0, "kg");
+// WeightSensor weight2 = new WeightSensor("Weight", "Conveyor Belt 2", "Active", 10.0, 50.0, "kg");
+
+// manager.addSensor(weight1);
+// manager.addSensor(weight2);
+// // System.out.println(" Sensor count: " + manager.getSensorCount());
+
+// // Step 4: Display all sensors
+// manager.displayAllSensors();
+
+// // Step 5: Update all sensors (simulate readings)
+// // System.out.println("\nStep 5: Updating all sensors (3 readings)");
+// // for (int i = 1; i <= 3; i++) {
+// // System.out.println("\n--- Reading #" + i + " ---");
+// // manager.updateAllSensors();
+
+// // // Check for alerts
+// // List alerts = manager.getAlertSensors();
+// // if (!alerts.isEmpty()) {
+// // System.out.println("\nā ļø ALERTS DETECTED:");
+// // for (Sensor sensor : alerts) {
+// // System.out.println(" " + sensor);
+// // }
+// // }
+
+// // try {
+// // Thread.sleep(1000);
+// // } catch (InterruptedException e) {
+// // e.printStackTrace();
+// // }
+// // }
+
+// // Step 6: Find sensors by type
+// // System.out.println("\n\nStep 6: Finding sensors by type");
+// // List tempSensors = manager.findSensorsByType("Temperature");
+// // System.out.println(" Temperature sensors: " + tempSensors.size());
+// // for (Sensor s : tempSensors) {
+// // System.out.println(" " + s);
+// // }
+
+// // List weightSensors = manager.findSensorsByType("Weight");
+// // System.out.println(" Weight sensors: " + weightSensors.size());
+// // for (Sensor s : weightSensors) {
+// // System.out.println(" " + s);
+// // }
+
+// // Step 7: Find sensor by ID
+// System.out.println("\nStep 7: Finding sensor by ID");
+// Sensor found = manager.findSensorById(2);
+// if (found != null) {
+// System.out.println(" Found: " + found);
+// }
+
+// // Step 8: Remove a sensor
+// System.out.println("\nStep 8: Removing sensor with ID 3");
+// manager.removeSensor(3);
+// // System.out.println(" Sensor count: " + manager.getSensorCount());
+
+// manager.displayAllSensors();
+
+// System.out.println("\n=== Demo Complete ===");
+// }
+// }
+
diff --git a/src/main/java/org/automation/TestInitialization.java b/src/main/java/org/automation/TestInitialization.java
new file mode 100644
index 0000000..3e88f34
--- /dev/null
+++ b/src/main/java/org/automation/TestInitialization.java
@@ -0,0 +1,35 @@
+// package org.automation;
+
+// import org.automation.entities.TemperatureSensor;
+
+// public class TestInitialization {
+// public static void main(String[] args) {
+// System.out.println("=== Testing Initialization ===\n");
+
+// Create sensor with minTemp=20, maxTemp=80
+// TemperatureSensor sensor = new TemperatureSensor("Temperature", "Factory Floor A", "Active", 20.0, 5,80.0, "°C");
+
+// // System.out.println("After constructor:");
+// // System.out.println(" minTemp = " + sensor.getMinTemp()); // Should be 20.0
+// // System.out.println(" maxTemp = " + sensor.getMaxTemp()); // Should be 80.0
+
+// System.out.println("\nCalling updateValue() 5 times:");
+// for (int i = 1; i <= 5; i++) {
+// sensor.updateValue(0.5);
+// double temp = (Double) sensor.getValue();
+// System.out.println(" Reading #" + i + ": " + temp + "°C");
+
+// // Verify temperature is using minTemp and maxTemp correctly
+// if (temp >= 20.0 && temp <= 80.0) {
+// System.out.println(" ā
Within range [20.0 - 80.0]");
+// } else {
+// System.out.println(" ā ļø Outside range (testing alert condition)");
+// }
+// }
+
+// System.out.println("\n=== Conclusion ===");
+// System.out.println("minTemp and maxTemp ARE initialized in the constructor!");
+// System.out.println("They are used by simulateTemperature() to generate values.");
+// }
+// }
+
diff --git a/src/main/java/org/automation/database/RowMapper.java b/src/main/java/org/automation/database/RowMapper.java
new file mode 100644
index 0000000..e9f60ee
--- /dev/null
+++ b/src/main/java/org/automation/database/RowMapper.java
@@ -0,0 +1,12 @@
+package org.automation.database;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Functional interface for mapping a ResultSet row to a domain object.
+ */
+@FunctionalInterface
+public interface RowMapper {
+ T mapRow(ResultSet rs) throws SQLException;
+}