From 943396952cc0d3bfe6850bd426602fad97f0afc6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 4 Oct 2025 01:19:22 +0000
Subject: [PATCH 1/6] Initial plan
From 7d559f0d1573e54cebb16672ca0f82057ec55356 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 4 Oct 2025 01:24:25 +0000
Subject: [PATCH 2/6] Add comprehensive documentation for adding buttons with
backend acknowledgment
Co-authored-by: SamWalts <96598232+SamWalts@users.noreply.github.com>
---
ADDING_BUTTONS.md | 360 ++++++++++++++++++++++++++++++++++++++++++++++
README.md | 50 +++++++
2 files changed, 410 insertions(+)
create mode 100644 ADDING_BUTTONS.md
create mode 100644 README.md
diff --git a/ADDING_BUTTONS.md b/ADDING_BUTTONS.md
new file mode 100644
index 0000000..46a50de
--- /dev/null
+++ b/ADDING_BUTTONS.md
@@ -0,0 +1,360 @@
+# Adding New Buttons to the JavaFX Train GUI
+
+This document provides a comprehensive guide on how to add new buttons to the JavaFX Train GUI project. New buttons must wait for backend acknowledgment before allowing further interactions.
+
+## Table of Contents
+1. [Architecture Overview](#architecture-overview)
+2. [Step-by-Step Guide](#step-by-step-guide)
+3. [Complete Example](#complete-example)
+4. [Backend Acknowledgment Flow](#backend-acknowledgment-flow)
+5. [Testing](#testing)
+
+## Architecture Overview
+
+The button implementation follows this flow:
+
+```
+FXML (UI Definition)
+ ↓
+Controller (TrainController.java)
+ ↓
+ViewModel (TrainViewModel.java)
+ ↓
+DAOService (Data Access)
+ ↓
+UIStateService (Acknowledgment Tracking)
+ ↓
+Backend (Python/Server)
+```
+
+### Key Components
+
+1. **FXML File** - Defines the UI layout and button appearance
+2. **Controller** - Binds UI elements to ViewModel actions
+3. **ViewModel** - Contains business logic and state management
+4. **DAOService** - Manages data synchronization with backend
+5. **UIStateService** - Tracks pending operations and backend acknowledgments
+6. **HmiData** - Data transfer object containing button state and metadata
+
+## Step-by-Step Guide
+
+### Step 1: Define the Button in FXML
+
+Add your button to the appropriate FXML file (e.g., `trainScreen.fxml`):
+
+```xml
+
+```
+
+**Key attributes:**
+- `fx:id` - Unique identifier used to reference the button in the controller
+- `layoutX`, `layoutY` - Position on the screen
+- `text` - Button label
+
+### Step 2: Add Button Reference in Controller
+
+In your controller class (e.g., `TrainController.java`), add:
+
+```java
+@FXML private Button MyNewButton;
+```
+
+### Step 3: Create Action Method in ViewModel
+
+In your ViewModel class (e.g., `TrainViewModel.java`), add an action method:
+
+```java
+/**
+ * Action for MyNewButton. Toggles the button state and waits for backend ACK.
+ * @param tag The HMI data tag associated with this button
+ */
+public void handleMyNewButton(String tag) {
+ findDataByTag(tag).ifPresent(data -> {
+ boolean currentState = data.getHmiValueb() != null && data.getHmiValueb();
+
+ // Mark this operation as pending - this prevents UI changes until ACK
+ UIStateService.getInstance().markPending(Set.of(String.valueOf(data.getIndex())));
+
+ // Update the data
+ data.setHmiValueb(!currentState);
+ data.setHmiReadi(2); // Signal to backend that HMI has updated this value
+
+ // Putting the data back triggers the listener and notifies the backend
+ daoService.getHmiDataMap().put(String.valueOf(data.getIndex()), data);
+ });
+}
+```
+
+### Step 4: Bind Button Action in Controller
+
+In the controller's `initialize()` method, bind the button to the ViewModel action:
+
+```java
+@FXML
+private void initialize() {
+ this.viewModel = new TrainViewModel();
+
+ // Bind button action to ViewModel
+ MyNewButton.setOnAction(event -> viewModel.handleMyNewButton("HMI_MyNewButtonb"));
+
+ // ... rest of initialization
+}
+```
+
+### Step 5: Register Button with Backend
+
+Ensure the backend database has an entry for your button. The Python backend uses TinyDB with entries like:
+
+```python
+{
+ "INDEX": 20,
+ "TAG": "HMI_MyNewButtonb",
+ "HMI_VALUEb": False,
+ "HMI_VALUEi": 1,
+ "PI_VALUEb": False,
+ "PI_VALUEf": 0.0,
+ "HMI_READi": 1
+}
+```
+
+## Complete Example
+
+Here's a complete example of adding a "Train Light" button:
+
+### 1. FXML (trainScreen.fxml)
+
+```xml
+
+```
+
+### 2. Controller (TrainController.java)
+
+```java
+public class TrainController {
+ private TrainViewModel viewModel;
+
+ @FXML private Button TrainLight;
+
+ @FXML
+ private void initialize() {
+ this.viewModel = new TrainViewModel();
+
+ // Bind the train light button
+ TrainLight.setOnAction(event -> viewModel.toggleTrainLight("HMI_TrainLightb"));
+
+ System.out.println("TrainController initialized and bound to TrainViewModel.");
+ }
+}
+```
+
+### 3. ViewModel (TrainViewModel.java)
+
+```java
+public class TrainViewModel implements HMIControllerInterface {
+ private final DAOService daoService;
+
+ public TrainViewModel() {
+ this.daoService = DAOService.getInstance();
+ new HMIChangeListener(daoService.getHmiJsonDao(), this);
+ }
+
+ /**
+ * Toggle the train light on/off and wait for backend acknowledgment.
+ */
+ public void toggleTrainLight(String tag) {
+ findDataByTag(tag).ifPresent(data -> {
+ boolean currentState = data.getHmiValueb() != null && data.getHmiValueb();
+
+ // Mark as pending - blocks further UI interactions
+ UIStateService.getInstance().markPending(Set.of(String.valueOf(data.getIndex())));
+
+ // Toggle the state
+ data.setHmiValueb(!currentState);
+
+ // Set HMI_READi to 2 to signal HMI update to backend
+ data.setHmiReadi(2);
+
+ // Trigger backend update
+ daoService.getHmiDataMap().put(String.valueOf(data.getIndex()), data);
+
+ System.out.println("[TrainViewModel] Train light toggled to: " + !currentState);
+ });
+ }
+
+ private Optional findDataByTag(String tag) {
+ return daoService.getHmiDataMap().values().stream()
+ .filter(d -> tag.equals(d.getTag()))
+ .findFirst();
+ }
+
+ @Override
+ public void onMapUpdate(String key, Object oldValue, Object newValue) {
+ if (!(newValue instanceof HmiData)) return;
+ HmiData data = (HmiData) newValue;
+
+ // Check for backend acknowledgment
+ UIStateService.getInstance().checkAck(key, data);
+
+ // Handle other updates...
+ }
+}
+```
+
+### 4. Backend Database Entry
+
+Add to the Python backend's database initialization:
+
+```python
+PIdb.insert({
+ "INDEX": 20,
+ "TAG": "HMI_TrainLightb",
+ "HMI_VALUEb": False,
+ "HMI_VALUEi": 1,
+ "PI_VALUEb": False,
+ "PI_VALUEf": 0.0,
+ "HMI_READi": 1
+})
+```
+
+## Backend Acknowledgment Flow
+
+The backend acknowledgment system ensures data synchronization between the frontend and backend:
+
+### How It Works
+
+1. **User Clicks Button** → Controller calls ViewModel action
+2. **ViewModel Marks Pending** → `UIStateService.markPending()` is called with the data key(s)
+3. **Data Updated** → HMI_READi is set to 2, signaling frontend initiated the change
+4. **Backend Receives Update** → Python server processes the change
+5. **Backend Acknowledges** → Backend sets HMI_READi to 1 (or 0/null) after processing
+6. **Frontend Detects ACK** → `UIStateService.checkAck()` removes the key from pending set
+7. **UI Unblocked** → When all pending keys are acknowledged, `waitingForServer` becomes false
+
+### UIStateService Methods
+
+```java
+// Mark operations as pending (call before updating data)
+UIStateService.getInstance().markPending(Set.of(dataKey));
+
+// Check for acknowledgment (called automatically in onMapUpdate)
+UIStateService.getInstance().checkAck(key, updatedHmiData);
+
+// Check if waiting for server
+boolean isWaiting = UIStateService.getInstance().isWaitingForServer();
+```
+
+### HMI_READi Values
+
+- `1` - Backend has read the value (normal state)
+- `2` - Frontend has updated the value (waiting for backend)
+- `0` or `null` - Also considered as acknowledged
+
+**Important:** The backend must change HMI_READi from 2 to any other value (typically 1) to signal acknowledgment.
+
+## Testing
+
+### Manual Testing
+
+1. **Build the project:**
+ ```bash
+ mvn clean install
+ ```
+
+2. **Start the backend server:**
+ ```bash
+ python backend/src/main/PythonScripts/server20a.py
+ ```
+
+3. **Run the frontend:**
+ ```bash
+ mvn -pl frontend javafx:run
+ ```
+
+4. **Test the button:**
+ - Click the button
+ - Observe console output for "[UIState] Pending started for keys=..."
+ - Wait for "[UIState] ACK received for key=..."
+ - Verify "[UIState] All ACKed. Waiting cleared."
+
+### Unit Testing
+
+Create a test in `frontend/src/test/java/org/viewModels/`:
+
+```java
+@Test
+void testButtonToggleAndAck() {
+ // Setup
+ TrainViewModel viewModel = new TrainViewModel();
+ UIStateService uiStateService = UIStateService.getInstance();
+
+ // Action - Toggle button
+ viewModel.toggleTrainLight("HMI_TrainLightb");
+
+ // Verify - Should be waiting for server
+ assertTrue(uiStateService.isWaitingForServer());
+
+ // Simulate backend acknowledgment
+ HmiData ackData = new HmiData();
+ ackData.setHmiReadi(1);
+ uiStateService.checkAck("20", ackData);
+
+ // Verify - Should no longer be waiting
+ assertFalse(uiStateService.isWaitingForServer());
+}
+```
+
+## Common Pitfalls
+
+1. **Forgetting to mark pending** → UI won't block and may allow duplicate actions
+2. **Wrong TAG name** → Button won't find its data in the map
+3. **Missing backend entry** → Button will have no data to update
+4. **Backend not clearing HMI_READi** → UI will remain blocked indefinitely
+5. **Not calling checkAck in onMapUpdate** → Acknowledgments won't be detected
+
+## Best Practices
+
+1. **Always use `markPending()`** before updating data
+2. **Use descriptive TAG names** following the pattern: `HMI_b` (e.g., `HMI_TrainLightb`)
+3. **Set HMI_READi to 2** when frontend initiates a change
+4. **Implement proper error handling** in case backend doesn't respond
+5. **Log state changes** for debugging
+6. **Test with backend disconnected** to verify timeout/error behavior
+7. **Use consistent naming** across FXML, Controller, ViewModel, and Backend
+
+## Additional Resources
+
+- **UIStateService.java** - Manages pending operations and ACKs
+- **TrainViewModel.java** - Example ViewModel implementation
+- **TrainController.java** - Example Controller implementation
+- **HmiData.java** - Data structure definition
+- **DAOService.java** - Data access layer
+
+## Summary
+
+Adding a new button requires:
+1. ✅ Define button in FXML with unique fx:id
+2. ✅ Add @FXML reference in Controller
+3. ✅ Create action method in ViewModel
+4. ✅ Bind button to action in Controller's initialize()
+5. ✅ Call `markPending()` before data update
+6. ✅ Set `HMI_READi = 2` when updating data
+7. ✅ Ensure backend has matching database entry
+8. ✅ Verify backend clears HMI_READi after processing
+
+The key requirement is that **buttons must wait for backend acknowledgment** using the `UIStateService` to ensure data consistency between frontend and backend.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5d16eea
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+# JavaFX Train GUI
+
+A JavaFX-based graphical user interface for controlling a model train system with real-time synchronization to a Python backend.
+
+## Project Structure
+
+- **frontend/** - JavaFX frontend application
+- **backend/** - Java backend services and Python control scripts
+- **docs/** - Documentation
+
+## Documentation
+
+- **[Adding Buttons Guide](ADDING_BUTTONS.md)** - Comprehensive guide on how to add new buttons to the project with backend acknowledgment
+
+## Building the Project
+
+```bash
+mvn clean install
+```
+
+## Running the Application
+
+1. Start the backend server:
+ ```bash
+ python backend/src/main/PythonScripts/server20a.py
+ ```
+
+2. Run the frontend:
+ ```bash
+ mvn -pl frontend javafx:run
+ ```
+
+## Requirements
+
+- Java 21+
+- Maven 3.6+
+- Python 3.x (for backend)
+
+## Architecture
+
+The application uses a multi-layer architecture:
+- **View Layer** (FXML) - UI definition
+- **Controller Layer** - UI event handling
+- **ViewModel Layer** - Business logic and state management
+- **Service Layer** - Data access and synchronization
+- **Backend Layer** - Python-based hardware control
+
+## Contributing
+
+When adding new features, especially UI buttons, please refer to the [Adding Buttons Guide](ADDING_BUTTONS.md) to ensure proper implementation with backend acknowledgment.
From a171c3007a17c3e4d986cc4e0b37fb61dbdcbc85 Mon Sep 17 00:00:00 2001
From: Sam <96598232+SamWalts@users.noreply.github.com>
Date: Fri, 3 Oct 2025 20:34:08 -0500
Subject: [PATCH 3/6] Update ADDING_BUTTONS.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
ADDING_BUTTONS.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/ADDING_BUTTONS.md b/ADDING_BUTTONS.md
index 46a50de..bbd8774 100644
--- a/ADDING_BUTTONS.md
+++ b/ADDING_BUTTONS.md
@@ -261,11 +261,11 @@ boolean isWaiting = UIStateService.getInstance().isWaitingForServer();
### HMI_READi Values
-- `1` - Backend has read the value (normal state)
-- `2` - Frontend has updated the value (waiting for backend)
-- `0` or `null` - Also considered as acknowledged
+- `0` - Not sent
+- `1` - Sent (frontend updated/pending)
+- `2` - Acknowledged by backend
-**Important:** The backend must change HMI_READi from 2 to any other value (typically 1) to signal acknowledgment.
+**Important:** The backend sets HMI_READi to 2 to acknowledge.
## Testing
From 4a20e63c9cf2f771e0d58698048529c346b402d3 Mon Sep 17 00:00:00 2001
From: Sam <96598232+SamWalts@users.noreply.github.com>
Date: Fri, 3 Oct 2025 20:35:03 -0500
Subject: [PATCH 4/6] Update ADDING_BUTTONS.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
ADDING_BUTTONS.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ADDING_BUTTONS.md b/ADDING_BUTTONS.md
index bbd8774..f21c41c 100644
--- a/ADDING_BUTTONS.md
+++ b/ADDING_BUTTONS.md
@@ -85,7 +85,7 @@ public void handleMyNewButton(String tag) {
// Update the data
data.setHmiValueb(!currentState);
- data.setHmiReadi(2); // Signal to backend that HMI has updated this value
+ data.setHmiReadi(1); // Signal to backend that HMI has updated this value (pending ACK)
// Putting the data back triggers the listener and notifies the backend
daoService.getHmiDataMap().put(String.valueOf(data.getIndex()), data);
From 784d583b939f73b9bd82ae4eda0c4a3ae58fdba7 Mon Sep 17 00:00:00 2001
From: Sam <96598232+SamWalts@users.noreply.github.com>
Date: Fri, 3 Oct 2025 20:35:11 -0500
Subject: [PATCH 5/6] Update ADDING_BUTTONS.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
ADDING_BUTTONS.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ADDING_BUTTONS.md b/ADDING_BUTTONS.md
index f21c41c..f26750c 100644
--- a/ADDING_BUTTONS.md
+++ b/ADDING_BUTTONS.md
@@ -188,7 +188,7 @@ public class TrainViewModel implements HMIControllerInterface {
data.setHmiValueb(!currentState);
// Set HMI_READi to 2 to signal HMI update to backend
- data.setHmiReadi(2);
+ data.setHmiReadi(1);
// Trigger backend update
daoService.getHmiDataMap().put(String.valueOf(data.getIndex()), data);
From c281da8fd130e2e99ada057cf46450ff5ca98bad Mon Sep 17 00:00:00 2001
From: Sam <96598232+SamWalts@users.noreply.github.com>
Date: Fri, 3 Oct 2025 20:35:38 -0500
Subject: [PATCH 6/6] Update ADDING_BUTTONS.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
ADDING_BUTTONS.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ADDING_BUTTONS.md b/ADDING_BUTTONS.md
index f26750c..736307e 100644
--- a/ADDING_BUTTONS.md
+++ b/ADDING_BUTTONS.md
@@ -240,9 +240,9 @@ The backend acknowledgment system ensures data synchronization between the front
1. **User Clicks Button** → Controller calls ViewModel action
2. **ViewModel Marks Pending** → `UIStateService.markPending()` is called with the data key(s)
-3. **Data Updated** → HMI_READi is set to 2, signaling frontend initiated the change
+3. **Data Updated** → HMI_READi is set to 1 (frontend sent/pending)
4. **Backend Receives Update** → Python server processes the change
-5. **Backend Acknowledges** → Backend sets HMI_READi to 1 (or 0/null) after processing
+5. **Backend Acknowledges** → Backend sets HMI_READi to 2 (acknowledged) after processing
6. **Frontend Detects ACK** → `UIStateService.checkAck()` removes the key from pending set
7. **UI Unblocked** → When all pending keys are acknowledged, `waitingForServer` becomes false