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