diff --git a/.gitignore b/.gitignore index 4d7139e..68563dc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ # Extra Stuff /vscode/ + + +# Build directories +dist/ \ No newline at end of file diff --git a/test-app/.babelrc b/test-app/.babelrc new file mode 100644 index 0000000..18151f1 --- /dev/null +++ b/test-app/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] +} \ No newline at end of file diff --git a/test-app/package.json b/test-app/package.json index 6d84b16..adf5ea9 100644 --- a/test-app/package.json +++ b/test-app/package.json @@ -3,15 +3,34 @@ "version": "2.0.0", "main": "src/main.js", "scripts": { - "start": "electron ." + "start": "electron .", + "build": "webpack --mode production", + "dev": "webpack --mode production && electron ." }, "dependencies": { - "rclnodejs": "^0.27.4", - "fitty": "^2.3.6" + "fitty": "^2.3.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router-dom": "^7.8.2" }, "devDependencies": { + "@babel/core": "^7.28.3", + "@babel/preset-env": "^7.28.3", + "@babel/preset-react": "^7.27.1", "@electron/rebuild": "^3.6.0", + "autoprefixer": "^10.4.21", + "babel-loader": "^10.0.0", + "concurrently": "^9.2.1", + "css-loader": "^7.1.2", "electron": "^31.0.0", - "material-icons": "^1.13.9" + "html-webpack-plugin": "^5.6.4", + "material-icons": "^1.13.9", + "postcss": "^8.5.6", + "postcss-loader": "^8.2.0", + "style-loader": "^4.0.0", + "wait-on": "^8.0.4", + "webpack": "^5.101.3", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.2" } } diff --git a/test-app/src/lock_screen.html b/test-app/src/lock_screen.html index acefd4c..a55f16f 100644 --- a/test-app/src/lock_screen.html +++ b/test-app/src/lock_screen.html @@ -72,7 +72,6 @@ font-size: 125%; } .btn.pressed { - transform: scale(1.05); opacity: 0.8; } /* .shutdown { @@ -149,20 +148,8 @@ border-radius: 50%; background: rgb(95, 95, 95); margin: 0px 5px; - animation: scale 0.3s ease-in-out; /* Animation! */ - } - @keyframes scale { - from { - transform: scale(1.4); - } } .dot.removing { - animation: scaleIn 0.15s ease-in-out; - } - @keyframes scaleIn { - to { - transform: scale(0); - } } diff --git a/test-app/src/main.js b/test-app/src/main.js index 226b153..961c018 100644 --- a/test-app/src/main.js +++ b/test-app/src/main.js @@ -1,71 +1,86 @@ -const { app, BrowserWindow, ipcMain } = require('electron'); -const path = require('node:path'); +const { app, BrowserWindow, ipcMain } = require("electron"); +const path = require("node:path"); app.disableHardwareAcceleration(); // Disable GPU acceleration //-------------------------------------------------------------------------------------------------- // ROS 2 Setup Stuff -const rclnodejs = require('rclnodejs'); -let node = null; +// const rclnodejs = require('rclnodejs'); +// let node = null; let publisher_light, publisher_brake, publisher_lidar, publisher_selfdrive; +// Track current status of each system +let systemStatus = { + light: 'OFF', + brake: 'ON', + lidar: 'OFF', + selfdrive: 'OFF' +}; + async function initROS() { - await rclnodejs.init(); + console.log('ROS initialization disabled for testing'); + // All ROS functionality is commented out since rclnodejs is not available + // await rclnodejs.init(); // Create node - node = new rclnodejs.Node('electron_node'); + // node = new rclnodejs.Node('electron_node'); //-------------------------------------------------------------------------- // Create publishers - const message_string = 'std_msgs/msg/String'; - const message_int= 'std_msgs/msg/Int32'; + // const message_string = "std_msgs/msg/String"; + // const message_int = "std_msgs/msg/Int32"; - const topic_light = 'electron_light'; - publisher_light = node.createPublisher(message_string, topic_light); + // const topic_light = "electron_light"; + // publisher_light = node.createPublisher(message_string, topic_light); - const topic_brake = 'electron_brake'; - publisher_brake = node.createPublisher(message_string, topic_brake); + // const topic_brake = "electron_brake"; + // publisher_brake = node.createPublisher(message_string, topic_brake); - const topic_lidar = 'electron_lidar'; - publisher_lidar = node.createPublisher(message_string, topic_lidar); + // const topic_lidar = "electron_lidar"; + // publisher_lidar = node.createPublisher(message_string, topic_lidar); - const topic_selfdrive = 'electron_selfdrive'; - publisher_selfdrive = node.createPublisher(message_int, topic_selfdrive); + // const topic_selfdrive = "electron_selfdrive"; + // publisher_selfdrive = node.createPublisher(message_int, topic_selfdrive); - const topic_room = 'electron_room'; - publisher_room = node.createPublisher(message_int, topic_room); + // const topic_room = "electron_room"; + // publisher_room = node.createPublisher(message_int, topic_room); //-------------------------------------------------------------------------- // Create subscribers - const topic_battery = 'electron_battery'; - node.createSubscription(message_string, topic_battery, (msg) => { - console.log(`Received from Jetson: ${msg.data}`); - - temp = "BATTERY_" + msg.data; - if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents) { - mainWindow.webContents.send('update-display', temp); - } - }); - - const topic_speed = 'electron_speed'; - node.createSubscription(message_string, topic_speed, (msg) => { - console.log(`Received from Jetson: ${msg.data}`); - - temp = "SPEED_" + msg.data; - if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents) { - mainWindow.webContents.send('update-display', temp); - } - }); - - node.spin(); - console.log(`Created all publishers and subscribers. Waiting...`); - - defaultPublish(); - console.log(`Published default values. Waiting...`); + // const topic_battery = "electron_battery"; + // node.createSubscription(message_string, topic_battery, (msg) => { + // console.log(`Received from Jetson: ${msg.data}`); + // temp = "BATTERY_" + msg.data; + // if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents) { + // mainWindow.webContents.send("update-display", temp); + // } + // }); + + // const topic_speed = "electron_speed"; + // node.createSubscription(message_string, topic_speed, (msg) => { + // console.log(`Received from Jetson: ${msg.data}`); + // temp = "SPEED_" + msg.data; + // if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents) { + // mainWindow.webContents.send("update-display", temp); + // } + // }); + + // node.spin(); + // console.log(`Created all publishers and subscribers. Waiting...`); + + // defaultPublish(); + // console.log(`Published default values. Waiting...`); } //-------------------------------------------------------------------------------------------------- function defaultPublish() { + if (!publisher_light || !publisher_brake || !publisher_lidar || !publisher_selfdrive) { + console.log('Publishers not initialized, skipping defaultPublish'); + // Send default status to UI even without ROS + sendStatusToUI(); + return; + } + let temp = "LIGHT_OFF"; publisher_light.publish({ data: temp }); console.log(`---> Published from Electron app: ${temp}`); @@ -81,6 +96,18 @@ function defaultPublish() { temp = 0; publisher_selfdrive.publish({ data: temp }); console.log(`---> Published from Electron app: ${temp}`); + + sendStatusToUI(); +} + +function sendStatusToUI() { + if (mainWindow && !mainWindow.isDestroyed()) { + // Send individual status updates + mainWindow.webContents.send("update-display", `LIGHT_${systemStatus.light}`); + mainWindow.webContents.send("update-display", `BRAKE_${systemStatus.brake}`); + mainWindow.webContents.send("update-display", `LIDAR_${systemStatus.lidar}`); + mainWindow.webContents.send("update-display", `SELFDRIVE_${systemStatus.selfdrive}`); + } } //-------------------------------------------------------------------------------------------------- @@ -90,53 +117,33 @@ let lockWindow; let isShuttingDown = false; let actualShutdown = true; -function createLockWindow() { - lockWindow = new BrowserWindow({ +function createWindow() { + mainWindow = new BrowserWindow({ width: 970, height: 750, autoHideMenuBar: true, webPreferences: { - preload: path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, "preload.js"), contextIsolation: true, - nodeIntegration: false - } + nodeIntegration: false, + }, }); - lockWindow.loadFile(path.join(__dirname, 'lock_screen.html')); - lockWindow.maximize(); - - lockWindow.on('close', (event) => { - if (!actualShutdown) { - return; + const isDev = process.env.NODE_ENV === 'development'; + if (isDev) { + mainWindow.loadURL('http://localhost:8080'); + } else { + mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); } - - shutdownApp(); -}); -} - -function createMainWindow() { - mainWindow = new BrowserWindow({ // Create the window variable using BrowserWindow - width: 970, - height: 750, - frame: true, - autoHideMenuBar: true, // Hide menu bar - webPreferences: { - preload: path.join(__dirname, 'preload.js'), // __dirname gives the absolute path - contextIsolation: true, // Stuff to use with preload.js separation - nodeIntegration: false - } - }); - - mainWindow.loadFile(path.join(__dirname, 'index.html')); mainWindow.maximize(); - mainWindow.on('close', (event) => { - if (!actualShutdown) { - return; - } + mainWindow.on("close", (event) => { + if (!actualShutdown) { + return; + } - shutdownApp(); -}); + shutdownApp(); + }); } //-------------------------------------------------------------------- @@ -166,79 +173,107 @@ async function shutdownApp() { //-------------------------------------------------------------------------------------------------- // Interaction with render process -ipcMain.on('button-action', (event, action) => { +ipcMain.on("button-action", (event, action) => { console.log(`Received button action: ${action}`); // Lock Screen - if(action == "LOCK") { - actualShutdown = false; - mainWindow.close(); - actualShutdown = true; - createLockWindow(); + if (action == "LOCK") { + // Send lock message to React app + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("update-display", "LOCK"); + } defaultPublish(); } // Publish to ROS 2 & send display updates - if(action == "LIGHT_FLASHING" || action == "LIGHT_STEADY" || action == "LIGHT_OFF") { - publisher_light.publish({ data: action }); - console.log(`---> Published from Electron app: ${action}`); + if ( + action == "LIGHT_FLASHING" || + action == "LIGHT_STEADY" || + action == "LIGHT_OFF" + ) { + systemStatus.light = action.replace('LIGHT_', ''); + if (publisher_light) { + publisher_light.publish({ data: action }); + console.log(`---> Published from Electron app: ${action}`); + } } - if(action == "BRAKE_ON" || action == "BRAKE_OFF") { - publisher_brake.publish({ data: action }); - console.log(`---> Published from Electron app: ${action}`); + if (action == "BRAKE_ON" || action == "BRAKE_OFF") { + systemStatus.brake = action.replace('BRAKE_', ''); + if (publisher_brake) { + publisher_brake.publish({ data: action }); + console.log(`---> Published from Electron app: ${action}`); + } } if (action == "LIDAR_ON" || action == "LIDAR_OFF") { - publisher_lidar.publish({ data: action }); - console.log(`---> Published from Electron app: ${action}`); + systemStatus.lidar = action.replace('LIDAR_', ''); + if (publisher_lidar) { + publisher_lidar.publish({ data: action }); + console.log(`---> Published from Electron app: ${action}`); + } } //------------------------------------------------------------------------- // Self-drive Actions if (action == "SELFDRIVE_FULL") { // Self-drive full + systemStatus.selfdrive = 'FULL'; const msg = { data: 2 }; - publisher_selfdrive.publish(msg); - console.log(`---> Published from Electron app: ${msg.data}`); - } - else if (action == "SELFDRIVE_JOYSTICK") { + if (publisher_selfdrive) { + publisher_selfdrive.publish(msg); + console.log(`---> Published from Electron app: ${msg.data}`); + } + } else if (action == "SELFDRIVE_JOYSTICK") { // Self-drive w/Joystick + systemStatus.selfdrive = 'JOYSTICK'; const msg = { data: 1 }; - publisher_selfdrive.publish(msg); - console.log(`---> Published from Electron app: ${msg.data}`); - } - else if (action == "SELFDRIVE_OFF") { + if (publisher_selfdrive) { + publisher_selfdrive.publish(msg); + console.log(`---> Published from Electron app: ${msg.data}`); + } + } else if (action == "SELFDRIVE_OFF") { // Self-drive off + systemStatus.selfdrive = 'OFF'; const msg = { data: 0 }; - publisher_selfdrive.publish(msg); - console.log(`---> Published from Electron app: ${msg.data}`); + if (publisher_selfdrive) { + publisher_selfdrive.publish(msg); + console.log(`---> Published from Electron app: ${msg.data}`); + } } //------------------------------------------------------------------------- // Room destination actions - if(action.startsWith('ROOM_')) { - temp = action.substring(action.indexOf('_') + 1); + if (action.startsWith("ROOM_")) { + temp = action.substring(action.indexOf("_") + 1); const msg = { data: temp }; - publisher_room.publish(msg); - console.log(`---> Published from Electron app: ${msg.data}`); + if (publisher_room) { + publisher_room.publish(msg); + console.log(`---> Published from Electron app: ${msg.data}`); + } } //------------------------------------------------------------------------- // Send action name for screen update - mainWindow.webContents.send('update-display', action); + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("update-display", action); + } }); //------------------------------------------------------------ // Switching windows // Unlock -ipcMain.on('send-password', (event, password) => { +ipcMain.on("send-password", (event, password) => { const ACTUAL_PASSWORD = 7034; if (password == ACTUAL_PASSWORD) { - actualShutdown = false; - lockWindow.close(); - actualShutdown = true; - createMainWindow(); + // Send navigation message to React app + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("update-display", "NAVIGATE_TO_MAIN"); + // Send initial status after navigation + setTimeout(() => { + sendStatusToUI(); + }, 100); + } defaultPublish(); } }); @@ -246,16 +281,14 @@ ipcMain.on('send-password', (event, password) => { //-------------------------------------------------------------------------------------------------- // Other necessary stuff app.whenReady().then(async () => { - - createLockWindow(); + createWindow(); initROS(); - app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) createWindows(); + app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); - }); -app.on('window-all-closed', function () { - if (process.platform !== 'darwin') app.quit(); +app.on("window-all-closed", function () { + if (process.platform !== "darwin") app.quit(); }); diff --git a/test-app/src/preload.js b/test-app/src/preload.js index f902351..3e77be9 100644 --- a/test-app/src/preload.js +++ b/test-app/src/preload.js @@ -1,14 +1,14 @@ -// Use contextBrindge to safely expose an API to the render process +// Use contextBridge to safely expose an API to the render process const { contextBridge, ipcRenderer } = require('electron/renderer'); -// Custom API named "electronAppAPI" +// Custom API named "electronAPI" // This allows the preload script to be loaded into the main process (main.js), -// while using the custom API in the render processes (renderer.js) -contextBridge.exposeInMainWorld('electronAppAPI', { +// while using the custom API in the render processes (React app) +contextBridge.exposeInMainWorld('electronAPI', { // General purpose button - sendAction: (action) => ipcRenderer.send('button-action', action), + sendButtonAction: (action) => ipcRenderer.send('button-action', action), - onDisplayUpdate: (callback) => ipcRenderer.on('update-display', (_, msg) => callback(msg)), + onUpdateDisplay: (callback) => ipcRenderer.on('update-display', (_, msg) => callback(msg)), sendPassword: (password) => ipcRenderer.send('send-password', password) }); \ No newline at end of file diff --git a/test-app/src/renderer/App.jsx b/test-app/src/renderer/App.jsx new file mode 100644 index 0000000..0019983 --- /dev/null +++ b/test-app/src/renderer/App.jsx @@ -0,0 +1,37 @@ +import React, { useState, useEffect } from 'react'; +import MainScreen from './components/MainScreen'; +import LockScreen from './components/LockScreen'; + +function App() { + const [isUnlocked, setIsUnlocked] = useState(false); + + useEffect(() => { + if (window.electronAPI) { + // Listen for navigation events from main process + window.electronAPI.onUpdateDisplay((message) => { + // Check if we should navigate to main screen + if (message === 'NAVIGATE_TO_MAIN') { + setIsUnlocked(true); + } else if (message === 'LOCK') { + setIsUnlocked(false); + } + }); + } + }, []); + + return ( +
+ Enter your PIN to continue +
+